xstate 3.2.1 → 3.3.3

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.
Files changed (55) hide show
  1. package/.vscode/launch.json +15 -13
  2. package/README.md +37 -9
  3. package/dist/xstate.js +1 -1
  4. package/dist/xstate.utils.js +1 -1
  5. package/es/Machine.d.ts +2 -2
  6. package/es/Machine.js +2 -2
  7. package/es/State.d.ts +8 -7
  8. package/es/State.js +3 -2
  9. package/es/StateNode.d.ts +50 -13
  10. package/es/StateNode.js +617 -412
  11. package/es/graph.d.ts +9 -6
  12. package/es/graph.js +31 -24
  13. package/es/patterns.js +1 -1
  14. package/es/scxml.d.ts +2 -1
  15. package/es/scxml.js +33 -10
  16. package/es/types.d.ts +38 -7
  17. package/es/utils.d.ts +14 -1
  18. package/es/utils.js +33 -5
  19. package/lib/Machine.d.ts +2 -2
  20. package/lib/Machine.js +2 -2
  21. package/lib/State.d.ts +8 -7
  22. package/lib/State.js +3 -2
  23. package/lib/StateNode.d.ts +50 -13
  24. package/lib/StateNode.js +616 -411
  25. package/lib/graph.d.ts +9 -6
  26. package/lib/graph.js +30 -22
  27. package/lib/patterns.js +1 -1
  28. package/lib/scxml.d.ts +2 -1
  29. package/lib/scxml.js +33 -10
  30. package/lib/types.d.ts +38 -7
  31. package/lib/utils.d.ts +14 -1
  32. package/lib/utils.js +35 -5
  33. package/package.json +3 -3
  34. package/src/Machine.ts +5 -3
  35. package/src/State.ts +10 -2
  36. package/src/StateNode.ts +966 -590
  37. package/src/graph.ts +60 -31
  38. package/src/scxml.ts +80 -49
  39. package/src/types.ts +48 -7
  40. package/src/utils.ts +52 -7
  41. package/test/actions.test.ts +24 -1
  42. package/test/activities.test.ts +165 -0
  43. package/test/deep.test.ts +14 -16
  44. package/test/deterministic.test.ts +26 -5
  45. package/test/examples/6.17.test.ts +64 -0
  46. package/test/fixtures/id.ts +1 -1
  47. package/test/graph.test.ts +39 -16
  48. package/test/guards.test.ts +172 -15
  49. package/test/history.test.ts +193 -58
  50. package/test/invalid.test.ts +48 -0
  51. package/test/multiple.test.ts +12 -18
  52. package/test/parallel.test.ts +472 -1
  53. package/test/scxml.test.ts +13 -4
  54. package/test/stateIn.test.ts +1 -1
  55. package/test/transient.test.ts +183 -1
@@ -7,7 +7,7 @@ describe('history states', () => {
7
7
  initial: 'off',
8
8
  states: {
9
9
  off: {
10
- on: { POWER: 'on.$history' }
10
+ on: { POWER: 'on.$history', H_POWER: 'on.H' }
11
11
  },
12
12
  on: {
13
13
  initial: 'first',
@@ -18,10 +18,14 @@ describe('history states', () => {
18
18
  second: {
19
19
  on: { SWITCH: 'third' }
20
20
  },
21
- third: {}
21
+ third: {},
22
+ H: {
23
+ history: true
24
+ }
22
25
  },
23
26
  on: {
24
- POWER: 'off'
27
+ POWER: 'off',
28
+ H_POWER: 'off'
25
29
  }
26
30
  }
27
31
  }
@@ -37,12 +41,36 @@ describe('history states', () => {
37
41
  );
38
42
  });
39
43
 
44
+ it('should go to the most recently visited state (explicit)', () => {
45
+ const onSecondState = historyMachine.transition('on', 'SWITCH');
46
+ const offState = historyMachine.transition(onSecondState, 'H_POWER');
47
+
48
+ assert.equal(
49
+ historyMachine.transition(offState, 'H_POWER').toString(),
50
+ 'on.second'
51
+ );
52
+ });
53
+
40
54
  it('should go to the initial state when no history present', () => {
41
55
  assert.equal(
42
56
  historyMachine.transition('off', 'POWER').toString(),
43
57
  'on.first'
44
58
  );
45
59
  });
60
+
61
+ it('should go to the initial state when no history present (explicit)', () => {
62
+ assert.equal(
63
+ historyMachine.transition('off', 'H_POWER').toString(),
64
+ 'on.first'
65
+ );
66
+ });
67
+
68
+ it('should dispose of previous histories', () => {
69
+ const onSecondState = historyMachine.transition('on', 'SWITCH');
70
+ const offState = historyMachine.transition(onSecondState, 'H_POWER');
71
+ const nextState = historyMachine.transition(offState, 'H_POWER');
72
+ assert.isUndefined(nextState.history!.history);
73
+ });
46
74
  });
