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); }); });