94 lines
4.1 KiB
JavaScript
94 lines
4.1 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import test from "node:test";
|
|
import ts from "typescript";
|
|
|
|
const repoRoot = path.resolve(import.meta.dirname, "../../..");
|
|
const sourcePath = path.join(repoRoot, "web/protocol/user-action-state.ts");
|
|
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
|
|
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
|
|
fileName: sourcePath,
|
|
reportDiagnostics: true,
|
|
});
|
|
assert.deepEqual(transpiled.diagnostics, []);
|
|
const moduleUrl = `data:text/javascript;base64,${Buffer.from(transpiled.outputText).toString("base64")}`;
|
|
const {
|
|
USER_ACTION_KINDS,
|
|
createInitialUserActionStates,
|
|
reduceUserActionStates,
|
|
transitionUserAction,
|
|
} = await import(moduleUrl);
|
|
|
|
function identity(kind, sequence = 1) {
|
|
return { kind, actionId: `${kind}:${sequence}` };
|
|
}
|
|
|
|
test("M7-01 initializes all five user actions at IDLE", () => {
|
|
const states = createInitialUserActionStates();
|
|
assert.deepEqual(Object.keys(states), USER_ACTION_KINDS);
|
|
for (const kind of USER_ACTION_KINDS) {
|
|
assert.deepEqual(states[kind], { kind, status: "IDLE", identity: null, errorCode: null });
|
|
}
|
|
});
|
|
|
|
test("M7-01 records deterministic RUNNING and SUCCEEDED transitions for every action", () => {
|
|
let states = createInitialUserActionStates();
|
|
for (const kind of USER_ACTION_KINDS) {
|
|
const currentIdentity = identity(kind);
|
|
states = reduceUserActionStates(states, { type: "START", identity: currentIdentity });
|
|
assert.deepEqual(states[kind], { kind, status: "RUNNING", identity: currentIdentity, errorCode: null });
|
|
states = reduceUserActionStates(states, { type: "SUCCEED", identity: currentIdentity });
|
|
assert.deepEqual(states[kind], { kind, status: "SUCCEEDED", identity: currentIdentity, errorCode: null });
|
|
}
|
|
});
|
|
|
|
test("M7-01 records stable failure and cancellation codes", () => {
|
|
const failedIdentity = identity("OPEN");
|
|
let failed = transitionUserAction(createInitialUserActionStates().OPEN, { type: "START", identity: failedIdentity });
|
|
assert.equal(failed.ok, true);
|
|
failed = transitionUserAction(failed.state, { type: "FAIL", identity: failedIdentity, errorCode: "OPEN_ENGINE_FAILED" });
|
|
assert.deepEqual(failed, {
|
|
ok: true,
|
|
state: { kind: "OPEN", status: "FAILED", identity: failedIdentity, errorCode: "OPEN_ENGINE_FAILED" },
|
|
});
|
|
|
|
const cancelledIdentity = identity("IMPORT");
|
|
let cancelled = transitionUserAction(createInitialUserActionStates().IMPORT, { type: "START", identity: cancelledIdentity });
|
|
assert.equal(cancelled.ok, true);
|
|
cancelled = transitionUserAction(cancelled.state, { type: "CANCEL", identity: cancelledIdentity });
|
|
assert.deepEqual(cancelled, {
|
|
ok: true,
|
|
state: { kind: "IMPORT", status: "CANCELLED", identity: cancelledIdentity, errorCode: "USER_ACTION_CANCELLED" },
|
|
});
|
|
});
|
|
|
|
test("M7-01 rejects wrong identities, reused identities and invalid transitions without mutation", () => {
|
|
const first = identity("SAVE");
|
|
const other = identity("SAVE", 2);
|
|
const initial = createInitialUserActionStates().SAVE;
|
|
const beforeStart = transitionUserAction(initial, { type: "SUCCEED", identity: first });
|
|
assert.deepEqual(beforeStart, { ok: false, state: initial, errorCode: "USER_ACTION_INVALID_TRANSITION" });
|
|
|
|
const running = transitionUserAction(initial, { type: "START", identity: first });
|
|
assert.equal(running.ok, true);
|
|
assert.deepEqual(transitionUserAction(running.state, { type: "FAIL", identity: other, errorCode: "SAVE_FAILED" }), {
|
|
ok: false,
|
|
state: running.state,
|
|
errorCode: "USER_ACTION_IDENTITY_MISMATCH",
|
|
});
|
|
assert.deepEqual(transitionUserAction(running.state, { type: "FAIL", identity: first, errorCode: "not-stable" }), {
|
|
ok: false,
|
|
state: running.state,
|
|
errorCode: "USER_ACTION_INVALID_ERROR_CODE",
|
|
});
|
|
|
|
const succeeded = transitionUserAction(running.state, { type: "SUCCEED", identity: first });
|
|
assert.equal(succeeded.ok, true);
|
|
assert.deepEqual(transitionUserAction(succeeded.state, { type: "START", identity: first }), {
|
|
ok: false,
|
|
state: succeeded.state,
|
|
errorCode: "USER_ACTION_IDENTITY_REUSED",
|
|
});
|
|
});
|