47
75
 
48
76
  describe('deep history states', () => {
@@ -53,8 +81,7 @@ describe('deep history states', () => {
53
81
  off: {
54
82
  on: {
55
83
  POWER: 'on.$history',
56
- DEEP_POWER: 'on.$history.$history',
57
- DEEPEST_POWER: 'on.$history.$history.$history'
84
+ DEEP_POWER: 'on.deepHistory'
58
85
  }
59
86
  },
60
87
  on: {
@@ -79,6 +106,9 @@ describe('deep history states', () => {
79
106
  }
80
107
  }
81
108
  }
109
+ },
110
+ deepHistory: {
111
+ history: 'deep'
82
112
  }
83
113
  },
84
114
  on: {
@@ -104,7 +134,8 @@ describe('deep history states', () => {
104
134
  'on.second.A'
105
135
  );
106
136
  });
107
- it('should go to the deep history', () => {
137
+
138
+ it('should go to the deep history (explicit)', () => {
108
139
  // on.second.B.P -> off
109
140
  const stateOff = historyMachine.transition(state2BP, 'POWER');
110
141
  assert.equal(
@@ -112,21 +143,15 @@ describe('deep history states', () => {
112
143
  'on.second.B.P'
113
144
  );
114
145
  });
146
+
115
147
  it('should go to the deepest history', () => {
116
148
  // on.second.B.Q -> off
117
149
  const stateOff = historyMachine.transition(state2BQ, 'POWER');
118
150
  assert.equal(
119
- historyMachine.transition(stateOff, 'DEEPEST_POWER').toString(),
151
+ historyMachine.transition(stateOff, 'DEEP_POWER').toString(),
120
152
  'on.second.B.Q'
121
153
  );
122
154
  });
123
- it('can go to the shallow histor even when $history.$history is used', () => {
124
- const stateOff = historyMachine.transition(state2A, 'POWER');
125
- assert.equal(
126
- historyMachine.transition(stateOff, 'DEEPEST_POWER').toString(),
127
- 'on.second.A'
128
- );
129
- });
130
155
  });
131
156
  });
132
157
 
@@ -139,12 +164,11 @@ describe('parallel history states', () => {
139
164
  on: {
140
165
  SWITCH: 'on', // go to the initial states
141
166
  POWER: 'on.$history',
142
- DEEP_POWER: 'on.$history.$history',
143
- DEEPEST_POWER: 'on.$history.$history.$history',
167
+ DEEP_POWER: 'on.deepHistory',
144
168
  PARALLEL_HISTORY: [{ target: ['on.A.$history', 'on.K.$history'] }],
145
169
  PARALLEL_SOME_HISTORY: [{ target: ['on.A.C', 'on.K.$history'] }],
146
170
  PARALLEL_DEEP_HISTORY: [
147
- { target: ['on.A.$history.$history', 'on.K.$history.$history'] }
171
+ { target: ['on.A.deepHistory', 'on.K.deepHistory'] }
148
172
  ]
149
173
  }
150
174
  },
@@ -165,6 +189,9 @@ describe('parallel history states', () => {
165
189
  },
166
190
  E: {}
167
191
  }
192
+ },
193
+ deepHistory: {
194
+ history: 'deep'
168
195
  }
169
196
  }
170
197
  },
