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
@@ -35,6 +35,57 @@ const lightMachine = Machine({
35
35
  }
36
36
  });
37
37
 
38
+ describe('activities with guarded transitions', () => {
39
+ const machine = Machine({
40
+ initial: 'A',
41
+ states: {
42
+ A: {
43
+ on: {
44
+ E: 'B'
45
+ }
46
+ },
47
+ B: {
48
+ on: {
49
+ '': [{ cond: () => false, target: 'A' }]
50
+ },
51
+ activities: ['B_ACTIVITY']
52
+ }
53
+ }
54
+ });
55
+
56
+ it('should activate even if there are subsequent automatic, but blocked transitions', () => {
57
+ let state = machine.initialState;
58
+ state = machine.transition(state, 'E');
59
+ assert.deepEqual(state.activities, { B_ACTIVITY: true });
60
+ });
61
+ });
62
+
63
+ describe('remembering activities', () => {
64
+ const machine = Machine({
65
+ initial: 'A',
66
+ states: {
67
+ A: {
68
+ on: {
69
+ E: 'B'
70
+ }
71
+ },
72
+ B: {
73
+ on: {
74
+ E: 'A'
75
+ },
76
+ activities: ['B_ACTIVITY']
77
+ }
78
+ }
79
+ });
80
+
81
+ it('should remember the activities even after an event', () => {
82
+ let state = machine.initialState;
83
+ state = machine.transition(state, 'E');
84
+ state = machine.transition(state, 'IGNORE');
85
+ assert.deepEqual(state.activities, { B_ACTIVITY: true });
86
+ });
87
+ });
88
+
38
89
  describe('activities', () => {
39
90
  it('identifies initial activities', () => {
40
91
  const { initialState } = lightMachine;
@@ -92,3 +143,117 @@ describe('activities', () => {
92
143
  ]);
93
144
  });
94
145
  });
