@praxisjs/fsm 2.2.0 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,182 +0,0 @@
1
- import { describe, it, expect, vi } from "vitest";
2
- import { Transition, StateMachine } from "../decorators";
3
- import { createMachine } from "../machine";
4
- const TOGGLE_DEF = {
5
- initial: "off",
6
- states: {
7
- off: { on: { toggle: "on" } },
8
- on: { on: { toggle: "off" } },
9
- },
10
- };
11
- // Helper to simulate TC39 field decorator context
12
- function fieldCtx(name) {
13
- const initializers = [];
14
- return {
15
- ctx: {
16
- name,
17
- kind: "field",
18
- addInitializer(fn) { initializers.push(fn); },
19
- },
20
- run(instance) {
21
- initializers.forEach((fn) => { fn.call(instance); });
22
- },
23
- };
24
- }
25
- // Helper to simulate TC39 method decorator context
26
- function mockMethodContext(name) {
27
- const initializers = [];
28
- return {
29
- name,
30
- kind: "method",
31
- addInitializer(fn) { initializers.push(fn); },
32
- runInitializers(instance) {
33
- initializers.forEach((fn) => { fn.call(instance); });
34
- },
35
- };
36
- }
37
- // ── StateMachine ──────────────────────────────────────────────────────────────
38
- describe("StateMachine decorator", () => {
39
- it("injects a machine via field decorator", () => {
40
- const { ctx, run } = fieldCtx("machine");
41
- StateMachine(TOGGLE_DEF)(undefined, ctx);
42
- const instance = {};
43
- run(instance);
44
- expect(instance.machine).toBeDefined();
45
- expect(instance.machine.state()).toBe("off");
46
- });
47
- it("returns a separate machine per instance", () => {
48
- const { ctx, run } = fieldCtx("machine");
49
- StateMachine(TOGGLE_DEF)(undefined, ctx);
50
- const a = {};
51
- const b = {};
52
- run(a);
53
- run(b);
54
- a.machine.send("toggle");
55
- expect(a.machine.state()).toBe("on");
56
- expect(b.machine.state()).toBe("off");
57
- });
58
- it("supports a custom field name", () => {
59
- const { ctx, run } = fieldCtx("fsm");
60
- StateMachine(TOGGLE_DEF)(undefined, ctx);
61
- const instance = {};
62
- run(instance);
63
- expect(instance.fsm).toBeDefined();
64
- expect(instance.fsm.state()).toBe("off");
65
- });
66
- it("returns the same machine instance on repeated access", () => {
67
- const { ctx, run } = fieldCtx("machine");
68
- StateMachine(TOGGLE_DEF)(undefined, ctx);
69
- const instance = {};
70
- run(instance);
71
- expect(instance.machine).toBe(instance.machine);
72
- });
73
- it("setter is a no-op — assigning to the field does not replace the machine", () => {
74
- const { ctx, run } = fieldCtx("machine");
75
- StateMachine(TOGGLE_DEF)(undefined, ctx);
76
- const instance = {};
77
- run(instance);
78
- const original = instance.machine;
79
- instance.machine = "overwrite-attempt"; // triggers set(): void {} (no-op)
80
- expect(instance.machine).toBe(original);
81
- });
82
- it("accepts a factory function — machine created with instance as this", () => {
83
- const { ctx, run } = fieldCtx("machine");
84
- const instances = [];
85
- const factory = function () {
86
- instances.push(this);
87
- return TOGGLE_DEF;
88
- };
89
- StateMachine(factory)(undefined, ctx);
90
- const instance = {};
91
- run(instance);
92
- // Access triggers lazy creation
93
- const m = instance.machine;
94
- expect(m.state()).toBe("off");
95
- expect(instances).toHaveLength(1);
96
- expect(instances[0]).toBe(instance);
97
- });
98
- it("factory receives correct instance — guard can close over instance state", () => {
99
- const { ctx, run } = fieldCtx("machine");
100
- let allow = false;
101
- const factory = function () {
102
- return {
103
- initial: "a",
104
- states: {
105
- a: { on: { NEXT: { target: "b", guard: () => allow } } },
106
- b: {},
107
- },
108
- };
109
- };
110
- StateMachine(factory)(undefined, ctx);
111
- const instance = {};
112
- run(instance);
113
- const m = instance.machine;
114
- expect(m.send("NEXT")).toBe(false);
115
- allow = true;
116
- expect(m.send("NEXT")).toBe(true);
117
- expect(m.state()).toBe("b");
118
- });
119
- });
120
- // ── Transition ────────────────────────────────────────────────────────────────
121
- describe("Transition decorator", () => {
122
- it("calls the original method when send succeeds", () => {
123
- const original = vi.fn(() => "ran");
124
- const ctx = mockMethodContext("doTransition");
125
- Transition("machine", "toggle")(original, ctx);
126
- const instance = { machine: createMachine(TOGGLE_DEF) };
127
- ctx.runInitializers(instance);
128
- const method = instance.doTransition;
129
- method();
130
- expect(original).toHaveBeenCalled();
131
- });
132
- it("returns without calling method when machine is missing", () => {
133
- const warn = vi.spyOn(console, "warn").mockImplementation(() => { });
134
- const original = vi.fn();
135
- const ctx = mockMethodContext("doTransition");
136
- Transition("machine", "toggle")(original, ctx);
137
- const instance = {};
138
- ctx.runInitializers(instance);
139
- const method = instance.doTransition;
140
- method();
141
- expect(original).not.toHaveBeenCalled();
142
- expect(warn).toHaveBeenCalledWith(expect.stringContaining("[Transition]"));
143
- warn.mockRestore();
144
- });
145
- it("does not call method when transition is invalid", () => {
146
- const original = vi.fn();
147
- const ctx = mockMethodContext("doTransition");
148
- Transition("machine", "toggle")(original, ctx);
149
- const instance = {
150
- machine: createMachine({ initial: "on", states: { on: {} } }),
151
- };
152
- ctx.runInitializers(instance);
153
- const method = instance.doTransition;
154
- method();
155
- expect(original).not.toHaveBeenCalled();
156
- });
157
- it("passes through arguments to the original method", () => {
158
- const original = vi.fn((_x) => undefined);
159
- const ctx = mockMethodContext("doTransition");
160
- Transition("machine", "toggle")(original, ctx);
161
- const instance = { machine: createMachine(TOGGLE_DEF) };
162
- ctx.runInitializers(instance);
163
- const method = instance.doTransition;
164
- method(42);
165
- expect(original).toHaveBeenCalledWith(42);
166
- });
167
- it("@Transition on method without @StateMachine — warns and returns undefined", () => {
168
- const warn = vi.spyOn(console, "warn").mockImplementation(() => { });
169
- const original = vi.fn(() => "ran");
170
- const ctx = mockMethodContext("doTransition");
171
- Transition("machine", "toggle")(original, ctx);
172
- const instance = {};
173
- ctx.runInitializers(instance);
174
- const method = instance.doTransition;
175
- const result = method();
176
- expect(result).toBeUndefined();
177
- expect(original).not.toHaveBeenCalled();
178
- expect(warn).toHaveBeenCalledWith(expect.stringContaining("[Transition]"));
179
- warn.mockRestore();
180
- });
181
- });
182
- //# sourceMappingURL=decorators.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"decorators.test.js","sourceRoot":"","sources":["../../src/__tests__/decorators.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=machine.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"machine.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/machine.test.ts"],"names":[],"mappings":""}
@@ -1,448 +0,0 @@
1
- import { describe, it, expect, vi } from "vitest";
2
- import { createMachine } from "../machine";
3
- function trafficLight() {
4
- return createMachine({
5
- initial: "red",
6
- states: {
7
- red: { on: { GO: "green" } },
8
- green: { on: { SLOW: "yellow" } },
9
- yellow: { on: { STOP: "red" } },
10
- },
11
- });
12
- }
13
- describe("createMachine", () => {
14
- it("initializes in the given state", () => {
15
- const m = trafficLight();
16
- expect(m.state()).toBe("red");
17
- });
18
- it("transitions on a valid event", () => {
19
- const m = trafficLight();
20
- m.send("GO");
21
- expect(m.state()).toBe("green");
22
- });
23
- it("returns true from send() on valid transition", () => {
24
- const m = trafficLight();
25
- expect(m.send("GO")).toBe(true);
26
- });
27
- it("returns false from send() on invalid event", () => {
28
- const m = trafficLight();
29
- expect(m.send("SLOW")).toBe(false); // SLOW is not valid in red
30
- });
31
- it("does not change state on invalid event", () => {
32
- const m = trafficLight();
33
- m.send("STOP"); // invalid in red
34
- expect(m.state()).toBe("red");
35
- });
36
- it("can() returns true for valid events", () => {
37
- const m = trafficLight();
38
- expect(m.can("GO")).toBe(true);
39
- });
40
- it("can() returns false for invalid events", () => {
41
- const m = trafficLight();
42
- expect(m.can("SLOW")).toBe(false);
43
- });
44
- it("is() checks current state", () => {
45
- const m = trafficLight();
46
- expect(m.is("red")).toBe(true);
47
- expect(m.is("green")).toBe(false);
48
- });
49
- it("tracks history of transitions", () => {
50
- const m = trafficLight();
51
- m.send("GO");
52
- m.send("SLOW");
53
- const h = m.history();
54
- expect(h).toHaveLength(2);
55
- expect(h[0]).toEqual({ from: "red", event: "GO", to: "green" });
56
- expect(h[1]).toEqual({ from: "green", event: "SLOW", to: "yellow" });
57
- });
58
- it("resets to initial state and clears history", () => {
59
- const m = trafficLight();
60
- m.send("GO");
61
- m.send("SLOW");
62
- m.reset();
63
- expect(m.state()).toBe("red");
64
- expect(m.history()).toHaveLength(0);
65
- });
66
- it("calls onEnter on entering a state", () => {
67
- const onEnter = vi.fn();
68
- const m = createMachine({
69
- initial: "idle",
70
- states: {
71
- idle: { on: { START: "active" } },
72
- active: { onEnter },
73
- },
74
- });
75
- m.send("START");
76
- expect(onEnter).toHaveBeenCalledOnce();
77
- });
78
- it("calls onExit on leaving a state", () => {
79
- const onExit = vi.fn();
80
- const m = createMachine({
81
- initial: "idle",
82
- states: {
83
- idle: { on: { START: "active" }, onExit },
84
- active: {},
85
- },
86
- });
87
- m.send("START");
88
- expect(onExit).toHaveBeenCalledOnce();
89
- });
90
- it("onExit throwing prevents the state change and keeps history clean", () => {
91
- const m = createMachine({
92
- initial: "idle",
93
- states: {
94
- idle: {
95
- on: { START: "active" },
96
- onExit: () => { throw new Error("exit failed"); },
97
- },
98
- active: {},
99
- },
100
- });
101
- expect(() => m.send("START")).toThrow("exit failed");
102
- expect(m.state()).toBe("idle");
103
- expect(m.history()).toHaveLength(0);
104
- });
105
- it("onEnter throwing after state change leaves machine in the new state", () => {
106
- const m = createMachine({
107
- initial: "idle",
108
- states: {
109
- idle: { on: { START: "active" } },
110
- active: { onEnter: () => { throw new Error("enter failed"); } },
111
- },
112
- });
113
- expect(() => m.send("START")).toThrow("enter failed");
114
- // State was already committed before onEnter ran
115
- expect(m.state()).toBe("active");
116
- expect(m.history()).toHaveLength(1);
117
- });
118
- it("reset() calls onEnter of the initial state", () => {
119
- const onEnter = vi.fn();
120
- const m = createMachine({
121
- initial: "idle",
122
- states: {
123
- idle: { on: { START: "active" }, onEnter },
124
- active: {},
125
- },
126
- });
127
- m.send("START");
128
- m.reset();
129
- expect(onEnter).toHaveBeenCalledOnce();
130
- });
131
- it("calls onTransition callback", () => {
132
- const onTransition = vi.fn();
133
- const m = createMachine({
134
- initial: "a",
135
- states: {
136
- a: { on: { NEXT: "b" } },
137
- b: {},
138
- },
139
- onTransition,
140
- });
141
- m.send("NEXT");
142
- expect(onTransition).toHaveBeenCalledWith("a", "NEXT", "b");
143
- });
144
- it("state with on:{} — can() returns false for all events", () => {
145
- const m = createMachine({
146
- initial: "idle",
147
- states: {
148
- idle: { on: {} },
149
- },
150
- });
151
- expect(m.can("START")).toBe(false);
152
- expect(m.can("STOP")).toBe(false);
153
- });
154
- it("circular transition A→B→A — history contains correct entries", () => {
155
- const m = createMachine({
156
- initial: "a",
157
- states: {
158
- a: { on: { NEXT: "b" } },
159
- b: { on: { NEXT: "a" } },
160
- },
161
- });
162
- m.send("NEXT"); // a→b
163
- m.send("NEXT"); // b→a
164
- const h = m.history();
165
- expect(h).toHaveLength(2);
166
- expect(h[0]).toEqual({ from: "a", event: "NEXT", to: "b" });
167
- expect(h[1]).toEqual({ from: "b", event: "NEXT", to: "a" });
168
- });
169
- it("send() with undefined event — does not crash, returns false", () => {
170
- const m = trafficLight();
171
- // Cast to bypass TypeScript — runtime guard should handle it gracefully
172
- expect(m.send(undefined)).toBe(false);
173
- });
174
- it("onEnter callback is called AFTER the state has changed", () => {
175
- let stateAtOnEnter;
176
- const m = createMachine({
177
- initial: "idle",
178
- states: {
179
- idle: { on: { START: "active" } },
180
- active: {
181
- onEnter() {
182
- stateAtOnEnter = m.state();
183
- },
184
- },
185
- },
186
- });
187
- m.send("START");
188
- expect(stateAtOnEnter).toBe("active");
189
- });
190
- it("history not polluted by failed transitions (onExit throws)", () => {
191
- const m = createMachine({
192
- initial: "idle",
193
- states: {
194
- idle: {
195
- on: { START: "active" },
196
- onExit: () => { throw new Error("blocked"); },
197
- },
198
- active: {},
199
- },
200
- });
201
- expect(() => m.send("START")).toThrow("blocked");
202
- expect(m.history()).toHaveLength(0);
203
- expect(m.state()).toBe("idle");
204
- });
205
- // ── Guards ──────────────────────────────────────────────────────────────────
206
- it("guard returning true allows the transition", () => {
207
- const m = createMachine({
208
- initial: "a",
209
- states: {
210
- a: { on: { NEXT: { target: "b", guard: () => true } } },
211
- b: {},
212
- },
213
- });
214
- expect(m.send("NEXT")).toBe(true);
215
- expect(m.state()).toBe("b");
216
- });
217
- it("guard returning false blocks the transition — send returns false", () => {
218
- const m = createMachine({
219
- initial: "a",
220
- states: {
221
- a: { on: { NEXT: { target: "b", guard: () => false } } },
222
- b: {},
223
- },
224
- });
225
- expect(m.send("NEXT")).toBe(false);
226
- expect(m.state()).toBe("a");
227
- });
228
- it("guard returning false — history not updated", () => {
229
- const m = createMachine({
230
- initial: "a",
231
- states: {
232
- a: { on: { NEXT: { target: "b", guard: () => false } } },
233
- b: {},
234
- },
235
- });
236
- m.send("NEXT");
237
- expect(m.history()).toHaveLength(0);
238
- });
239
- it("guard returning false — onExit is NOT called", () => {
240
- const onExit = vi.fn();
241
- const m = createMachine({
242
- initial: "a",
243
- states: {
244
- a: { on: { NEXT: { target: "b", guard: () => false } }, onExit },
245
- b: {},
246
- },
247
- });
248
- m.send("NEXT");
249
- expect(onExit).not.toHaveBeenCalled();
250
- });
251
- it("can() returns false when guard returns false", () => {
252
- const m = createMachine({
253
- initial: "a",
254
- states: {
255
- a: { on: { NEXT: { target: "b", guard: () => false } } },
256
- b: {},
257
- },
258
- });
259
- expect(m.can("NEXT")).toBe(false);
260
- });
261
- it("can() returns true when guard returns true", () => {
262
- const m = createMachine({
263
- initial: "a",
264
- states: {
265
- a: { on: { NEXT: { target: "b", guard: () => true } } },
266
- b: {},
267
- },
268
- });
269
- expect(m.can("NEXT")).toBe(true);
270
- });
271
- it("guard is evaluated at transition time, not at definition time", () => {
272
- let allow = false;
273
- const m = createMachine({
274
- initial: "a",
275
- states: {
276
- a: { on: { NEXT: { target: "b", guard: () => allow } } },
277
- b: {},
278
- },
279
- });
280
- expect(m.send("NEXT")).toBe(false);
281
- allow = true;
282
- expect(m.send("NEXT")).toBe(true);
283
- expect(m.state()).toBe("b");
284
- });
285
- it("transition without guard behaves as before (plain string target)", () => {
286
- const m = createMachine({
287
- initial: "a",
288
- states: {
289
- a: { on: { NEXT: "b" } },
290
- b: {},
291
- },
292
- });
293
- expect(m.can("NEXT")).toBe(true);
294
- expect(m.send("NEXT")).toBe(true);
295
- expect(m.state()).toBe("b");
296
- });
297
- // ── Per-transition action ───────────────────────────────────────────────────
298
- it("action is called on successful transition", () => {
299
- const action = vi.fn();
300
- const m = createMachine({
301
- initial: "a",
302
- states: {
303
- a: { on: { NEXT: { target: "b", action } } },
304
- b: {},
305
- },
306
- });
307
- m.send("NEXT");
308
- expect(action).toHaveBeenCalledOnce();
309
- });
310
- it("action is NOT called when guard blocks the transition", () => {
311
- const action = vi.fn();
312
- const m = createMachine({
313
- initial: "a",
314
- states: {
315
- a: { on: { NEXT: { target: "b", guard: () => false, action } } },
316
- b: {},
317
- },
318
- });
319
- m.send("NEXT");
320
- expect(action).not.toHaveBeenCalled();
321
- });
322
- it("action runs after onTransition and before onEnter", () => {
323
- const order = [];
324
- const m = createMachine({
325
- initial: "a",
326
- states: {
327
- a: { on: { NEXT: { target: "b", action: () => { order.push("action"); } } } },
328
- b: { onEnter: () => { order.push("onEnter"); } },
329
- },
330
- onTransition: () => { order.push("onTransition"); },
331
- });
332
- m.send("NEXT");
333
- expect(order).toEqual(["onTransition", "action", "onEnter"]);
334
- });
335
- // ── onEnter / onExit context ────────────────────────────────────────────────
336
- it("onEnter receives event and from-state in context", () => {
337
- let ctx;
338
- const m = createMachine({
339
- initial: "a",
340
- states: {
341
- a: { on: { NEXT: "b" } },
342
- b: { onEnter: (c) => { ctx = c; } },
343
- },
344
- });
345
- m.send("NEXT");
346
- expect(ctx).toEqual({ event: "NEXT", from: "a" });
347
- });
348
- it("onExit receives event and to-state in context", () => {
349
- let ctx;
350
- const m = createMachine({
351
- initial: "a",
352
- states: {
353
- a: { on: { NEXT: "b" }, onExit: (c) => { ctx = c; } },
354
- b: {},
355
- },
356
- });
357
- m.send("NEXT");
358
- expect(ctx).toEqual({ event: "NEXT", to: "b" });
359
- });
360
- it("onEnter and onExit called without context on reset()", () => {
361
- const onEnter = vi.fn();
362
- const onExit = vi.fn();
363
- const m = createMachine({
364
- initial: "idle",
365
- states: {
366
- idle: { on: { START: "active" }, onEnter },
367
- active: { onExit },
368
- },
369
- });
370
- m.send("START");
371
- m.reset();
372
- expect(onExit).toHaveBeenCalledWith(undefined);
373
- expect(onEnter).toHaveBeenCalledWith(undefined);
374
- });
375
- it("zero-argument onEnter/onExit still work with context (backward compat)", () => {
376
- const onEnter = vi.fn();
377
- const onExit = vi.fn();
378
- const m = createMachine({
379
- initial: "a",
380
- states: {
381
- a: { on: { NEXT: "b" }, onExit },
382
- b: { onEnter },
383
- },
384
- });
385
- m.send("NEXT");
386
- expect(onExit).toHaveBeenCalledOnce();
387
- expect(onEnter).toHaveBeenCalledOnce();
388
- });
389
- });
390
- describe("createMachine historyLimit", () => {
391
- function limited(historyLimit) {
392
- return createMachine({
393
- initial: "red",
394
- historyLimit,
395
- states: {
396
- red: { on: { GO: "green" } },
397
- green: { on: { SLOW: "yellow" } },
398
- yellow: { on: { STOP: "red" } },
399
- },
400
- });
401
- }
402
- function cycle(m, times) {
403
- for (let i = 0; i < times; i++) {
404
- m.send("GO");
405
- m.send("SLOW");
406
- m.send("STOP");
407
- }
408
- }
409
- it("keeps the last 100 entries by default", () => {
410
- const m = limited();
411
- cycle(m, 40);
412
- expect(m.history()).toHaveLength(100);
413
- expect(m.history().at(-1)).toEqual({ from: "yellow", event: "STOP", to: "red" });
414
- });
415
- it("Infinity keeps every entry", () => {
416
- const m = limited(Infinity);
417
- cycle(m, 40);
418
- expect(m.history()).toHaveLength(120);
419
- });
420
- it("keeps only the most recent entries, in order", () => {
421
- const m = limited(2);
422
- m.send("GO");
423
- m.send("SLOW");
424
- m.send("STOP");
425
- expect(m.history()).toEqual([
426
- { from: "green", event: "SLOW", to: "yellow" },
427
- { from: "yellow", event: "STOP", to: "red" },
428
- ]);
429
- });
430
- it("does not drop anything while below the limit", () => {
431
- const m = limited(5);
432
- m.send("GO");
433
- m.send("SLOW");
434
- expect(m.history()).toHaveLength(2);
435
- });
436
- it("a limit of 1 keeps just the last transition", () => {
437
- const m = limited(1);
438
- cycle(m, 2);
439
- expect(m.history()).toEqual([{ from: "yellow", event: "STOP", to: "red" }]);
440
- });
441
- it("a limit of 0 disables the history without affecting transitions", () => {
442
- const m = limited(0);
443
- cycle(m, 2);
444
- expect(m.history()).toEqual([]);
445
- expect(m.state()).toBe("red");
446
- });
447
- });
448
- //# sourceMappingURL=machine.test.js.map