@pyreon/machine 0.11.5 → 0.11.7

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,59 +1,59 @@
1
- import { computed, effect } from "@pyreon/reactivity"
2
- import { describe, expect, it, vi } from "vitest"
3
- import { createMachine } from "../index"
1
+ import { computed, effect } from '@pyreon/reactivity'
2
+ import { describe, expect, it, vi } from 'vitest'
3
+ import { createMachine } from '../index'
4
4
 
5
- describe("createMachine — nextEvents()", () => {
6
- it("returns valid events from current state", () => {
5
+ describe('createMachine — nextEvents()', () => {
6
+ it('returns valid events from current state', () => {
7
7
  const m = createMachine({
8
- initial: "idle",
8
+ initial: 'idle',
9
9
  states: {
10
- idle: { on: { FETCH: "loading", RESET: "idle" } },
11
- loading: { on: { SUCCESS: "done", ERROR: "error" } },
10
+ idle: { on: { FETCH: 'loading', RESET: 'idle' } },
11
+ loading: { on: { SUCCESS: 'done', ERROR: 'error' } },
12
12
  done: {},
13
13
  error: {},
14
14
  },
15
15
  })
16
- expect(m.nextEvents()).toEqual(expect.arrayContaining(["FETCH", "RESET"]))
16
+ expect(m.nextEvents()).toEqual(expect.arrayContaining(['FETCH', 'RESET']))
17
17
  expect(m.nextEvents()).toHaveLength(2)
18
18
  })
19
19
 
20
- it("returns empty array for final states (no transitions)", () => {
20
+ it('returns empty array for final states (no transitions)', () => {
21
21
  const m = createMachine({
22
- initial: "idle",
22
+ initial: 'idle',
23
23
  states: {
24
- idle: { on: { DONE: "finished" } },
24
+ idle: { on: { DONE: 'finished' } },
25
25
  finished: {},
26
26
  },
27
27
  })
28
- m.send("DONE")
28
+ m.send('DONE')
29
29
  expect(m.nextEvents()).toEqual([])
30
30
  })
31
31
 
32
- it("updates after transition", () => {
32
+ it('updates after transition', () => {
33
33
  const m = createMachine({
34
- initial: "idle",
34
+ initial: 'idle',
35
35
  states: {
36
- idle: { on: { START: "running" } },
37
- running: { on: { STOP: "idle", PAUSE: "paused" } },
38
- paused: { on: { RESUME: "running" } },
36
+ idle: { on: { START: 'running' } },
37
+ running: { on: { STOP: 'idle', PAUSE: 'paused' } },
38
+ paused: { on: { RESUME: 'running' } },
39
39
  },
40
40
  })
41
41
 
42
- expect(m.nextEvents()).toEqual(["START"])
42
+ expect(m.nextEvents()).toEqual(['START'])
43
43
 
44
- m.send("START")
45
- expect(m.nextEvents()).toEqual(expect.arrayContaining(["STOP", "PAUSE"]))
44
+ m.send('START')
45
+ expect(m.nextEvents()).toEqual(expect.arrayContaining(['STOP', 'PAUSE']))
46
46
 
47
- m.send("PAUSE")
48
- expect(m.nextEvents()).toEqual(["RESUME"])
47
+ m.send('PAUSE')
48
+ expect(m.nextEvents()).toEqual(['RESUME'])
49
49
  })
50
50
 
51
- it("is reactive in effects", () => {
51
+ it('is reactive in effects', () => {
52
52
  const m = createMachine({
53
- initial: "idle",
53
+ initial: 'idle',
54
54
  states: {
55
- idle: { on: { GO: "active" } },
56
- active: { on: { STOP: "idle", PAUSE: "paused" } },
55
+ idle: { on: { GO: 'active' } },
56
+ active: { on: { STOP: 'idle', PAUSE: 'paused' } },
57
57
  paused: {},
58
58
  },
59
59
  })
@@ -63,20 +63,20 @@ describe("createMachine — nextEvents()", () => {
63
63
  results.push(m.nextEvents())
64
64
  })
65
65
 
66
- m.send("GO")
66
+ m.send('GO')
67
67
  expect(results).toHaveLength(2)
68
- expect(results[0]).toEqual(["GO"])
69
- expect(results[1]).toEqual(expect.arrayContaining(["STOP", "PAUSE"]))
68
+ expect(results[0]).toEqual(['GO'])
69
+ expect(results[1]).toEqual(expect.arrayContaining(['STOP', 'PAUSE']))
70
70
  })
71
71
 
72
- it("includes guarded event names", () => {
72
+ it('includes guarded event names', () => {
73
73
  const m = createMachine({
74
- initial: "editing",
74
+ initial: 'editing',
75
75
  states: {
76
76
  editing: {
77
77
  on: {
78
- SUBMIT: { target: "submitting", guard: () => false },
79
- CANCEL: "cancelled",
78
+ SUBMIT: { target: 'submitting', guard: () => false },
79
+ CANCEL: 'cancelled',
80
80
  },
81
81
  },
82
82
  submitting: {},
@@ -84,151 +84,151 @@ describe("createMachine — nextEvents()", () => {
84
84
  },
85
85
  })
86
86
  // nextEvents returns all event keys, even guarded ones
87
- expect(m.nextEvents()).toEqual(expect.arrayContaining(["SUBMIT", "CANCEL"]))
87
+ expect(m.nextEvents()).toEqual(expect.arrayContaining(['SUBMIT', 'CANCEL']))
88
88
  })
89
89
  })
90
90
 
91
- describe("createMachine — can()", () => {
92
- it("returns true for valid events from current state", () => {
91
+ describe('createMachine — can()', () => {
92
+ it('returns true for valid events from current state', () => {
93
93
  const m = createMachine({
94
- initial: "idle",
94
+ initial: 'idle',
95
95
  states: {
96
- idle: { on: { START: "running" } },
97
- running: { on: { STOP: "idle" } },
96
+ idle: { on: { START: 'running' } },
97
+ running: { on: { STOP: 'idle' } },
98
98
  },
99
99
  })
100
- expect(m.can("START")).toBe(true)
100
+ expect(m.can('START')).toBe(true)
101
101
  })
102
102
 
103
- it("returns false for events not in current state", () => {
103
+ it('returns false for events not in current state', () => {
104
104
  const m = createMachine({
105
- initial: "idle",
105
+ initial: 'idle',
106
106
  states: {
107
- idle: { on: { START: "running" } },
108
- running: { on: { STOP: "idle" } },
107
+ idle: { on: { START: 'running' } },
108
+ running: { on: { STOP: 'idle' } },
109
109
  },
110
110
  })
111
- expect(m.can("STOP")).toBe(false)
111
+ expect(m.can('STOP')).toBe(false)
112
112
  })
113
113
 
114
- it("returns false for completely unknown events", () => {
114
+ it('returns false for completely unknown events', () => {
115
115
  const m = createMachine({
116
- initial: "idle",
116
+ initial: 'idle',
117
117
  states: {
118
- idle: { on: { START: "running" } },
118
+ idle: { on: { START: 'running' } },
119
119
  running: {},
120
120
  },
121
121
  })
122
- expect(m.can("NONEXISTENT" as any)).toBe(false)
122
+ expect(m.can('NONEXISTENT' as any)).toBe(false)
123
123
  })
124
124
 
125
- it("returns false when in a final state (no transitions)", () => {
125
+ it('returns false when in a final state (no transitions)', () => {
126
126
  const m = createMachine({
127
- initial: "idle",
127
+ initial: 'idle',
128
128
  states: {
129
- idle: { on: { DONE: "finished" } },
129
+ idle: { on: { DONE: 'finished' } },
130
130
  finished: {},
131
131
  },
132
132
  })
133
- m.send("DONE")
134
- expect(m.can("DONE")).toBe(false)
135
- expect(m.can("START" as any)).toBe(false)
133
+ m.send('DONE')
134
+ expect(m.can('DONE')).toBe(false)
135
+ expect(m.can('START' as any)).toBe(false)
136
136
  })
137
137
 
138
- it("returns true for guarded transitions (guard not evaluated by can())", () => {
138
+ it('returns true for guarded transitions (guard not evaluated by can())', () => {
139
139
  const m = createMachine({
140
- initial: "editing",
140
+ initial: 'editing',
141
141
  states: {
142
142
  editing: {
143
- on: { SUBMIT: { target: "submitting", guard: () => false } },
143
+ on: { SUBMIT: { target: 'submitting', guard: () => false } },
144
144
  },
145
145
  submitting: {},
146
146
  },
147
147
  })
148
148
  // can() checks existence only, not guard
149
- expect(m.can("SUBMIT")).toBe(true)
149
+ expect(m.can('SUBMIT')).toBe(true)
150
150
  })
151
151
 
152
- it("is reactive — updates when state changes", () => {
152
+ it('is reactive — updates when state changes', () => {
153
153
  const m = createMachine({
154
- initial: "idle",
154
+ initial: 'idle',
155
155
  states: {
156
- idle: { on: { START: "running" } },
157
- running: { on: { STOP: "idle" } },
156
+ idle: { on: { START: 'running' } },
157
+ running: { on: { STOP: 'idle' } },
158
158
  },
159
159
  })
160
160
  const canStop: boolean[] = []
161
161
 
162
162
  effect(() => {
163
- canStop.push(m.can("STOP"))
163
+ canStop.push(m.can('STOP'))
164
164
  })
165
165
 
166
166
  expect(canStop).toEqual([false])
167
167
 
168
- m.send("START")
168
+ m.send('START')
169
169
  expect(canStop).toEqual([false, true])
170
170
 
171
- m.send("STOP")
171
+ m.send('STOP')
172
172
  expect(canStop).toEqual([false, true, false])
173
173
  })
174
174
 
175
- it("works in computed", () => {
175
+ it('works in computed', () => {
176
176
  const m = createMachine({
177
- initial: "idle",
177
+ initial: 'idle',
178
178
  states: {
179
- idle: { on: { START: "running" } },
180
- running: { on: { STOP: "idle" } },
179
+ idle: { on: { START: 'running' } },
180
+ running: { on: { STOP: 'idle' } },
181
181
  },
182
182
  })
183
183
 
184
- const canStart = computed(() => m.can("START"))
184
+ const canStart = computed(() => m.can('START'))
185
185
  expect(canStart()).toBe(true)
186
186
 
187
- m.send("START")
187
+ m.send('START')
188
188
  expect(canStart()).toBe(false)
189
189
  })
190
190
  })
191
191
 
192
- describe("createMachine — reset()", () => {
193
- it("returns to initial state", () => {
192
+ describe('createMachine — reset()', () => {
193
+ it('returns to initial state', () => {
194
194
  const m = createMachine({
195
- initial: "idle",
195
+ initial: 'idle',
196
196
  states: {
197
- idle: { on: { START: "running" } },
198
- running: { on: { STOP: "idle" } },
197
+ idle: { on: { START: 'running' } },
198
+ running: { on: { STOP: 'idle' } },
199
199
  },
200
200
  })
201
- m.send("START")
202
- expect(m()).toBe("running")
201
+ m.send('START')
202
+ expect(m()).toBe('running')
203
203
 
204
204
  m.reset()
205
- expect(m()).toBe("idle")
205
+ expect(m()).toBe('idle')
206
206
  })
207
207
 
208
- it("reset from deeply nested state", () => {
208
+ it('reset from deeply nested state', () => {
209
209
  const m = createMachine({
210
- initial: "step1",
210
+ initial: 'step1',
211
211
  states: {
212
- step1: { on: { NEXT: "step2" } },
213
- step2: { on: { NEXT: "step3" } },
214
- step3: { on: { NEXT: "step4" } },
212
+ step1: { on: { NEXT: 'step2' } },
213
+ step2: { on: { NEXT: 'step3' } },
214
+ step3: { on: { NEXT: 'step4' } },
215
215
  step4: {},
216
216
  },
217
217
  })
218
- m.send("NEXT")
219
- m.send("NEXT")
220
- m.send("NEXT")
221
- expect(m()).toBe("step4")
218
+ m.send('NEXT')
219
+ m.send('NEXT')
220
+ m.send('NEXT')
221
+ expect(m()).toBe('step4')
222
222
 
223
223
  m.reset()
224
- expect(m()).toBe("step1")
224
+ expect(m()).toBe('step1')
225
225
  })
226
226
 
227
- it("reset is reactive", () => {
227
+ it('reset is reactive', () => {
228
228
  const m = createMachine({
229
- initial: "idle",
229
+ initial: 'idle',
230
230
  states: {
231
- idle: { on: { GO: "active" } },
231
+ idle: { on: { GO: 'active' } },
232
232
  active: {},
233
233
  },
234
234
  })
@@ -238,86 +238,86 @@ describe("createMachine — reset()", () => {
238
238
  states.push(m())
239
239
  })
240
240
 
241
- m.send("GO")
241
+ m.send('GO')
242
242
  m.reset()
243
243
 
244
- expect(states).toEqual(["idle", "active", "idle"])
244
+ expect(states).toEqual(['idle', 'active', 'idle'])
245
245
  })
246
246
 
247
- it("reset does not fire onEnter for initial state", () => {
247
+ it('reset does not fire onEnter for initial state', () => {
248
248
  const m = createMachine({
249
- initial: "idle",
249
+ initial: 'idle',
250
250
  states: {
251
- idle: { on: { GO: "active" } },
252
- active: { on: { BACK: "idle" } },
251
+ idle: { on: { GO: 'active' } },
252
+ active: { on: { BACK: 'idle' } },
253
253
  },
254
254
  })
255
255
  const fn = vi.fn()
256
- m.onEnter("idle", fn)
256
+ m.onEnter('idle', fn)
257
257
 
258
- m.send("GO")
258
+ m.send('GO')
259
259
  m.reset() // Sets state directly, does not go through send()
260
260
  // reset() uses current.set() directly, not send(), so no enter callback
261
261
  expect(fn).not.toHaveBeenCalled()
262
262
  })
263
263
 
264
- it("after reset, transitions work from initial state", () => {
264
+ it('after reset, transitions work from initial state', () => {
265
265
  const m = createMachine({
266
- initial: "idle",
266
+ initial: 'idle',
267
267
  states: {
268
- idle: { on: { GO: "active" } },
269
- active: { on: { DONE: "finished" } },
268
+ idle: { on: { GO: 'active' } },
269
+ active: { on: { DONE: 'finished' } },
270
270
  finished: {},
271
271
  },
272
272
  })
273
- m.send("GO")
274
- m.send("DONE")
275
- expect(m()).toBe("finished")
273
+ m.send('GO')
274
+ m.send('DONE')
275
+ expect(m()).toBe('finished')
276
276
 
277
277
  m.reset()
278
- expect(m()).toBe("idle")
278
+ expect(m()).toBe('idle')
279
279
 
280
- m.send("GO")
281
- expect(m()).toBe("active")
280
+ m.send('GO')
281
+ expect(m()).toBe('active')
282
282
  })
283
283
 
284
- it("reset from initial state is a no-op (same value)", () => {
284
+ it('reset from initial state is a no-op (same value)', () => {
285
285
  const m = createMachine({
286
- initial: "idle",
286
+ initial: 'idle',
287
287
  states: {
288
- idle: { on: { GO: "active" } },
288
+ idle: { on: { GO: 'active' } },
289
289
  active: {},
290
290
  },
291
291
  })
292
292
 
293
293
  m.reset()
294
- expect(m()).toBe("idle")
294
+ expect(m()).toBe('idle')
295
295
  })
296
296
  })
297
297
 
298
- describe("createMachine — dispose()", () => {
299
- it("removes all onEnter listeners", () => {
298
+ describe('createMachine — dispose()', () => {
299
+ it('removes all onEnter listeners', () => {
300
300
  const m = createMachine({
301
- initial: "a",
301
+ initial: 'a',
302
302
  states: {
303
- a: { on: { GO: "b" } },
304
- b: { on: { GO: "a" } },
303
+ a: { on: { GO: 'b' } },
304
+ b: { on: { GO: 'a' } },
305
305
  },
306
306
  })
307
307
  const fn = vi.fn()
308
- m.onEnter("b", fn)
308
+ m.onEnter('b', fn)
309
309
 
310
310
  m.dispose()
311
311
 
312
- m.send("GO")
312
+ m.send('GO')
313
313
  expect(fn).not.toHaveBeenCalled()
314
314
  })
315
315
 
316
- it("removes all onTransition listeners", () => {
316
+ it('removes all onTransition listeners', () => {
317
317
  const m = createMachine({
318
- initial: "a",
318
+ initial: 'a',
319
319
  states: {
320
- a: { on: { GO: "b" } },
320
+ a: { on: { GO: 'b' } },
321
321
  b: {},
322
322
  },
323
323
  })
@@ -326,37 +326,37 @@ describe("createMachine — dispose()", () => {
326
326
 
327
327
  m.dispose()
328
328
 
329
- m.send("GO")
329
+ m.send('GO')
330
330
  expect(fn).not.toHaveBeenCalled()
331
331
  })
332
332
 
333
- it("machine still transitions after dispose (only listeners removed)", () => {
333
+ it('machine still transitions after dispose (only listeners removed)', () => {
334
334
  const m = createMachine({
335
- initial: "a",
335
+ initial: 'a',
336
336
  states: {
337
- a: { on: { GO: "b" } },
337
+ a: { on: { GO: 'b' } },
338
338
  b: {},
339
339
  },
340
340
  })
341
- m.onEnter("b", vi.fn())
341
+ m.onEnter('b', vi.fn())
342
342
  m.onTransition(vi.fn())
343
343
 
344
344
  m.dispose()
345
345
 
346
- m.send("GO")
347
- expect(m()).toBe("b") // transitions still work
346
+ m.send('GO')
347
+ expect(m()).toBe('b') // transitions still work
348
348
  })
349
349
 
350
- it("dispose is idempotent", () => {
350
+ it('dispose is idempotent', () => {
351
351
  const m = createMachine({
352
- initial: "a",
352
+ initial: 'a',
353
353
  states: {
354
- a: { on: { GO: "b" } },
354
+ a: { on: { GO: 'b' } },
355
355
  b: {},
356
356
  },
357
357
  })
358
358
  m.dispose()
359
359
  m.dispose() // should not throw
360
- expect(m()).toBe("a")
360
+ expect(m()).toBe('a')
361
361
  })
362
362
  })