146
+
147
+ describe('transient activities', () => {
148
+ const machine = Machine({
149
+ parallel: true,
150
+ states: {
151
+ A: {
152
+ activities: ['A'],
153
+ initial: 'A1',
154
+ states: {
155
+ A1: {
156
+ activities: ['A1'],
157
+ on: {
158
+ A: 'AWAIT'
159
+ }
160
+ },
161
+ AWAIT: {
162
+ activities: ['AWAIT'],
163
+ on: {
164
+ '': 'A2'
165
+ }
166
+ },
167
+ A2: {
168
+ activities: ['A2'],
169
+ on: {
170
+ A: 'A1'
171
+ }
172
+ }
173
+ },
174
+ on: {
175
+ A1: '.A1',
176
+ A2: '.A2'
177
+ }
178
+ },
179
+ B: {
180
+ initial: 'B1',
181
+ activities: ['B'],
182
+ states: {
183
+ B1: {
184
+ activities: ['B1'],
185
+ on: {
186
+ '': [
187
+ {
188
+ in: 'A.AWAIT',
189
+ target: 'B2'
190
+ }
191
+ ],
192
+ B: 'B2'
193
+ }
194
+ },
195
+ B2: {
196
+ activities: ['B2'],
197
+ on: {
198
+ B: 'B1'
199
+ }
200
+ }
201
+ },
202
+ on: {
203
+ B1: '.B1',
204
+ B2: '.B2'
205
+ }
206
+ },
207
+ C: {
208
+ initial: 'C1',
209
+ states: {
210
+ C1: {
211
+ activities: ['C1'],
212
+ on: {
213
+ C: 'C1',
214
+ C_SIMILAR: 'C2'
215
+ }
216
+ },
217
+ C2: {
218
+ activities: ['C1'],
219
+ },
220
+ }
221
+ }
222
+ }
223
+ });
224
+
225
+ it('should have started initial activities', () => {
226
+ let state = machine.initialState;
227
+ assert.deepEqual(state.activities.A, true);
228
+ });
229
+
230
+ it('should have started deep initial activities', () => {
231
+ let state = machine.initialState;
232
+ assert.deepEqual(state.activities.A1, true);
233
+ });
234
+
235
+ it('should have kept existing activities', () => {
236
+ let state = machine.initialState;
237
+ state = machine.transition(state, 'A');
238
+ assert.deepEqual(state.activities.A, true);
239
+ });
240
+
241
+ it('should have kept same activities', () => {
242
+ let state = machine.initialState;
243
+ state = machine.transition(state, 'C_SIMILAR');
244
+ assert.deepEqual(state.activities.C1, true);
245
+ });
246
+
247
+ it('should have kept same activities after self transition', () => {
248
+ let state = machine.initialState;
249
+ state = machine.transition(state, 'C');
250
+ assert.deepEqual(state.activities.C1, true);
251
+ });
252
+
253
+ it('should have stopped after automatic transitions', () => {
254
+ let state = machine.initialState;
255
+ state = machine.transition(state, 'A');
256
+ assert.deepEqual(state.value, { A: 'A2', B: 'B2', C: 'C1' });
257
+ assert.deepEqual(state.activities.B2, true);
258
+ });
259
+ });
package/test/deep.test.ts CHANGED
@@ -6,7 +6,7 @@ describe('deep transitions', () => {
6
6
  key: 'deep',
7
7
  initial: 'A',
8
8
  on: {
9
- MACHINE_EVENT: '#DONE'
9
+ MACHINE_EVENT: '#deep.DONE'
10
10
  },
11
11
  states: {
12
12
  DONE: {},
@@ -103,7 +103,7 @@ describe('deep transitions', () => {
103
103
  assert.deepEqual(actual, expected);
104
104
  });
105
105
 
106
- xit('should exit substates and superstates when exiting (B_EVENT)', () => {
106
+ it('should exit substates and superstates when exiting (B_EVENT)', () => {
107
107
  const actual = deepMachine
108
108
  .transition(deepMachine.initialState, 'B_EVENT')
109
109
  .actions.map(a => `${a}`);
@@ -111,7 +111,7 @@ describe('deep transitions', () => {
111
111
  assert.deepEqual(actual, expected);
112
112
  });
113
113
 
114
- xit('should exit substates and superstates when exiting (C_EVENT)', () => {
114
+ it('should exit substates and superstates when exiting (C_EVENT)', () => {
115
115
  const actual = deepMachine
116
116
  .transition(deepMachine.initialState, 'C_EVENT')
117
117
  .actions.map(a => `${a}`);
@@ -119,7 +119,7 @@ describe('deep transitions', () => {
119
119
  assert.deepEqual(actual, expected);
120
120
  });
121
121
 
122
- xit('should exit superstates when exiting (D_EVENT)', () => {
122
+ it('should exit superstates when exiting (D_EVENT)', () => {
123
123
  const actual = deepMachine
124
124
  .transition(deepMachine.initialState, 'D_EVENT')
125
125
  .actions.map(a => `${a}`);
@@ -127,16 +127,14 @@ describe('deep transitions', () => {
127
127
  assert.deepEqual(actual, expected);
128
128
  });
129
129
 
130
- xit(
131
- 'should exit substate when machine handles event (MACHINE_EVENT)',
132
- () => {
133
- const actual = deepMachine
134
- .transition(deepMachine.initialState, 'MACHINE_EVENT')
135
- .actions.map(a => `${a}`);
136
- const expected = ['EXIT_D', 'EXIT_C', 'EXIT_B', 'EXIT_A'];
137
- assert.deepEqual(actual, expected);
138
- }
139
- );
130
+ it('should exit substate when machine handles event (MACHINE_EVENT)', () => {
131
+ // console.log(deepMachine.initialState.value);
132
+ const actual = deepMachine
133
+ .transition(deepMachine.initialState, 'MACHINE_EVENT')
134
+ .actions.map(a => `${a}`);
135
+ const expected = ['EXIT_D', 'EXIT_C', 'EXIT_B', 'EXIT_A'];
136
+ assert.deepEqual(actual, expected);
137
+ });
140
138
 
141
139
  const DBCAPQRS = [
142
140
  'EXIT_D',
@@ -157,7 +155,7 @@ describe('deep transitions', () => {
157
155
  assert.deepEqual(actual, expected);
158
156
  });
159
157
 
160
- xit('should exit deep and enter deep (D_P)', () => {
158
+ it('should exit deep and enter deep (D_P)', () => {
161
159
  const actual = deepMachine
162
160
  .transition(deepMachine.initialState, 'D_P')
163
161
  .actions.map(a => `${a}`);
@@ -173,7 +171,7 @@ describe('deep transitions', () => {
173
171
  assert.deepEqual(actual, expected);
174
172
  });
175
173
 
176
- xit('should exit deep and enter deep (D_S)', () => {
174
+ it('should exit deep and enter deep (D_S)', () => {
177
175
  const actual = deepMachine
178
176
  .transition(deepMachine.initialState, 'D_S')
179
177
  .actions.map(a => `${a}`);
@@ -1,5 +1,5 @@
1
1
  import { assert } from 'chai';
2
- import { Machine } from '../src/index';
2
+ import { Machine } from '../src/StateNode';
3
3
 
4
4
  describe('deterministic machine', () => {
5
5
  const pedestrianStates = {
@@ -135,10 +135,7 @@ describe('deterministic machine', () => {
135
135
  });
136
136
 
137
137
  it('should throw an error for transitions to invalid states', () => {
138
- assert.throws(
139
- () => testMachine.transition('a', 'F'),
140
- "Event 'F' on state 'a' leads to undefined state 'c'."
141
- );
138
+ assert.throws(() => testMachine.transition('a', 'F'));
142
139
  });
143
140
 
144
141
  it('should throw an error for transitions from invalid substates', () => {
@@ -206,4 +203,28 @@ describe('deterministic machine', () => {
206
203
  assert.equal(initialState, nextState);
207
204
  });
208
205
  });
206
+
207
+ describe('state key names', () => {
208
+ const machine = Machine({
209
+ key: 'test',
210
+ initial: 'test',
211
+ states: {
212
+ test: {
213
+ activities: ['activity'],
214
+ onEntry: ['onEntry'],
215
+ on: {
216
+ NEXT: 'test'
217
+ },
218
+ onExit: ['onExit']
219
+ }
220
+ }
221
+ });
222
+
223
+ it('should work with substate nodes that have the same key', () => {
224
+ assert.deepEqual(
225
+ machine.transition(machine.initialState, 'NEXT').value,
226
+ 'test'
227
+ );
228
+ });
229
+ });
209
230
  });
@@ -65,3 +65,67 @@ describe('Example 6.17', () => {
65
65
  });
66
66
  });
67
67
  });
68
+
69
+ describe('Jump to ID', () => {
70
+ const machine = Machine({
71
+ initial: 'X',
72
+ states: {
73
+ X: {
74
+ id: 'X',
75
+ on: {
76
+ 1: 'Y',
77
+ 2: 'Y.A.C', // 6.18
78
+ // 3: { Y: { A: 'C', B: 'F' } } // 6.19
79
+ 4: 'Y.A.$history'
80
+ }
81
+ },
82
+ Y: {
83
+ parallel: true,
84
+ states: {
85
+ A: {
86
+ initial: 'D',
87
+ states: {
88
+ C: {
89
+ on: {
90
+ finish: '#X'
91
+ }
92
+ },
93
+ D: {},
94
+ E: {}
95
+ }
96
+ },
97
+ B: {
98
+ initial: 'G',
99
+ states: { F: {}, G: {}, H: {} }
100
+ }
101
+ },
102
+ on: {
103
+ kill: '#X'
104
+ }
105
+ }
106
+ }
107
+ });
108
+
109
+ const expected = {
110
+ 'Y.B.G': {
111
+ kill: 'X'
112
+ },
113
+ '{"Y":{"A":"C","B":"H"}}': {
114
+ finish: 'X'
115
+ }
116
+ };
117
+
118
+ Object.keys(expected).forEach(fromState => {
119
+ Object.keys(expected[fromState]).forEach(eventTypes => {
120
+ const toState = expected[fromState][eventTypes];
121
+
122
+ it(`should go from ${fromState} to ${JSON.stringify(
123
+ toState
124
+ )} on ${eventTypes}`, () => {
125
+ const resultState = testMultiTransition(machine, fromState, eventTypes);
126
+
127
+ assert.deepEqual(resultState.value, toState);
128
+ });
129
+ });
130
+ });
131
+ });
@@ -1,4 +1,4 @@
1
- import { Machine } from '../../src/StateNode';
1
+ import { Machine } from '../../src';
2
2
 
3
3
  export const machine = Machine({
4
4
  initial: 'A',
@@ -62,6 +62,27 @@ describe('graph utilities', () => {
62
62
  }
63
63
  });
64
64
 
65
+ const condMachine = Machine({
66
+ key: 'cond',
67
+ initial: 'pending',
68
+ states: {
69
+ pending: {
70
+ on: {
71
+ EVENT: [
72
+ { target: 'foo', cond: (_, e) => e.id === 'foo' },
73
+ { target: 'bar' }
74
+ ],
75
+ STATE: [
76
+ { target: 'foo', cond: s => s.id === 'foo' },
77
+ { target: 'bar' }
78
+ ]
79
+ }
80
+ },
81
+ foo: {},
82
+ bar: {}
83
+ }
84
+ });
85
+
65
86
  const parallelMachine = Machine({
66
87
  parallel: true,
67
88
  key: 'p',
@@ -342,24 +363,26 @@ describe('graph utilities', () => {
342
363
  });
343
364
 
344
365
  it('should not throw when a condition is present', () => {
345
- const condMachine = Machine({
346
- initial: 'a',
347
- states: {
348
- a: {
349
- on: {
350
- NEXT: {
351
- b: {
352
- cond: payload => payload.foo
353
- }
354
- }
355
- }
356
- },
357
- b: {}
358
- }
359
- });
360
-
361
366
  assert.doesNotThrow(() => getShortestPaths(condMachine));
362
367
  });
368
+
369
+ it('should represent conditional paths based on extended state', () => {
370
+ assert.deepEqual(getShortestPaths(condMachine, { id: 'foo' }), {
371
+ '"bar"': [
372
+ {
373
+ event: 'EVENT',
374
+ state: 'pending'
375
+ }
376
+ ],
377
+ '"foo"': [
378
+ {
379
+ event: 'STATE',
380
+ state: 'pending'
381
+ }
382
+ ],
383
+ '"pending"': []
384
+ });
385
+ });
363
386
  });
364
387
 
365
388
  describe('getShortestPathsAsArray()', () => {
@@ -1,26 +1,47 @@
1
1
  import { assert } from 'chai';
2
- import { Machine } from '../src/index';
2
+ import { Machine, matchesState } from '../src/index';
3
3
 
4
4
  describe('guard conditions', () => {
5
- const lightMachine = Machine({
6
- key: 'light',
7
- initial: 'green',
8
- states: {
9
- green: {
10
- on: {
11
- TIMER: {
12
- green: {
13
- cond: ({ elapsed }) => elapsed < 100
5
+ const lightMachine = Machine(
6
+ {
7
+ key: 'light',
8
+ initial: 'green',
9
+ states: {
10
+ green: {
11
+ on: {
12
+ TIMER: {
13
+ green: {
14
+ cond: ({ elapsed }) => elapsed < 100
15
+ },
16
+ yellow: {
17
+ cond: ({ elapsed }) => elapsed >= 100 && elapsed < 200
18
+ }
14
19
  },
15
- yellow: {
16
- cond: ({ elapsed }) => elapsed >= 100 && elapsed < 200
20
+ EMERGENCY: {
21
+ red: { cond: (_, event) => event.isEmergency }
22
+ }
23
+ }
24
+ },
25
+ yellow: {
26
+ on: {
27
+ TIMER: {
28
+ red: { cond: 'minTimeElapsed' }
17
29
  }
18
30
  }
31
+ },
32
+ red: {
33
+ on: {
34
+ BAD_COND: { red: { cond: 'doesNotExist' } }
35
+ }
19
36
  }
20
- },
21
- yellow: {}
37
+ }
38
+ },
39
+ {
40
+ guards: {
41
+ minTimeElapsed: ({ elapsed }) => elapsed >= 100 && elapsed < 200
42
+ }
22
43
  }
23
- });
44
+ );
24
45
 
25
46
  it('should transition only if condition is met', () => {
26
47
  assert.equal(
@@ -42,6 +63,22 @@ describe('guard conditions', () => {
42
63
  );
43
64
  });
44
65
 
66
+ it('should transition if condition based on event is met', () => {
67
+ assert.equal(
68
+ lightMachine
69
+ .transition('green', { type: 'EMERGENCY', isEmergency: true })
70
+ .toString(),
71
+ 'red'
72
+ );
73
+ });
74
+
75
+ it('should not transition if condition based on event is not met', () => {
76
+ assert.equal(
77
+ lightMachine.transition('green', { type: 'EMERGENCY' }).toString(),
78
+ 'green'
79
+ );
80
+ });
81
+
45
82
  it('should not transition if no condition is met', () => {
46
83
  const nextState = lightMachine.transition('green', 'TIMER', {
47
84
  elapsed: 9000
@@ -49,4 +86,124 @@ describe('guard conditions', () => {
49
86
  assert.equal(nextState.value, 'green');
50
87
  assert.isEmpty(nextState.actions);
51
88
  });
89
+
90
+ it('should work with defined string transitions', () => {
91
+ const nextState = lightMachine.transition('yellow', 'TIMER', {
92
+ elapsed: 150
93
+ });
94
+ assert.equal(nextState.value, 'red');
95
+ });
96
+
97
+ it('should work with defined string transitions (condition not met)', () => {
98
+ const nextState = lightMachine.transition('yellow', 'TIMER', {
99
+ elapsed: 10
100
+ });
101
+ assert.equal(nextState.value, 'yellow');
102
+ });
103
+
104
+ it('should throw if string transition is not defined', () => {
105
+ assert.throws(() => lightMachine.transition('red', 'BAD_COND'));
106
+ });
107
+ });
108
+
109
+ describe('guard conditions', () => {
110
+ const machine = Machine({
111
+ key: 'microsteps',
112
+ parallel: true,
113
+ states: {
114
+ A: {
115
+ initial: 'A0',
116
+ states: {
117
+ A0: {
118
+ on: {
119
+ A: 'A1'
120
+ }
121
+ },
122
+ A1: {
123
+ on: {
124
+ A: 'A2'
125
+ }
126
+ },
127
+ A2: {
128
+ on: {
129
+ A: 'A3'
130
+ }
131
+ },
132
+ A3: {
133
+ on: {
134
+ '': 'A4'
135
+ }
136
+ },
137
+ A4: {
138
+ on: {
139
+ '': 'A5'
140
+ }
141
+ },
142
+ A5: {}
143
+ }
144
+ },
145
+ B: {
146
+ initial: 'B0',
147
+ states: {
148
+ B0: {
149
+ on: {
150
+ T1: [
151
+ {
152
+ target: 'B1',
153
+ cond: (_state, _event, interim) =>
154
+ matchesState('A.A1', interim)
155
+ }
156
+ ],
157
+ T2: [
158
+ {
159
+ target: 'B2',
160
+ cond: (_state, _event, interim) =>
161
+ matchesState('A.A2', interim)
162
+ }
163
+ ],
164
+ T3: [
165
+ {
166
+ target: 'B3',
167
+ cond: (_state, _event, interim) =>
168
+ matchesState('A.A3', interim)
169
+ }
170
+ ],
171
+ '': [
172
+ {
173
+ target: 'B4',
174
+ cond: (_state, _event, interim) =>
175
+ matchesState('A.A4', interim)
176
+ }
177
+ ]
178
+ }
179
+ },
180
+ B1: {},
181
+ B2: {},
182
+ B3: {},
183
+ B4: {}
184
+ }
185
+ }
186
+ }
187
+ });
188
+
189
+ it('should guard against transition', () => {
190
+ assert.deepEqual(machine.transition({ A: 'A2', B: 'B0' }, 'T1').value, {
191
+ A: 'A2',
192
+ B: 'B0'
193
+ });
194
+ });
195
+
196
+ it('should allow a matching transition', () => {
197
+ assert.deepEqual(machine.transition({ A: 'A2', B: 'B0' }, 'T2').value, {
198
+ A: 'A2',
199
+ B: 'B2'
200
+ });
201
+ });
202
+
203
+ it('should check guards with interim states', () => {
204
+ assert.deepEqual(machine.transition({ A: 'A2', B: 'B0' }, 'A').value, {
205
+ A: 'A5',
206
+ B: 'B4'
207
+ });
208
+ });
52
209
  });