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
@@ -1,7 +1,194 @@
1
1
  import { assert } from 'chai';
2
- import { Machine } from '../src/index';
2
+ import { raise } from '../src/actions';
3
+ import { Machine } from '../src/StateNode';
3
4
  import { testMultiTransition } from './utils';
4
5
 
6
+ const composerMachine = Machine({
7
+ strict: true,
8
+ initial: 'ReadOnly',
9
+ states: {
10
+ ReadOnly: {
11
+ id: 'ReadOnly',
12
+ initial: 'StructureEdit',
13
+ onEntry: ['selectNone'],
14
+ states: {
15
+ StructureEdit: {
16
+ id: 'StructureEditRO',
17
+ parallel: true,
18
+ on: {
19
+ switchToProjectManagement: [
20
+ {
21
+ target: 'ProjectManagement'
22
+ }
23
+ ]
24
+ },
25
+ states: {
26
+ SelectionStatus: {
27
+ initial: 'SelectedNone',
28
+ on: {
29
+ singleClickActivity: [
30
+ {
31
+ target: '.SelectedActivity',
32
+ actions: ['selectActivity']
33
+ }
34
+ ],
35
+ singleClickLink: [
36
+ {
37
+ target: '.SelectedLink',
38
+ actions: ['selectLink']
39
+ }
40
+ ]
41
+ },
42
+ states: {
43
+ SelectedNone: {
44
+ onEntry: ['redraw']
45
+ },
46
+ SelectedActivity: {
47
+ onEntry: ['redraw'],
48
+ on: {
49
+ singleClickCanvas: [
50
+ {
51
+ target: 'SelectedNone',
52
+ actions: ['selectNone']
53
+ }
54
+ ]
55
+ }
56
+ },
57
+ SelectedLink: {
58
+ onEntry: ['redraw'],
59
+ on: {
60
+ singleClickCanvas: [
61
+ {
62
+ target: 'SelectedNone',
63
+ actions: ['selectNone']
64
+ }
65
+ ]
66
+ }
67
+ }
68
+ }
69
+ },
70
+ ClipboardStatus: {
71
+ initial: 'Empty',
72
+ states: {
73
+ Empty: {
74
+ onEntry: ['emptyClipboard'],
75
+ on: {
76
+ cutInClipboardSuccess: [
77
+ {
78
+ target: 'FilledByCut'
79
+ }
80
+ ],
81
+ copyInClipboardSuccess: [
82
+ {
83
+ target: 'FilledByCopy'
84
+ }
85
+ ]
86
+ }
87
+ },
88
+ FilledByCopy: {
89
+ on: {
90
+ cutInClipboardSuccess: [
91
+ {
92
+ target: 'FilledByCut'
93
+ }
94
+ ],
95
+ copyInClipboardSuccess: [
96
+ {
97
+ target: 'FilledByCopy'
98
+ }
99
+ ],
100
+ pasteFromClipboardSuccess: [
101
+ {
102
+ target: 'FilledByCopy'
103
+ }
104
+ ]
105
+ }
106
+ },
107
+ FilledByCut: {
108
+ on: {
109
+ cutInClipboardSuccess: [
110
+ {
111
+ target: 'FilledByCut'
112
+ }
113
+ ],
114
+ copyInClipboardSuccess: [
115
+ {
116
+ target: 'FilledByCopy'
117
+ }
118
+ ],
119
+ pasteFromClipboardSuccess: [
120
+ {
121
+ target: 'Empty'
122
+ }
123
+ ]
124
+ }
125
+ }
126
+ }
127
+ }
128
+ }
129
+ },
130
+ ProjectManagement: {
131
+ id: 'ProjectManagementRO',
132
+ parallel: true,
133
+ on: {
134
+ switchToStructureEdit: [
135
+ {
136
+ target: 'StructureEdit'
137
+ }
138
+ ]
139
+ },
140
+ states: {
141
+ SelectionStatus: {
142
+ initial: 'SelectedNone',
143
+ on: {
144
+ singleClickActivity: [
145
+ {
146
+ target: '.SelectedActivity',
147
+ actions: ['selectActivity']
148
+ }
149
+ ],
150
+ singleClickLink: [
151
+ {
152
+ target: '.SelectedLink',
153
+ actions: ['selectLink']
154
+ }
155
+ ]
156
+ },
157
+ states: {
158
+ SelectedNone: {
159
+ onEntry: ['redraw']
160
+ },
161
+ SelectedActivity: {
162
+ onEntry: ['redraw'],
163
+ on: {
164
+ singleClickCanvas: [
165
+ {
166
+ target: 'SelectedNone',
167
+ actions: ['selectNone']
168
+ }
169
+ ]
170
+ }
171
+ },
172
+ SelectedLink: {
173
+ onEntry: ['redraw'],
174
+ on: {
175
+ singleClickCanvas: [
176
+ {
177
+ target: 'SelectedNone',
178
+ actions: ['selectNone']
179
+ }
180
+ ]
181
+ }
182
+ }
183
+ }
184
+ }
185
+ }
186
+ }
187
+ }
188
+ }
189
+ }
190
+ });
191
+
5
192
  const wakMachine = Machine({
6
193
  id: 'wakMachine',
7
194
  parallel: true,
@@ -99,6 +286,172 @@ const wordMachine = Machine({
99
286
  }
100
287
  });
101
288
 
289
+ const flatParallelMachine = Machine({
290
+ parallel: true,
291
+ states: {
292
+ foo: {},
293
+ bar: {},
294
+ baz: {
295
+ initial: 'one',
296
+ states: {
297
+ one: { on: { E: 'two' } },
298
+ two: {}
299
+ }
300
+ }
301
+ }
302
+ });
303
+
304
+ const raisingParallelMachine = Machine({
305
+ strict: true,
306
+ parallel: true,
307
+ states: {
308
+ OUTER1: {
309
+ initial: 'C',
310
+ states: {
311
+ A: {
312
+ onEntry: [raise('TURN_OFF')],
313
+ on: {
314
+ EVENT_OUTER1_B: 'B',
315
+ EVENT_OUTER1_C: 'C'
316
+ }
317
+ },
318
+ B: {
319
+ onEntry: [raise('TURN_ON')],
320
+ on: {
321
+ EVENT_OUTER1_A: 'A',
322
+ EVENT_OUTER1_C: 'C'
323
+ }
324
+ },
325
+ C: {
326
+ onEntry: [raise('CLEAR')],
327
+ on: {
328
+ EVENT_OUTER1_A: 'A',
329
+ EVENT_OUTER1_B: 'B'
330
+ }
331
+ }
332
+ }
333
+ },
334
+ OUTER2: {
335
+ parallel: true,
336
+ states: {
337
+ INNER1: {
338
+ initial: 'ON',
339
+ states: {
340
+ OFF: {
341
+ on: {
342
+ TURN_ON: 'ON'
343
+ }
344
+ },
345
+ ON: {
346
+ on: {
347
+ CLEAR: 'OFF'
348
+ }
349
+ }
350
+ }
351
+ },
352
+ INNER2: {
353
+ initial: 'OFF',
354
+ states: {
355
+ OFF: {
356
+ on: {
357
+ TURN_ON: 'ON'
358
+ }
359
+ },
360
+ ON: {
361
+ on: {
362
+ TURN_OFF: 'OFF'
363
+ }
364
+ }
365
+ }
366
+ }
367
+ }
368
+ }
369
+ }
370
+ });
371
+
372
+ const nestedParallelState = Machine({
373
+ parallel: true,
374
+ states: {
375
+ OUTER1: {
376
+ initial: 'STATE_OFF',
377
+ states: {
378
+ STATE_OFF: {
379
+ on: {
380
+ EVENT_COMPLEX: 'STATE_ON',
381
+ EVENT_SIMPLE: 'STATE_ON'
382
+ }
383
+ },
384
+ STATE_ON: {
385
+ parallel: true,
386
+ states: {
387
+ STATE_NTJ0: {
388
+ initial: 'STATE_IDLE_0',
389
+ states: {
390
+ STATE_IDLE_0: {
391
+ on: {
392
+ EVENT_STATE_NTJ0_WORK: 'STATE_WORKING_0'
393
+ }
394
+ },
395
+ STATE_WORKING_0: {
396
+ on: {
397
+ EVENT_STATE_NTJ0_IDLE: 'STATE_IDLE_0'
398
+ }
399
+ }
400
+ }
401
+ },
402
+ STATE_NTJ1: {
403
+ initial: 'STATE_IDLE_1',
404
+ states: {
405
+ STATE_IDLE_1: {
406
+ on: {
407
+ EVENT_STATE_NTJ1_WORK: 'STATE_WORKING_1'
408
+ }
409
+ },
410
+ STATE_WORKING_1: {
411
+ on: {
412
+ EVENT_STATE_NTJ1_IDLE: 'STATE_IDLE_1'
413
+ }
414
+ }
415
+ }
416
+ }
417
+ }
418
+ }
419
+ }
420
+ },
421
+ OUTER2: {
422
+ initial: 'STATE_OFF',
423
+ states: {
424
+ STATE_OFF: {
425
+ on: {
426
+ EVENT_COMPLEX: 'STATE_ON_COMPLEX',
427
+ EVENT_SIMPLE: 'STATE_ON_SIMPLE'
428
+ }
429
+ },
430
+ STATE_ON_SIMPLE: {},
431
+ STATE_ON_COMPLEX: {
432
+ parallel: true,
433
+ states: {
434
+ STATE_INNER1: {
435
+ initial: 'STATE_OFF',
436
+ states: {
437
+ STATE_OFF: {},
438
+ STATE_ON: {}
439
+ }
440
+ },
441
+ STATE_INNER2: {
442
+ initial: 'STATE_OFF',
443
+ states: {
444
+ STATE_OFF: {},
445
+ STATE_ON: {}
446
+ }
447
+ }
448
+ }
449
+ }
450
+ }
451
+ }
452
+ }
453
+ });
454
+
102
455
  describe('parallel states', () => {
103
456
  it('should have initial parallel states', () => {
104
457
  const { initialState } = wordMachine;
@@ -166,4 +519,122 @@ describe('parallel states', () => {
166
519
 
167
520
  assert.deepEqual(nextState.value, { wak1: 'wak1sonB', wak2: 'wak2sonA' });
168
521
  });
522
+
523
+ it('should have all parallel states represented in the state value (2)', () => {
524
+ const nextState = wakMachine.transition(wakMachine.initialState, 'WAK2');
525
+
526
+ assert.deepEqual(nextState.value, { wak1: 'wak1sonA', wak2: 'wak2sonB' });
527
+ });
528
+
529
+ it('should work with regions without states', () => {
530
+ assert.deepEqual(flatParallelMachine.initialState.value, {
531
+ foo: {},
532
+ bar: {},
533
+ baz: 'one'
534
+ });
535
+ });
536
+
537
+ it('should work with regions without states', () => {
538
+ const nextState = flatParallelMachine.transition(
539
+ flatParallelMachine.initialState,
540
+ 'E'
541
+ );
542
+ assert.deepEqual(nextState.value, {
543
+ foo: {},
544
+ bar: {},
545
+ baz: 'two'
546
+ });
547
+ });
548
+
549
+ it('should properly transition to relative substate', () => {
550
+ const nextState = composerMachine.transition(
551
+ composerMachine.initialState,
552
+ 'singleClickActivity'
553
+ );
554
+
555
+ assert.deepEqual(nextState.value, {
556
+ ReadOnly: {
557
+ StructureEdit: {
558
+ SelectionStatus: 'SelectedActivity',
559
+ ClipboardStatus: 'Empty'
560
+ }
561
+ }
562
+ });
563
+ });
564
+
565
+ it('should properly transition according to onEntry events on an initial state', () => {
566
+ assert.deepEqual(raisingParallelMachine.initialState.value, {
567
+ OUTER1: 'C',
568
+ OUTER2: {
569
+ INNER1: 'OFF',
570
+ INNER2: 'OFF'
571
+ }
572
+ });
573
+ });
574
+
575
+ it('should properly transition when raising events for a parallel state', () => {
576
+ const nextState = raisingParallelMachine.transition(
577
+ raisingParallelMachine.initialState,
578
+ 'EVENT_OUTER1_B'
579
+ );
580
+
581
+ assert.deepEqual(nextState.value, {
582
+ OUTER1: 'B',
583
+ OUTER2: {
584
+ INNER1: 'ON',
585
+ INNER2: 'ON'
586
+ }
587
+ });
588
+ });
589
+
590
+ describe('transitions with nested parallel states', () => {
591
+ const initialState = nestedParallelState.initialState;
592
+ const simpleNextState = nestedParallelState.transition(
593
+ initialState,
594
+ 'EVENT_SIMPLE'
595
+ );
596
+ const complexNextState = nestedParallelState.transition(
597
+ initialState,
598
+ 'EVENT_COMPLEX'
599
+ );
600
+
601
+ it('should properly transition when in a simple nested state', () => {
602
+ const nextState = nestedParallelState.transition(
603
+ simpleNextState,
604
+ 'EVENT_STATE_NTJ0_WORK'
605
+ );
606
+
607
+ assert.deepEqual(nextState.value, {
608
+ OUTER1: {
609
+ STATE_ON: {
610
+ STATE_NTJ0: 'STATE_WORKING_0',
611
+ STATE_NTJ1: 'STATE_IDLE_1'
612
+ }
613
+ },
614
+ OUTER2: 'STATE_ON_SIMPLE'
615
+ });
616
+ });
617
+
618
+ it('should properly transition when in a complex nested state', () => {
619
+ const nextState = nestedParallelState.transition(
620
+ complexNextState,
621
+ 'EVENT_STATE_NTJ0_WORK'
622
+ );
623
+
624
+ assert.deepEqual(nextState.value, {
625
+ OUTER1: {
626
+ STATE_ON: {
627
+ STATE_NTJ0: 'STATE_WORKING_0',
628
+ STATE_NTJ1: 'STATE_IDLE_1'
629
+ }
630
+ },
631
+ OUTER2: {
632
+ STATE_ON_COMPLEX: {
633
+ STATE_INNER1: 'STATE_OFF',
634
+ STATE_INNER2: 'STATE_OFF'
635
+ }
636
+ }
637
+ });
638
+ });
639
+ });
169
640
  });
@@ -26,6 +26,15 @@ const testGroups = {
26
26
  documentOrder: ['documentOrder0'],
27
27
  hierarchy: ['hier0', 'hier1', 'hier2'],
28
28
  'hierarchy+documentOrder': ['test0', 'test1'],
29
+ history: [
30
+ 'history0',
31
+ 'history1',
32
+ 'history2',
33
+ 'history3'
34
+ // 'history4'
35
+ // 'history5'
36
+ // 'history6'
37
+ ],
29
38
  misc: ['deep-initial'],
30
39
  parallel: [
31
40
  'test0',
@@ -52,15 +61,15 @@ function runTestToCompletion(machine: StateNode, test: SCIONTest): void {
52
61
  test.initialConfiguration.map(id => machine.getStateNodeById(id).path)
53
62
  );
54
63
 
55
- for (const { event, nextConfiguration } of test.events) {
64
+ test.events.forEach(({ event, nextConfiguration }, i) => {
56
65
  nextState = machine.transition(nextState, event.name);
57
66
 
58
67
  const stateIds = machine
59
68
  .getStateNodes(nextState)
60
69
  .map(stateNode => stateNode.id);
61
70
 
62
- assert.include(stateIds, nextConfiguration[0]);
63
- }
71
+ assert.include(stateIds, nextConfiguration[0], `run ${i}`);
72
+ });
64
73
  }
65
74
 
66
75
  function evalCond(expr: string) {
@@ -92,7 +101,7 @@ describe('scxml', () => {
92
101
  evalCond,
93
102
  delimiter: '$'
94
103
  });
95
- // console.log(util.inspect(machine, false, 10));
104
+ // console.dir(machine, { depth: null });
96
105
  runTestToCompletion(Machine(machine), scxmlTest);
97
106
  });
98
107
  });
@@ -118,7 +118,7 @@ describe('transition "in" check', () => {
118
118
  );
119
119
  });
120
120
 
121
- it('matching should be relative to grandparent (match)', () => {
121
+ xit('matching should be relative to grandparent (match)', () => {
122
122
  assert.deepEqual(
123
123
  machine.transition(
124
124
  { a: 'a1', b: { b2: { foo: 'foo1', bar: 'bar1' } } },