Initial Commit
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import { FSM_CODES, FSMError } from "../errors/fsm-error.js";
|
||||
import { validateDefinition } from "./definition-validator.js";
|
||||
|
||||
describe("validateDefinition", () => {
|
||||
it("accepts a valid minimal definition", { timeout: 5000 }, () => {
|
||||
expect(() =>
|
||||
validateDefinition({
|
||||
initial: "idle",
|
||||
context: {},
|
||||
states: { idle: {} },
|
||||
})
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it("accepts a valid multi-state definition", { timeout: 5000 }, () => {
|
||||
expect(() =>
|
||||
validateDefinition({
|
||||
initial: "red",
|
||||
context: {},
|
||||
states: {
|
||||
red: { on: { NEXT: { target: "green" } } },
|
||||
green: { on: { NEXT: { target: "yellow" } } },
|
||||
yellow: { on: { NEXT: { target: "red" } } },
|
||||
},
|
||||
})
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it("throws INVALID_DEFINITION when initial state does not exist", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
let err: FSMError | undefined;
|
||||
try {
|
||||
validateDefinition({
|
||||
initial: "ghost",
|
||||
context: {},
|
||||
states: { idle: {} },
|
||||
});
|
||||
} catch (e) {
|
||||
err = e as FSMError;
|
||||
}
|
||||
expect(err).toBeInstanceOf(FSMError);
|
||||
expect(err?.code).toBe(FSM_CODES.INVALID_DEFINITION);
|
||||
expect(err?.message).toContain("ghost");
|
||||
});
|
||||
|
||||
it("throws INVALID_DEFINITION when transition targets non-existent state", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
let err: FSMError | undefined;
|
||||
try {
|
||||
validateDefinition({
|
||||
initial: "idle",
|
||||
context: {},
|
||||
states: {
|
||||
idle: { on: { GO: { target: "nowhere" } } },
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
err = e as FSMError;
|
||||
}
|
||||
expect(err).toBeInstanceOf(FSMError);
|
||||
expect(err?.code).toBe(FSM_CODES.INVALID_DEFINITION);
|
||||
expect(err?.message).toContain("nowhere");
|
||||
});
|
||||
|
||||
it("accepts multiple transition candidates for the same event", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
expect(() =>
|
||||
validateDefinition({
|
||||
initial: "idle",
|
||||
context: { count: 0 },
|
||||
states: {
|
||||
idle: {
|
||||
on: {
|
||||
GO: [{ target: "active" }, { target: "inactive" }],
|
||||
},
|
||||
},
|
||||
active: {},
|
||||
inactive: {},
|
||||
},
|
||||
})
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it("throws when nested initial child does not exist", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
let err: FSMError | undefined;
|
||||
try {
|
||||
validateDefinition({
|
||||
initial: "form",
|
||||
context: {},
|
||||
states: {
|
||||
form: {
|
||||
initial: "step999",
|
||||
states: {
|
||||
step1: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
err = e as FSMError;
|
||||
}
|
||||
expect(err).toBeInstanceOf(FSMError);
|
||||
expect(err?.code).toBe(FSM_CODES.INVALID_DEFINITION);
|
||||
expect(err?.message).toContain("step999");
|
||||
});
|
||||
|
||||
it("throws when nested states exist but no initial is declared", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
let err: FSMError | undefined;
|
||||
try {
|
||||
validateDefinition({
|
||||
initial: "form",
|
||||
context: {},
|
||||
states: {
|
||||
form: {
|
||||
states: {
|
||||
step1: {},
|
||||
},
|
||||
// initial deliberately omitted
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
err = e as FSMError;
|
||||
}
|
||||
expect(err).toBeInstanceOf(FSMError);
|
||||
expect(err?.code).toBe(FSM_CODES.INVALID_DEFINITION);
|
||||
});
|
||||
|
||||
it("accepts a valid nested definition with correct initial", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
expect(() =>
|
||||
validateDefinition({
|
||||
initial: "form",
|
||||
context: {},
|
||||
states: {
|
||||
form: {
|
||||
initial: "step1",
|
||||
states: {
|
||||
step1: { on: { NEXT: { target: "step2" } } },
|
||||
step2: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it("throws when a transition target in nested states is unknown", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
let err: FSMError | undefined;
|
||||
try {
|
||||
validateDefinition({
|
||||
initial: "form",
|
||||
context: {},
|
||||
states: {
|
||||
form: {
|
||||
initial: "step1",
|
||||
states: {
|
||||
step1: { on: { GO: { target: "ghost" } } },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
err = e as FSMError;
|
||||
}
|
||||
expect(err).toBeInstanceOf(FSMError);
|
||||
expect(err?.code).toBe(FSM_CODES.INVALID_DEFINITION);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user