@@ -182,8 +209,17 @@ describe('parallel history states', () => {
182
209
  },
183
210
  O: {}
184
211
  }
212
+ },
213
+ deepHistory: {
214
+ history: 'deep'
185
215
  }
186
216
  }
217
+ },
218
+ shallowHistory: {
219
+ history: 'shallow'
220
+ },
221
+ deepHistory: {
222
+ history: 'deep'
187
223
  }
188
224
  },
189
225
  on: {
@@ -224,50 +260,10 @@ describe('parallel history states', () => {
224
260
  );
225
261
  });
226
262
 
227
- it('should remember second level state history', () => {
228
- const stateOff = historyMachine.transition(stateACDKL, 'POWER');
229
- assert.deepEqual(
230
- historyMachine.transition(stateOff, 'DEEPEST_POWER').value,
231
- {
232
- on: { A: { C: 'D' }, K: 'L' }
233
- }
234
- );
235
- });
236
-
237
- it('should remember second level state history, ignoring too many levels of $history', () => {
238
- const stateOff = historyMachine.transition(stateACDKL, 'POWER');
239
- assert.deepEqual(
240
- historyMachine.transition(stateOff, 'DEEPEST_POWER').value,
241
- {
242
- on: { A: { C: 'D' }, K: 'L' }
243
- }
244
- );
245
- });
246
-
247
- it('should remember three levels of state history', () => {
248
- const stateOff = historyMachine.transition(stateACEKL, 'POWER');
249
- assert.deepEqual(
250
- historyMachine.transition(stateOff, 'DEEPEST_POWER').value,
251
- {
252
- on: { A: { C: 'E' }, K: 'L' }
253
- }
254
- );
255
- });
256
-
257
- xit('should re-enter each regions of parallel state correctly', () => {
263
+ it('should re-enter each regions of parallel state correctly', () => {
258
264
  const stateOff = historyMachine.transition(stateACEKMO, 'POWER');
259
265
  assert.deepEqual(
260
266
  historyMachine.transition(stateOff, 'DEEP_POWER').value,
261
- {
262
- on: { A: { C: 'D' }, K: { M: 'N' } }
263
- }
264
- );
265
- });
266
-
267
- xit('should retain all regions of parallel state', () => {
268
- const stateOff = historyMachine.transition(stateACEKMO, 'POWER');
269
- assert.deepEqual(
270
- historyMachine.transition(stateOff, 'DEEPEST_POWER').value,
271
267
  {
272
268
  on: { A: { C: 'E' }, K: { M: 'O' } }
273
269
  }
@@ -305,3 +301,142 @@ describe('parallel history states', () => {
305
301
  });
306
302
  });
307
303
  });
304
+
305
+ describe('transient history', () => {
306
+ const transientMachine = Machine({
307
+ initial: 'A',
308
+ parallel: false,
309
+ states: {
310
+ A: {
311
+ on: { EVENT: 'B' }
312
+ },
313
+ B: {
314
+ on: {
315
+ // eventless transition
316
+ '': 'C'
317
+ }
318
+ },
319
+ C: {}
320
+ }
321
+ });
322
+ it('should have history on transient transitions', () => {
323
+ const nextState = transientMachine.transition('A', 'EVENT');
324
+ assert.equal(nextState.value, 'C');
325
+ assert.isDefined(nextState.history);
326
+ });
327
+ });
328
+
329
+ describe('internal transition with history', () => {
330
+ const machine = Machine({
331
+ key: 'test',
332
+ initial: 'first',
333
+ states: {
334
+ first: {
335
+ initial: 'foo',
336
+ states: {
337
+ foo: {}
338
+ },
339
+ on: {
340
+ NEXT: 'second.other'
341
+ }
342
+ },
343
+ second: {
344
+ initial: 'nested',
345
+ states: {
346
+ nested: {},
347
+ other: {},
348
+ hist: {
349
+ history: true
350
+ }
351
+ },
352
+ on: {
353
+ NEXT: [
354
+ {
355
+ target: '.hist'
356
+ }
357
+ ]
358
+ }
359
+ }
360
+ }
361
+ });
362
+
363
+ it('should transition internally to the most recently visited state', () => {
364
+ // {
365
+ // $current: 'first',
366
+ // first: undefined,
367
+ // second: {
368
+ // $current: 'nested',
369
+ // nested: undefined,
370
+ // other: undefined
371
+ // }
372
+ // }
373
+ const state2 = machine.transition(machine.initialState, 'NEXT');
374
+ // {
375
+ // $current: 'second',
376
+ // first: undefined,
377
+ // second: {
378
+ // $current: 'other',
379
+ // nested: undefined,
380
+ // other: undefined
381
+ // }
382
+ // }
383
+ const state3 = machine.transition(state2, 'NEXT');
384
+ // {
385
+ // $current: 'second',
386
+ // first: undefined,
387
+ // second: {
388
+ // $current: 'other',
389
+ // nested: undefined,
390
+ // other: undefined
391
+ // }
392
+ // }
393
+
394
+ assert.deepEqual(state3.value, { second: 'other' });
395
+ });
396
+ });
397
+
398
+ describe('multistage history states', () => {
399
+ const pcWithTurboButtonMachine = Machine({
400
+ key: 'pc-with-turbo-button',
401
+ initial: 'off',
402
+ states: {
403
+ off: {
404
+ on: { POWER: 'starting' }
405
+ },
406
+ starting: {
407
+ on: { STARTED: 'running.H' }
408
+ },
409
+ running: {
410
+ initial: 'normal',
411
+ states: {
412
+ normal: {
413
+ on: { SWITCH_TURBO: 'turbo' }
414
+ },
415
+ turbo: {
416
+ on: { SWITCH_TURBO: 'normal' }
417
+ },
418
+ H: {
419
+ history: true
420
+ }
421
+ },
422
+ on: {
423
+ POWER: 'off'
424
+ }
425
+ }
426
+ }
427
+ });
428
+
429
+ it('should go to the most recently visited state', () => {
430
+ const onTurboState = pcWithTurboButtonMachine.transition(
431
+ 'running',
432
+ 'SWITCH_TURBO'
433
+ );
434
+ const offState = pcWithTurboButtonMachine.transition(onTurboState, 'POWER');
435
+ const loadingState = pcWithTurboButtonMachine.transition(offState, 'POWER');
436
+
437
+ assert.equal(
438
+ pcWithTurboButtonMachine.transition(loadingState, 'STARTED').toString(),
439
+ 'running.turbo'
440
+ );
441
+ });
442
+ });
@@ -0,0 +1,48 @@
1
+ import { assert } from 'chai';
2
+ import { Machine } from '../src/index';
3
+
4
+ const machine = Machine({
5
+ parallel: true,
6
+ states: {
7
+ A: {
8
+ initial: 'A1',
9
+ states: {
10
+ A1: {},
11
+ A2: {}
12
+ }
13
+ },
14
+ B: {
15
+ initial: 'B1',
16
+ states: {
17
+ B1: {},
18
+ B2: {}
19
+ }
20
+ }
21
+ }
22
+ });
23
+
24
+ describe('invalid states', () => {
25
+ xit('should reject transitioning from a String state', () => {
26
+ assert.throws(() => machine.transition('A', 'E'));
27
+ });
28
+
29
+ xit('should reject transitioning from empty states', () => {
30
+ assert.throws(() => machine.transition({ A: {}, B: {} }, 'E'));
31
+ });
32
+
33
+ it('should allow transitioning from valid states', () => {
34
+ machine.transition({ A: 'A1', B: 'B1' }, 'E');
35
+ });
36
+
37
+ it('should reject transitioning from bad state configs', () => {
38
+ assert.throws(() => machine.transition({ A: 'A3', B: 'B3' }, 'E'));
39
+ });
40
+
41
+ xit('should reject transitioning from partially valid states', () => {
42
+ assert.throws(() => machine.transition({ A: 'A1' }, 'E'));
43
+ });
44
+
45
+ xit("should reject transitioning from regions that don't exist", () => {
46
+ assert.throws(() => machine.transition({ A: 'A1', B: 'B1', Z: 'Z1' }, 'E'));
47
+ });
48
+ });
@@ -163,44 +163,38 @@ describe('multiple', () => {
163
163
  });
164
164
 
165
165
  it('should reject two targets in the same region', () => {
166
- assert.throws(
167
- () => machine.transition(stateSimple, 'BROKEN_SAME_REGION'),
168
- "Event 'BROKEN_SAME_REGION' on state 'machine.simple' leads to an invalid configuration: Two or more states in the orthogonal region 'machine.para.A'"
166
+ assert.throws(() =>
167
+ machine.transition(stateSimple, 'BROKEN_SAME_REGION')
169
168
  );
170
169
  });
171
170
 
172
171
  it('should reject targets inside and outside a region', () => {
173
- assert.throws(
174
- () => machine.transition(stateSimple, 'BROKEN_DIFFERENT_REGIONS'),
175
- "Event 'BROKEN_DIFFERENT_REGIONS' on state 'machine.simple' leads to an invalid configuration: Two or more states in the orthogonal region 'machine'"
172
+ assert.throws(() =>
173
+ machine.transition(stateSimple, 'BROKEN_DIFFERENT_REGIONS')
176
174
  );
177
175
  });
178
176
 
179
177
  it('should reject two targets in different regions', () => {
180
- assert.throws(
181
- () => machine.transition(stateSimple, 'BROKEN_DIFFERENT_REGIONS_2'),
182
- "Event 'BROKEN_DIFFERENT_REGIONS_2' on state 'machine.simple' leads to an invalid configuration: Two or more states in the orthogonal region 'machine'"
178
+ assert.throws(() =>
179
+ machine.transition(stateSimple, 'BROKEN_DIFFERENT_REGIONS_2')
183
180
  );
184
181
  });
185
182
 
186
183
  it('should reject two targets in different regions at different levels', () => {
187
- assert.throws(
188
- () => machine.transition(stateSimple, 'BROKEN_DIFFERENT_REGIONS_3'),
189
- "Event 'BROKEN_DIFFERENT_REGIONS_3' on state 'machine.simple' leads to an invalid configuration: Two or more states in the orthogonal region 'machine'"
184
+ assert.throws(() =>
185
+ machine.transition(stateSimple, 'BROKEN_DIFFERENT_REGIONS_3')
190
186
  );
191
187
  });
192
188
 
193
189
  it('should reject two deep targets in different regions at top level', () => {
194
- assert.throws(
195
- () => machine.transition(stateSimple, 'BROKEN_DIFFERENT_REGIONS_3'),
196
- "Event 'BROKEN_DIFFERENT_REGIONS_3' on state 'machine.simple' leads to an invalid configuration: Two or more states in the orthogonal region 'machine'"
190
+ assert.throws(() =>
191
+ machine.transition(stateSimple, 'BROKEN_DIFFERENT_REGIONS_3')
197
192
  );
198
193
  });
199
194
 
200
195
  it('should reject two deep targets in different regions at different levels', () => {
201
- assert.throws(
202
- () => machine.transition(stateSimple, 'BROKEN_DIFFERENT_REGIONS_4'),
203
- "Event 'BROKEN_DIFFERENT_REGIONS_4' on state 'machine.simple' leads to an invalid configuration: Two or more states in the orthogonal region 'machine.para2.K2'"
196
+ assert.throws(() =>
197
+ machine.transition(stateSimple, 'BROKEN_DIFFERENT_REGIONS_4')
204
198
  );
205
199
  });
206
200
  });