@praxisjs/fsm 2.2.0 → 2.2.1
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.
- package/CHANGELOG.md +32 -0
- package/dist/decorators.d.ts.map +1 -1
- package/dist/decorators.js +25 -20
- package/dist/decorators.js.map +1 -1
- package/dist/machine.d.ts.map +1 -1
- package/dist/machine.js +31 -36
- package/dist/machine.js.map +1 -1
- package/package.json +15 -6
- package/src/decorators.ts +26 -21
- package/src/machine.ts +40 -42
- package/dist/__tests__/decorators.test.d.ts +0 -2
- package/dist/__tests__/decorators.test.d.ts.map +0 -1
- package/dist/__tests__/decorators.test.js +0 -182
- package/dist/__tests__/decorators.test.js.map +0 -1
- package/dist/__tests__/machine.test.d.ts +0 -2
- package/dist/__tests__/machine.test.d.ts.map +0 -1
- package/dist/__tests__/machine.test.js +0 -448
- package/dist/__tests__/machine.test.js.map +0 -1
- package/src/__tests__/decorators.test.ts +0 -219
- package/src/__tests__/machine.test.ts +0 -499
- package/tsconfig.json +0 -8
|
@@ -1,499 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from "vitest";
|
|
2
|
-
|
|
3
|
-
import { createMachine } from "../machine";
|
|
4
|
-
|
|
5
|
-
type TrafficState = "red" | "green" | "yellow";
|
|
6
|
-
type TrafficEvent = "GO" | "SLOW" | "STOP";
|
|
7
|
-
|
|
8
|
-
function trafficLight() {
|
|
9
|
-
return createMachine<TrafficState, TrafficEvent>({
|
|
10
|
-
initial: "red",
|
|
11
|
-
states: {
|
|
12
|
-
red: { on: { GO: "green" } },
|
|
13
|
-
green: { on: { SLOW: "yellow" } },
|
|
14
|
-
yellow: { on: { STOP: "red" } },
|
|
15
|
-
},
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
describe("createMachine", () => {
|
|
20
|
-
it("initializes in the given state", () => {
|
|
21
|
-
const m = trafficLight();
|
|
22
|
-
expect(m.state()).toBe("red");
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it("transitions on a valid event", () => {
|
|
26
|
-
const m = trafficLight();
|
|
27
|
-
m.send("GO");
|
|
28
|
-
expect(m.state()).toBe("green");
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it("returns true from send() on valid transition", () => {
|
|
32
|
-
const m = trafficLight();
|
|
33
|
-
expect(m.send("GO")).toBe(true);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it("returns false from send() on invalid event", () => {
|
|
37
|
-
const m = trafficLight();
|
|
38
|
-
expect(m.send("SLOW")).toBe(false); // SLOW is not valid in red
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("does not change state on invalid event", () => {
|
|
42
|
-
const m = trafficLight();
|
|
43
|
-
m.send("STOP"); // invalid in red
|
|
44
|
-
expect(m.state()).toBe("red");
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it("can() returns true for valid events", () => {
|
|
48
|
-
const m = trafficLight();
|
|
49
|
-
expect(m.can("GO")).toBe(true);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it("can() returns false for invalid events", () => {
|
|
53
|
-
const m = trafficLight();
|
|
54
|
-
expect(m.can("SLOW")).toBe(false);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("is() checks current state", () => {
|
|
58
|
-
const m = trafficLight();
|
|
59
|
-
expect(m.is("red")).toBe(true);
|
|
60
|
-
expect(m.is("green")).toBe(false);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("tracks history of transitions", () => {
|
|
64
|
-
const m = trafficLight();
|
|
65
|
-
m.send("GO");
|
|
66
|
-
m.send("SLOW");
|
|
67
|
-
const h = m.history();
|
|
68
|
-
expect(h).toHaveLength(2);
|
|
69
|
-
expect(h[0]).toEqual({ from: "red", event: "GO", to: "green" });
|
|
70
|
-
expect(h[1]).toEqual({ from: "green", event: "SLOW", to: "yellow" });
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it("resets to initial state and clears history", () => {
|
|
74
|
-
const m = trafficLight();
|
|
75
|
-
m.send("GO");
|
|
76
|
-
m.send("SLOW");
|
|
77
|
-
m.reset();
|
|
78
|
-
expect(m.state()).toBe("red");
|
|
79
|
-
expect(m.history()).toHaveLength(0);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it("calls onEnter on entering a state", () => {
|
|
83
|
-
const onEnter = vi.fn();
|
|
84
|
-
const m = createMachine<"idle" | "active", "START">({
|
|
85
|
-
initial: "idle",
|
|
86
|
-
states: {
|
|
87
|
-
idle: { on: { START: "active" } },
|
|
88
|
-
active: { onEnter },
|
|
89
|
-
},
|
|
90
|
-
});
|
|
91
|
-
m.send("START");
|
|
92
|
-
expect(onEnter).toHaveBeenCalledOnce();
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("calls onExit on leaving a state", () => {
|
|
96
|
-
const onExit = vi.fn();
|
|
97
|
-
const m = createMachine<"idle" | "active", "START">({
|
|
98
|
-
initial: "idle",
|
|
99
|
-
states: {
|
|
100
|
-
idle: { on: { START: "active" }, onExit },
|
|
101
|
-
active: {},
|
|
102
|
-
},
|
|
103
|
-
});
|
|
104
|
-
m.send("START");
|
|
105
|
-
expect(onExit).toHaveBeenCalledOnce();
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it("onExit throwing prevents the state change and keeps history clean", () => {
|
|
109
|
-
const m = createMachine<"idle" | "active", "START">({
|
|
110
|
-
initial: "idle",
|
|
111
|
-
states: {
|
|
112
|
-
idle: {
|
|
113
|
-
on: { START: "active" },
|
|
114
|
-
onExit: () => { throw new Error("exit failed"); },
|
|
115
|
-
},
|
|
116
|
-
active: {},
|
|
117
|
-
},
|
|
118
|
-
});
|
|
119
|
-
expect(() => m.send("START")).toThrow("exit failed");
|
|
120
|
-
expect(m.state()).toBe("idle");
|
|
121
|
-
expect(m.history()).toHaveLength(0);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it("onEnter throwing after state change leaves machine in the new state", () => {
|
|
125
|
-
const m = createMachine<"idle" | "active", "START">({
|
|
126
|
-
initial: "idle",
|
|
127
|
-
states: {
|
|
128
|
-
idle: { on: { START: "active" } },
|
|
129
|
-
active: { onEnter: () => { throw new Error("enter failed"); } },
|
|
130
|
-
},
|
|
131
|
-
});
|
|
132
|
-
expect(() => m.send("START")).toThrow("enter failed");
|
|
133
|
-
// State was already committed before onEnter ran
|
|
134
|
-
expect(m.state()).toBe("active");
|
|
135
|
-
expect(m.history()).toHaveLength(1);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it("reset() calls onEnter of the initial state", () => {
|
|
139
|
-
const onEnter = vi.fn();
|
|
140
|
-
const m = createMachine<"idle" | "active", "START">({
|
|
141
|
-
initial: "idle",
|
|
142
|
-
states: {
|
|
143
|
-
idle: { on: { START: "active" }, onEnter },
|
|
144
|
-
active: {},
|
|
145
|
-
},
|
|
146
|
-
});
|
|
147
|
-
m.send("START");
|
|
148
|
-
m.reset();
|
|
149
|
-
expect(onEnter).toHaveBeenCalledOnce();
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it("calls onTransition callback", () => {
|
|
153
|
-
const onTransition = vi.fn();
|
|
154
|
-
const m = createMachine<"a" | "b", "NEXT">({
|
|
155
|
-
initial: "a",
|
|
156
|
-
states: {
|
|
157
|
-
a: { on: { NEXT: "b" } },
|
|
158
|
-
b: {},
|
|
159
|
-
},
|
|
160
|
-
onTransition,
|
|
161
|
-
});
|
|
162
|
-
m.send("NEXT");
|
|
163
|
-
expect(onTransition).toHaveBeenCalledWith("a", "NEXT", "b");
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it("state with on:{} — can() returns false for all events", () => {
|
|
167
|
-
const m = createMachine<"idle", "START" | "STOP">({
|
|
168
|
-
initial: "idle",
|
|
169
|
-
states: {
|
|
170
|
-
idle: { on: {} },
|
|
171
|
-
},
|
|
172
|
-
});
|
|
173
|
-
expect(m.can("START")).toBe(false);
|
|
174
|
-
expect(m.can("STOP")).toBe(false);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it("circular transition A→B→A — history contains correct entries", () => {
|
|
178
|
-
const m = createMachine<"a" | "b", "NEXT">({
|
|
179
|
-
initial: "a",
|
|
180
|
-
states: {
|
|
181
|
-
a: { on: { NEXT: "b" } },
|
|
182
|
-
b: { on: { NEXT: "a" } },
|
|
183
|
-
},
|
|
184
|
-
});
|
|
185
|
-
m.send("NEXT"); // a→b
|
|
186
|
-
m.send("NEXT"); // b→a
|
|
187
|
-
const h = m.history();
|
|
188
|
-
expect(h).toHaveLength(2);
|
|
189
|
-
expect(h[0]).toEqual({ from: "a", event: "NEXT", to: "b" });
|
|
190
|
-
expect(h[1]).toEqual({ from: "b", event: "NEXT", to: "a" });
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it("send() with undefined event — does not crash, returns false", () => {
|
|
194
|
-
const m = trafficLight();
|
|
195
|
-
// Cast to bypass TypeScript — runtime guard should handle it gracefully
|
|
196
|
-
expect(m.send(undefined as unknown as TrafficEvent)).toBe(false);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
it("onEnter callback is called AFTER the state has changed", () => {
|
|
200
|
-
let stateAtOnEnter: string | undefined;
|
|
201
|
-
const m = createMachine<"idle" | "active", "START">({
|
|
202
|
-
initial: "idle",
|
|
203
|
-
states: {
|
|
204
|
-
idle: { on: { START: "active" } },
|
|
205
|
-
active: {
|
|
206
|
-
onEnter() {
|
|
207
|
-
stateAtOnEnter = m.state();
|
|
208
|
-
},
|
|
209
|
-
},
|
|
210
|
-
},
|
|
211
|
-
});
|
|
212
|
-
m.send("START");
|
|
213
|
-
expect(stateAtOnEnter).toBe("active");
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
it("history not polluted by failed transitions (onExit throws)", () => {
|
|
217
|
-
const m = createMachine<"idle" | "active", "START">({
|
|
218
|
-
initial: "idle",
|
|
219
|
-
states: {
|
|
220
|
-
idle: {
|
|
221
|
-
on: { START: "active" },
|
|
222
|
-
onExit: () => { throw new Error("blocked"); },
|
|
223
|
-
},
|
|
224
|
-
active: {},
|
|
225
|
-
},
|
|
226
|
-
});
|
|
227
|
-
expect(() => m.send("START")).toThrow("blocked");
|
|
228
|
-
expect(m.history()).toHaveLength(0);
|
|
229
|
-
expect(m.state()).toBe("idle");
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
// ── Guards ──────────────────────────────────────────────────────────────────
|
|
233
|
-
|
|
234
|
-
it("guard returning true allows the transition", () => {
|
|
235
|
-
const m = createMachine<"a" | "b", "NEXT">({
|
|
236
|
-
initial: "a",
|
|
237
|
-
states: {
|
|
238
|
-
a: { on: { NEXT: { target: "b", guard: () => true } } },
|
|
239
|
-
b: {},
|
|
240
|
-
},
|
|
241
|
-
});
|
|
242
|
-
expect(m.send("NEXT")).toBe(true);
|
|
243
|
-
expect(m.state()).toBe("b");
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
it("guard returning false blocks the transition — send returns false", () => {
|
|
247
|
-
const m = createMachine<"a" | "b", "NEXT">({
|
|
248
|
-
initial: "a",
|
|
249
|
-
states: {
|
|
250
|
-
a: { on: { NEXT: { target: "b", guard: () => false } } },
|
|
251
|
-
b: {},
|
|
252
|
-
},
|
|
253
|
-
});
|
|
254
|
-
expect(m.send("NEXT")).toBe(false);
|
|
255
|
-
expect(m.state()).toBe("a");
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
it("guard returning false — history not updated", () => {
|
|
259
|
-
const m = createMachine<"a" | "b", "NEXT">({
|
|
260
|
-
initial: "a",
|
|
261
|
-
states: {
|
|
262
|
-
a: { on: { NEXT: { target: "b", guard: () => false } } },
|
|
263
|
-
b: {},
|
|
264
|
-
},
|
|
265
|
-
});
|
|
266
|
-
m.send("NEXT");
|
|
267
|
-
expect(m.history()).toHaveLength(0);
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
it("guard returning false — onExit is NOT called", () => {
|
|
271
|
-
const onExit = vi.fn();
|
|
272
|
-
const m = createMachine<"a" | "b", "NEXT">({
|
|
273
|
-
initial: "a",
|
|
274
|
-
states: {
|
|
275
|
-
a: { on: { NEXT: { target: "b", guard: () => false } }, onExit },
|
|
276
|
-
b: {},
|
|
277
|
-
},
|
|
278
|
-
});
|
|
279
|
-
m.send("NEXT");
|
|
280
|
-
expect(onExit).not.toHaveBeenCalled();
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
it("can() returns false when guard returns false", () => {
|
|
284
|
-
const m = createMachine<"a" | "b", "NEXT">({
|
|
285
|
-
initial: "a",
|
|
286
|
-
states: {
|
|
287
|
-
a: { on: { NEXT: { target: "b", guard: () => false } } },
|
|
288
|
-
b: {},
|
|
289
|
-
},
|
|
290
|
-
});
|
|
291
|
-
expect(m.can("NEXT")).toBe(false);
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
it("can() returns true when guard returns true", () => {
|
|
295
|
-
const m = createMachine<"a" | "b", "NEXT">({
|
|
296
|
-
initial: "a",
|
|
297
|
-
states: {
|
|
298
|
-
a: { on: { NEXT: { target: "b", guard: () => true } } },
|
|
299
|
-
b: {},
|
|
300
|
-
},
|
|
301
|
-
});
|
|
302
|
-
expect(m.can("NEXT")).toBe(true);
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
it("guard is evaluated at transition time, not at definition time", () => {
|
|
306
|
-
let allow = false;
|
|
307
|
-
const m = createMachine<"a" | "b", "NEXT">({
|
|
308
|
-
initial: "a",
|
|
309
|
-
states: {
|
|
310
|
-
a: { on: { NEXT: { target: "b", guard: () => allow } } },
|
|
311
|
-
b: {},
|
|
312
|
-
},
|
|
313
|
-
});
|
|
314
|
-
expect(m.send("NEXT")).toBe(false);
|
|
315
|
-
allow = true;
|
|
316
|
-
expect(m.send("NEXT")).toBe(true);
|
|
317
|
-
expect(m.state()).toBe("b");
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
it("transition without guard behaves as before (plain string target)", () => {
|
|
321
|
-
const m = createMachine<"a" | "b", "NEXT">({
|
|
322
|
-
initial: "a",
|
|
323
|
-
states: {
|
|
324
|
-
a: { on: { NEXT: "b" } },
|
|
325
|
-
b: {},
|
|
326
|
-
},
|
|
327
|
-
});
|
|
328
|
-
expect(m.can("NEXT")).toBe(true);
|
|
329
|
-
expect(m.send("NEXT")).toBe(true);
|
|
330
|
-
expect(m.state()).toBe("b");
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
// ── Per-transition action ───────────────────────────────────────────────────
|
|
334
|
-
|
|
335
|
-
it("action is called on successful transition", () => {
|
|
336
|
-
const action = vi.fn();
|
|
337
|
-
const m = createMachine<"a" | "b", "NEXT">({
|
|
338
|
-
initial: "a",
|
|
339
|
-
states: {
|
|
340
|
-
a: { on: { NEXT: { target: "b", action } } },
|
|
341
|
-
b: {},
|
|
342
|
-
},
|
|
343
|
-
});
|
|
344
|
-
m.send("NEXT");
|
|
345
|
-
expect(action).toHaveBeenCalledOnce();
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
it("action is NOT called when guard blocks the transition", () => {
|
|
349
|
-
const action = vi.fn();
|
|
350
|
-
const m = createMachine<"a" | "b", "NEXT">({
|
|
351
|
-
initial: "a",
|
|
352
|
-
states: {
|
|
353
|
-
a: { on: { NEXT: { target: "b", guard: () => false, action } } },
|
|
354
|
-
b: {},
|
|
355
|
-
},
|
|
356
|
-
});
|
|
357
|
-
m.send("NEXT");
|
|
358
|
-
expect(action).not.toHaveBeenCalled();
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
it("action runs after onTransition and before onEnter", () => {
|
|
362
|
-
const order: string[] = [];
|
|
363
|
-
const m = createMachine<"a" | "b", "NEXT">({
|
|
364
|
-
initial: "a",
|
|
365
|
-
states: {
|
|
366
|
-
a: { on: { NEXT: { target: "b", action: () => { order.push("action"); } } } },
|
|
367
|
-
b: { onEnter: () => { order.push("onEnter"); } },
|
|
368
|
-
},
|
|
369
|
-
onTransition: () => { order.push("onTransition"); },
|
|
370
|
-
});
|
|
371
|
-
m.send("NEXT");
|
|
372
|
-
expect(order).toEqual(["onTransition", "action", "onEnter"]);
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
// ── onEnter / onExit context ────────────────────────────────────────────────
|
|
376
|
-
|
|
377
|
-
it("onEnter receives event and from-state in context", () => {
|
|
378
|
-
let ctx: { event: string; from: string } | undefined;
|
|
379
|
-
const m = createMachine<"a" | "b", "NEXT">({
|
|
380
|
-
initial: "a",
|
|
381
|
-
states: {
|
|
382
|
-
a: { on: { NEXT: "b" } },
|
|
383
|
-
b: { onEnter: (c) => { ctx = c; } },
|
|
384
|
-
},
|
|
385
|
-
});
|
|
386
|
-
m.send("NEXT");
|
|
387
|
-
expect(ctx).toEqual({ event: "NEXT", from: "a" });
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
it("onExit receives event and to-state in context", () => {
|
|
391
|
-
let ctx: { event: string; to: string } | undefined;
|
|
392
|
-
const m = createMachine<"a" | "b", "NEXT">({
|
|
393
|
-
initial: "a",
|
|
394
|
-
states: {
|
|
395
|
-
a: { on: { NEXT: "b" }, onExit: (c) => { ctx = c; } },
|
|
396
|
-
b: {},
|
|
397
|
-
},
|
|
398
|
-
});
|
|
399
|
-
m.send("NEXT");
|
|
400
|
-
expect(ctx).toEqual({ event: "NEXT", to: "b" });
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
it("onEnter and onExit called without context on reset()", () => {
|
|
404
|
-
const onEnter = vi.fn();
|
|
405
|
-
const onExit = vi.fn();
|
|
406
|
-
const m = createMachine<"idle" | "active", "START">({
|
|
407
|
-
initial: "idle",
|
|
408
|
-
states: {
|
|
409
|
-
idle: { on: { START: "active" }, onEnter },
|
|
410
|
-
active: { onExit },
|
|
411
|
-
},
|
|
412
|
-
});
|
|
413
|
-
m.send("START");
|
|
414
|
-
m.reset();
|
|
415
|
-
expect(onExit).toHaveBeenCalledWith(undefined);
|
|
416
|
-
expect(onEnter).toHaveBeenCalledWith(undefined);
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
it("zero-argument onEnter/onExit still work with context (backward compat)", () => {
|
|
420
|
-
const onEnter = vi.fn();
|
|
421
|
-
const onExit = vi.fn();
|
|
422
|
-
const m = createMachine<"a" | "b", "NEXT">({
|
|
423
|
-
initial: "a",
|
|
424
|
-
states: {
|
|
425
|
-
a: { on: { NEXT: "b" }, onExit },
|
|
426
|
-
b: { onEnter },
|
|
427
|
-
},
|
|
428
|
-
});
|
|
429
|
-
m.send("NEXT");
|
|
430
|
-
expect(onExit).toHaveBeenCalledOnce();
|
|
431
|
-
expect(onEnter).toHaveBeenCalledOnce();
|
|
432
|
-
});
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
describe("createMachine historyLimit", () => {
|
|
436
|
-
function limited(historyLimit?: number) {
|
|
437
|
-
return createMachine<TrafficState, TrafficEvent>({
|
|
438
|
-
initial: "red",
|
|
439
|
-
historyLimit,
|
|
440
|
-
states: {
|
|
441
|
-
red: { on: { GO: "green" } },
|
|
442
|
-
green: { on: { SLOW: "yellow" } },
|
|
443
|
-
yellow: { on: { STOP: "red" } },
|
|
444
|
-
},
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
function cycle(m: ReturnType<typeof limited>, times: number) {
|
|
449
|
-
for (let i = 0; i < times; i++) {
|
|
450
|
-
m.send("GO");
|
|
451
|
-
m.send("SLOW");
|
|
452
|
-
m.send("STOP");
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
it("keeps the last 100 entries by default", () => {
|
|
457
|
-
const m = limited();
|
|
458
|
-
cycle(m, 40);
|
|
459
|
-
expect(m.history()).toHaveLength(100);
|
|
460
|
-
expect(m.history().at(-1)).toEqual({ from: "yellow", event: "STOP", to: "red" });
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
it("Infinity keeps every entry", () => {
|
|
464
|
-
const m = limited(Infinity);
|
|
465
|
-
cycle(m, 40);
|
|
466
|
-
expect(m.history()).toHaveLength(120);
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
it("keeps only the most recent entries, in order", () => {
|
|
470
|
-
const m = limited(2);
|
|
471
|
-
m.send("GO");
|
|
472
|
-
m.send("SLOW");
|
|
473
|
-
m.send("STOP");
|
|
474
|
-
expect(m.history()).toEqual([
|
|
475
|
-
{ from: "green", event: "SLOW", to: "yellow" },
|
|
476
|
-
{ from: "yellow", event: "STOP", to: "red" },
|
|
477
|
-
]);
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
it("does not drop anything while below the limit", () => {
|
|
481
|
-
const m = limited(5);
|
|
482
|
-
m.send("GO");
|
|
483
|
-
m.send("SLOW");
|
|
484
|
-
expect(m.history()).toHaveLength(2);
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
it("a limit of 1 keeps just the last transition", () => {
|
|
488
|
-
const m = limited(1);
|
|
489
|
-
cycle(m, 2);
|
|
490
|
-
expect(m.history()).toEqual([{ from: "yellow", event: "STOP", to: "red" }]);
|
|
491
|
-
});
|
|
492
|
-
|
|
493
|
-
it("a limit of 0 disables the history without affecting transitions", () => {
|
|
494
|
-
const m = limited(0);
|
|
495
|
-
cycle(m, 2);
|
|
496
|
-
expect(m.history()).toEqual([]);
|
|
497
|
-
expect(m.state()).toBe("red");
|
|
498
|
-
});
|
|
499
|
-
});
|