xstate 5.19.4 → 5.20.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.
Files changed (65) hide show
  1. package/README.md +0 -1
  2. package/actions/dist/xstate-actions.cjs.d.ts +1 -1
  3. package/actions/dist/xstate-actions.cjs.js +8 -7
  4. package/actions/dist/xstate-actions.development.cjs.js +8 -7
  5. package/actions/dist/xstate-actions.development.esm.js +3 -2
  6. package/actions/dist/xstate-actions.esm.js +3 -2
  7. package/actions/dist/xstate-actions.umd.min.js +1 -1
  8. package/actions/dist/xstate-actions.umd.min.js.map +1 -1
  9. package/actors/dist/xstate-actors.cjs.d.ts +1 -1
  10. package/actors/dist/xstate-actors.development.esm.js +1 -1
  11. package/actors/dist/xstate-actors.esm.js +1 -1
  12. package/actors/dist/xstate-actors.umd.min.js.map +1 -1
  13. package/dev/dist/xstate-dev.cjs.d.ts +1 -1
  14. package/dev/dist/xstate-dev.umd.min.js.map +1 -1
  15. package/dist/StateMachine-1cda96d3.cjs.js +560 -0
  16. package/dist/StateMachine-38b5bb3f.development.cjs.js +566 -0
  17. package/dist/StateMachine-b4e94439.development.esm.js +563 -0
  18. package/dist/StateMachine-c88ea5dd.esm.js +557 -0
  19. package/dist/assign-6313ccb3.development.esm.js +133 -0
  20. package/dist/assign-c3259787.esm.js +127 -0
  21. package/dist/assign-c84786ab.development.cjs.js +135 -0
  22. package/dist/assign-e9c344ea.cjs.js +129 -0
  23. package/dist/declarations/src/StateMachine.d.ts +2 -2
  24. package/dist/declarations/src/createMachine.d.ts +1 -2
  25. package/dist/declarations/src/graph/TestModel.d.ts +70 -0
  26. package/dist/declarations/src/graph/adjacency.d.ts +8 -0
  27. package/dist/declarations/src/graph/graph.d.ts +14 -0
  28. package/dist/declarations/src/graph/index.d.ts +9 -0
  29. package/dist/declarations/src/graph/pathFromEvents.d.ts +3 -0
  30. package/dist/declarations/src/graph/pathGenerators.d.ts +4 -0
  31. package/dist/declarations/src/graph/shortestPaths.d.ts +3 -0
  32. package/dist/declarations/src/graph/simplePaths.d.ts +3 -0
  33. package/dist/declarations/src/graph/types.d.ts +159 -0
  34. package/dist/declarations/src/index.d.ts +1 -1
  35. package/dist/declarations/src/inspection.d.ts +1 -1
  36. package/dist/{log-655aa404.esm.js → log-1c257a58.esm.js} +3 -126
  37. package/dist/{log-fa2e731a.cjs.js → log-215998b6.cjs.js} +2 -126
  38. package/dist/{log-fadc8808.development.cjs.js → log-2c8d7f98.development.cjs.js} +2 -132
  39. package/dist/{log-5a7b5528.development.esm.js → log-ef959da6.development.esm.js} +3 -132
  40. package/dist/{raise-59549771.development.esm.js → raise-78b8dcb8.development.esm.js} +1 -1
  41. package/dist/{raise-3e01e82a.esm.js → raise-b0a4e862.esm.js} +1 -1
  42. package/dist/xstate.cjs.d.ts +1 -1
  43. package/dist/xstate.cjs.js +7 -558
  44. package/dist/xstate.development.cjs.js +7 -564
  45. package/dist/xstate.development.esm.js +7 -564
  46. package/dist/xstate.esm.js +7 -558
  47. package/dist/xstate.umd.min.js +1 -1
  48. package/dist/xstate.umd.min.js.map +1 -1
  49. package/graph/dist/xstate-graph.cjs.d.mts +2 -0
  50. package/graph/dist/xstate-graph.cjs.d.ts +2 -0
  51. package/graph/dist/xstate-graph.cjs.js +901 -0
  52. package/graph/dist/xstate-graph.cjs.mjs +15 -0
  53. package/graph/dist/xstate-graph.development.cjs.js +901 -0
  54. package/graph/dist/xstate-graph.development.cjs.mjs +15 -0
  55. package/graph/dist/xstate-graph.development.esm.js +885 -0
  56. package/graph/dist/xstate-graph.esm.js +885 -0
  57. package/graph/dist/xstate-graph.umd.min.js +2 -0
  58. package/graph/dist/xstate-graph.umd.min.js.map +1 -0
  59. package/graph/package.json +8 -0
  60. package/guards/dist/xstate-guards.cjs.d.ts +1 -1
  61. package/guards/dist/xstate-guards.development.esm.js +1 -1
  62. package/guards/dist/xstate-guards.esm.js +1 -1
  63. package/guards/dist/xstate-guards.umd.min.js +1 -1
  64. package/guards/dist/xstate-guards.umd.min.js.map +1 -1
  65. package/package.json +22 -6
@@ -0,0 +1,901 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var actors_dist_xstateActors = require('../../actors/dist/xstate-actors.cjs.js');
6
+ var StateMachine = require('../../dist/StateMachine-1cda96d3.cjs.js');
7
+ var guards_dist_xstateGuards = require('../../dist/raise-5872b9e8.cjs.js');
8
+ require('../../dev/dist/xstate-dev.cjs.js');
9
+ require('../../dist/assign-e9c344ea.cjs.js');
10
+
11
+ function simpleStringify(value) {
12
+ return JSON.stringify(value);
13
+ }
14
+ function formatPathTestResult(path, testPathResult, options) {
15
+ const resolvedOptions = {
16
+ formatColor: (_color, string) => string,
17
+ serializeState: simpleStringify,
18
+ serializeEvent: simpleStringify,
19
+ ...options
20
+ };
21
+ const {
22
+ formatColor,
23
+ serializeState,
24
+ serializeEvent
25
+ } = resolvedOptions;
26
+ const {
27
+ state
28
+ } = path;
29
+ const targetStateString = serializeState(state, path.steps.length ? path.steps[path.steps.length - 1].event : undefined);
30
+ let errMessage = '';
31
+ let hasFailed = false;
32
+ errMessage += '\nPath:\n' + testPathResult.steps.map((s, i, steps) => {
33
+ const stateString = serializeState(s.step.state, i > 0 ? steps[i - 1].step.event : undefined);
34
+ const eventString = serializeEvent(s.step.event);
35
+ const stateResult = `\tState: ${hasFailed ? formatColor('gray', stateString) : s.state.error ? (hasFailed = true, formatColor('redBright', stateString)) : formatColor('greenBright', stateString)}`;
36
+ const eventResult = `\tEvent: ${hasFailed ? formatColor('gray', eventString) : s.event.error ? (hasFailed = true, formatColor('red', eventString)) : formatColor('green', eventString)}`;
37
+ return [stateResult, eventResult].join('\n');
38
+ }).concat(`\tState: ${hasFailed ? formatColor('gray', targetStateString) : testPathResult.state.error ? formatColor('red', targetStateString) : formatColor('green', targetStateString)}`).join('\n\n');
39
+ return errMessage;
40
+ }
41
+ function getDescription(snapshot) {
42
+ const contextString = !Object.keys(snapshot.context).length ? '' : `(${JSON.stringify(snapshot.context)})`;
43
+ const stateStrings = snapshot._nodes.filter(sn => sn.type === 'atomic' || sn.type === 'final').map(({
44
+ id,
45
+ path
46
+ }) => {
47
+ const meta = snapshot.getMeta()[id];
48
+ if (!meta) {
49
+ return `"${path.join('.')}"`;
50
+ }
51
+ const {
52
+ description
53
+ } = meta;
54
+ if (typeof description === 'function') {
55
+ return description(snapshot);
56
+ }
57
+ return description ? `"${description}"` : JSON.stringify(snapshot.value);
58
+ });
59
+ return `state${stateStrings.length === 1 ? '' : 's'} ` + stateStrings.join(', ') + ` ${contextString}`.trim();
60
+ }
61
+
62
+ /**
63
+ * Deduplicates your paths so that A -> B is not executed separately to A -> B
64
+ * -> C
65
+ */
66
+ const deduplicatePaths = (paths, serializeEvent = simpleStringify) => {
67
+ /** Put all paths on the same level so we can dedup them */
68
+ const allPathsWithEventSequence = [];
69
+ paths.forEach(path => {
70
+ allPathsWithEventSequence.push({
71
+ path,
72
+ eventSequence: path.steps.map(step => serializeEvent(step.event))
73
+ });
74
+ });
75
+
76
+ // Sort by path length, descending
77
+ allPathsWithEventSequence.sort((a, z) => z.path.steps.length - a.path.steps.length);
78
+ const superpathsWithEventSequence = [];
79
+
80
+ /** Filter out the paths that are subpaths of superpaths */
81
+ pathLoop: for (const pathWithEventSequence of allPathsWithEventSequence) {
82
+ // Check each existing superpath to see if the path is a subpath of it
83
+ superpathLoop: for (const superpathWithEventSequence of superpathsWithEventSequence) {
84
+ // eslint-disable-next-line @typescript-eslint/no-for-in-array
85
+ for (const i in pathWithEventSequence.eventSequence) {
86
+ // Check event sequence to determine if path is subpath, e.g.:
87
+ //
88
+ // This will short-circuit the check
89
+ // ['a', 'b', 'c', 'd'] (superpath)
90
+ // ['a', 'b', 'x'] (path)
91
+ //
92
+ // This will not short-circuit; path is subpath
93
+ // ['a', 'b', 'c', 'd'] (superpath)
94
+ // ['a', 'b', 'c'] (path)
95
+ if (pathWithEventSequence.eventSequence[i] !== superpathWithEventSequence.eventSequence[i]) {
96
+ // If the path is different from the superpath,
97
+ // continue to the next superpath
98
+ continue superpathLoop;
99
+ }
100
+ }
101
+
102
+ // If we reached here, path is subpath of superpath
103
+ // Continue & do not add path to superpaths
104
+ continue pathLoop;
105
+ }
106
+
107
+ // If we reached here, path is not a subpath of any existing superpaths
108
+ // So add it to the superpaths
109
+ superpathsWithEventSequence.push(pathWithEventSequence);
110
+ }
111
+ return superpathsWithEventSequence.map(path => path.path);
112
+ };
113
+
114
+ const createShortestPathsGen = () => (logic, defaultOptions) => {
115
+ const paths = getShortestPaths(logic, defaultOptions);
116
+ return paths;
117
+ };
118
+ const createSimplePathsGen = () => (logic, defaultOptions) => {
119
+ const paths = getSimplePaths(logic, defaultOptions);
120
+ return paths;
121
+ };
122
+
123
+ const validateState = state => {
124
+ if (state.invoke.length > 0) {
125
+ throw new Error('Invocations on test machines are not supported');
126
+ }
127
+ if (state.after.length > 0) {
128
+ throw new Error('After events on test machines are not supported');
129
+ }
130
+ // TODO: this doesn't account for always transitions
131
+ [...state.entry, ...state.exit, ...[...state.transitions.values()].flatMap(t => t.flatMap(t => t.actions))].forEach(action => {
132
+ // TODO: this doesn't check referenced actions, only the inline ones
133
+ if (typeof action === 'function' && 'resolve' in action && typeof action.delay === 'number') {
134
+ throw new Error('Delayed actions on test machines are not supported');
135
+ }
136
+ });
137
+ for (const child of Object.values(state.states)) {
138
+ validateState(child);
139
+ }
140
+ };
141
+ const validateMachine = machine => {
142
+ validateState(machine.root);
143
+ };
144
+
145
+ /**
146
+ * Creates a test model that represents an abstract model of a system under test
147
+ * (SUT).
148
+ *
149
+ * The test model is used to generate test paths, which are used to verify that
150
+ * states in the model are reachable in the SUT.
151
+ */
152
+ class TestModel {
153
+ getDefaultOptions() {
154
+ return {
155
+ serializeState: state => simpleStringify(state),
156
+ serializeEvent: event => simpleStringify(event),
157
+ // For non-state-machine test models, we cannot identify
158
+ // separate transitions, so just use event type
159
+ serializeTransition: (state, event) => `${simpleStringify(state)}|${event?.type}`,
160
+ events: [],
161
+ stateMatcher: (_, stateKey) => stateKey === '*',
162
+ logger: {
163
+ log: console.log.bind(console),
164
+ error: console.error.bind(console)
165
+ }
166
+ };
167
+ }
168
+ constructor(testLogic, options) {
169
+ this.testLogic = testLogic;
170
+ this.options = void 0;
171
+ this.defaultTraversalOptions = void 0;
172
+ this._toTestPath = statePath => {
173
+ function formatEvent(event) {
174
+ const {
175
+ type,
176
+ ...other
177
+ } = event;
178
+ const propertyString = Object.keys(other).length ? ` (${JSON.stringify(other)})` : '';
179
+ return `${type}${propertyString}`;
180
+ }
181
+ const eventsString = statePath.steps.map(s => formatEvent(s.event)).join(' → ');
182
+ return {
183
+ ...statePath,
184
+ test: params => this.testPath(statePath, params),
185
+ description: guards_dist_xstateGuards.isMachineSnapshot(statePath.state) ? `Reaches ${getDescription(statePath.state).trim()}: ${eventsString}` : JSON.stringify(statePath.state)
186
+ };
187
+ };
188
+ this.options = {
189
+ ...this.getDefaultOptions(),
190
+ ...options
191
+ };
192
+ }
193
+ getPaths(pathGenerator, options) {
194
+ const allowDuplicatePaths = options?.allowDuplicatePaths ?? false;
195
+ const paths = pathGenerator(this.testLogic, this._resolveOptions(options));
196
+ return (allowDuplicatePaths ? paths : deduplicatePaths(paths)).map(this._toTestPath);
197
+ }
198
+ getShortestPaths(options) {
199
+ return this.getPaths(createShortestPathsGen(), options);
200
+ }
201
+ getShortestPathsFrom(paths, options) {
202
+ const resultPaths = [];
203
+ for (const path of paths) {
204
+ const shortestPaths = this.getShortestPaths({
205
+ ...options,
206
+ fromState: path.state
207
+ });
208
+ for (const shortestPath of shortestPaths) {
209
+ resultPaths.push(this._toTestPath(joinPaths(path, shortestPath)));
210
+ }
211
+ }
212
+ return resultPaths;
213
+ }
214
+ getSimplePaths(options) {
215
+ return this.getPaths(createSimplePathsGen(), options);
216
+ }
217
+ getSimplePathsFrom(paths, options) {
218
+ const resultPaths = [];
219
+ for (const path of paths) {
220
+ const shortestPaths = this.getSimplePaths({
221
+ ...options,
222
+ fromState: path.state
223
+ });
224
+ for (const shortestPath of shortestPaths) {
225
+ resultPaths.push(this._toTestPath(joinPaths(path, shortestPath)));
226
+ }
227
+ }
228
+ return resultPaths;
229
+ }
230
+ getPathsFromEvents(events, options) {
231
+ const paths = getPathsFromEvents(this.testLogic, events, options);
232
+ return paths.map(this._toTestPath);
233
+ }
234
+
235
+ /**
236
+ * An array of adjacencies, which are objects that represent each `state` with
237
+ * the `nextState` given the `event`.
238
+ */
239
+ getAdjacencyMap() {
240
+ const adjMap = getAdjacencyMap(this.testLogic, this.options);
241
+ return adjMap;
242
+ }
243
+ async testPath(path, params, options) {
244
+ const testPathResult = {
245
+ steps: [],
246
+ state: {
247
+ error: null
248
+ }
249
+ };
250
+ try {
251
+ for (const step of path.steps) {
252
+ const testStepResult = {
253
+ step,
254
+ state: {
255
+ error: null
256
+ },
257
+ event: {
258
+ error: null
259
+ }
260
+ };
261
+ testPathResult.steps.push(testStepResult);
262
+ try {
263
+ await this.testTransition(params, step);
264
+ } catch (err) {
265
+ testStepResult.event.error = err;
266
+ throw err;
267
+ }
268
+ try {
269
+ await this.testState(params, step.state, options);
270
+ } catch (err) {
271
+ testStepResult.state.error = err;
272
+ throw err;
273
+ }
274
+ }
275
+ } catch (err) {
276
+ // TODO: make option
277
+ err.message += formatPathTestResult(path, testPathResult, this.options);
278
+ throw err;
279
+ }
280
+ return testPathResult;
281
+ }
282
+ async testState(params, state, options) {
283
+ const resolvedOptions = this._resolveOptions(options);
284
+ const stateTestKeys = this._getStateTestKeys(params, state, resolvedOptions);
285
+ for (const stateTestKey of stateTestKeys) {
286
+ await params.states?.[stateTestKey](state);
287
+ }
288
+ }
289
+ _getStateTestKeys(params, state, resolvedOptions) {
290
+ const states = params.states || {};
291
+ const stateTestKeys = Object.keys(states).filter(stateKey => {
292
+ return resolvedOptions.stateMatcher(state, stateKey);
293
+ });
294
+
295
+ // Fallthrough state tests
296
+ if (!stateTestKeys.length && '*' in states) {
297
+ stateTestKeys.push('*');
298
+ }
299
+ return stateTestKeys;
300
+ }
301
+ _getEventExec(params, step) {
302
+ const eventExec = params.events?.[step.event.type];
303
+ return eventExec;
304
+ }
305
+ async testTransition(params, step) {
306
+ const eventExec = this._getEventExec(params, step);
307
+ await eventExec?.(step);
308
+ }
309
+ _resolveOptions(options) {
310
+ return {
311
+ ...this.defaultTraversalOptions,
312
+ ...this.options,
313
+ ...options
314
+ };
315
+ }
316
+ }
317
+ function stateValuesEqual(a, b) {
318
+ if (a === b) {
319
+ return true;
320
+ }
321
+ if (a === undefined || b === undefined) {
322
+ return false;
323
+ }
324
+ if (typeof a === 'string' || typeof b === 'string') {
325
+ return a === b;
326
+ }
327
+ const aKeys = Object.keys(a);
328
+ const bKeys = Object.keys(b);
329
+ return aKeys.length === bKeys.length && aKeys.every(key => stateValuesEqual(a[key], b[key]));
330
+ }
331
+ function serializeMachineTransition(snapshot, event, previousSnapshot, {
332
+ serializeEvent
333
+ }) {
334
+ // TODO: the stateValuesEqual check here is very likely not exactly correct
335
+ // but I'm not sure what the correct check is and what this is trying to do
336
+ if (!event || previousSnapshot && stateValuesEqual(previousSnapshot.value, snapshot.value)) {
337
+ return '';
338
+ }
339
+ const prevStateString = previousSnapshot ? ` from ${simpleStringify(previousSnapshot.value)}` : '';
340
+ return ` via ${serializeEvent(event)}${prevStateString}`;
341
+ }
342
+
343
+ /**
344
+ * Creates a test model that represents an abstract model of a system under test
345
+ * (SUT).
346
+ *
347
+ * The test model is used to generate test paths, which are used to verify that
348
+ * states in the `machine` are reachable in the SUT.
349
+ *
350
+ * @example
351
+ *
352
+ * ```js
353
+ * const toggleModel = createModel(toggleMachine).withEvents({
354
+ * TOGGLE: {
355
+ * exec: async (page) => {
356
+ * await page.click('input');
357
+ * }
358
+ * }
359
+ * });
360
+ * ```
361
+ *
362
+ * @param machine The state machine used to represent the abstract model.
363
+ * @param options Options for the created test model:
364
+ *
365
+ * - `events`: an object mapping string event types (e.g., `SUBMIT`) to an event
366
+ * test config (e.g., `{exec: () => {...}, cases: [...]}`)
367
+ */
368
+ function createTestModel(machine, options) {
369
+ validateMachine(machine);
370
+ const serializeEvent = options?.serializeEvent ?? simpleStringify;
371
+ const serializeTransition = options?.serializeTransition ?? serializeMachineTransition;
372
+ const {
373
+ events: getEvents,
374
+ ...otherOptions
375
+ } = options ?? {};
376
+ const testModel = new TestModel(machine, {
377
+ serializeState: (state, event, prevState) => {
378
+ // Only consider the `state` if `serializeTransition()` is opted out (empty string)
379
+ return `${serializeSnapshot(state)}${serializeTransition(state, event, prevState, {
380
+ serializeEvent
381
+ })}`;
382
+ },
383
+ stateMatcher: (state, key) => {
384
+ return key.startsWith('#') ? state._nodes.includes(machine.getStateNodeById(key)) : state.matches(key);
385
+ },
386
+ events: state => {
387
+ const events = typeof getEvents === 'function' ? getEvents(state) : getEvents ?? [];
388
+ return guards_dist_xstateGuards.getAllOwnEventDescriptors(state).flatMap(eventType => {
389
+ if (events.some(e => e.type === eventType)) {
390
+ return events.filter(e => e.type === eventType);
391
+ }
392
+ return [{
393
+ type: eventType
394
+ }]; // TODO: fix types
395
+ });
396
+ },
397
+ ...otherOptions
398
+ });
399
+ return testModel;
400
+ }
401
+
402
+ function createMockActorScope() {
403
+ const emptyActor = actors_dist_xstateActors.createEmptyActor();
404
+ return {
405
+ self: emptyActor,
406
+ logger: console.log,
407
+ id: '',
408
+ sessionId: Math.random().toString(32).slice(2),
409
+ defer: () => {},
410
+ system: emptyActor.system,
411
+ // TODO: mock system?
412
+ stopChild: () => {},
413
+ emit: () => {},
414
+ actionExecutor: () => {}
415
+ };
416
+ }
417
+
418
+ /**
419
+ * Returns all state nodes of the given `node`.
420
+ *
421
+ * @param stateNode State node to recursively get child state nodes from
422
+ */
423
+ function getStateNodes(stateNode) {
424
+ const {
425
+ states
426
+ } = stateNode;
427
+ const nodes = Object.keys(states).reduce((accNodes, stateKey) => {
428
+ const childStateNode = states[stateKey];
429
+ const childStateNodes = getStateNodes(childStateNode);
430
+ accNodes.push(childStateNode, ...childStateNodes);
431
+ return accNodes;
432
+ }, []);
433
+ return nodes;
434
+ }
435
+ function getChildren(stateNode) {
436
+ if (!stateNode.states) {
437
+ return [];
438
+ }
439
+ const children = Object.keys(stateNode.states).map(key => {
440
+ return stateNode.states[key];
441
+ });
442
+ return children;
443
+ }
444
+ function serializeSnapshot(snapshot) {
445
+ const {
446
+ value,
447
+ context
448
+ } = snapshot;
449
+ return JSON.stringify({
450
+ value,
451
+ context: Object.keys(context ?? {}).length ? context : undefined
452
+ });
453
+ }
454
+ function serializeEvent(event) {
455
+ return JSON.stringify(event);
456
+ }
457
+ function createDefaultMachineOptions(machine, options) {
458
+ const {
459
+ events: getEvents,
460
+ ...otherOptions
461
+ } = options ?? {};
462
+ const traversalOptions = {
463
+ serializeState: serializeSnapshot,
464
+ serializeEvent,
465
+ events: state => {
466
+ const events = typeof getEvents === 'function' ? getEvents(state) : getEvents ?? [];
467
+ return guards_dist_xstateGuards.getAllOwnEventDescriptors(state).flatMap(type => {
468
+ const matchingEvents = events.filter(ev => ev.type === type);
469
+ if (matchingEvents.length) {
470
+ return matchingEvents;
471
+ }
472
+ return [{
473
+ type
474
+ }];
475
+ });
476
+ },
477
+ fromState: machine.getInitialSnapshot(createMockActorScope(), options?.input),
478
+ ...otherOptions
479
+ };
480
+ return traversalOptions;
481
+ }
482
+ function createDefaultLogicOptions() {
483
+ return {
484
+ serializeState: state => JSON.stringify(state),
485
+ serializeEvent
486
+ };
487
+ }
488
+ function toDirectedGraph(stateMachine) {
489
+ const stateNode = stateMachine instanceof StateMachine.StateMachine ? stateMachine.root : stateMachine; // TODO: accept only machines
490
+
491
+ const edges = [...stateNode.transitions.values()].flat().flatMap((t, transitionIndex) => {
492
+ const targets = t.target ? t.target : [stateNode];
493
+ return targets.map((target, targetIndex) => {
494
+ const edge = {
495
+ id: `${stateNode.id}:${transitionIndex}:${targetIndex}`,
496
+ source: stateNode,
497
+ target: target,
498
+ transition: t,
499
+ label: {
500
+ text: t.eventType,
501
+ toJSON: () => ({
502
+ text: t.eventType
503
+ })
504
+ },
505
+ toJSON: () => {
506
+ const {
507
+ label
508
+ } = edge;
509
+ return {
510
+ source: stateNode.id,
511
+ target: target.id,
512
+ label
513
+ };
514
+ }
515
+ };
516
+ return edge;
517
+ });
518
+ });
519
+ const graph = {
520
+ id: stateNode.id,
521
+ stateNode: stateNode,
522
+ children: getChildren(stateNode).map(toDirectedGraph),
523
+ edges,
524
+ toJSON: () => {
525
+ const {
526
+ id,
527
+ children,
528
+ edges: graphEdges
529
+ } = graph;
530
+ return {
531
+ id,
532
+ children,
533
+ edges: graphEdges
534
+ };
535
+ }
536
+ };
537
+ return graph;
538
+ }
539
+ function isMachineLogic(logic) {
540
+ return 'getStateNodeById' in logic;
541
+ }
542
+ function resolveTraversalOptions(logic, traversalOptions, defaultOptions) {
543
+ const resolvedDefaultOptions = defaultOptions ?? (isMachineLogic(logic) ? createDefaultMachineOptions(logic, traversalOptions) : undefined);
544
+ const serializeState = traversalOptions?.serializeState ?? resolvedDefaultOptions?.serializeState ?? (state => JSON.stringify(state));
545
+ const traversalConfig = {
546
+ serializeState,
547
+ serializeEvent,
548
+ events: [],
549
+ limit: Infinity,
550
+ fromState: undefined,
551
+ toState: undefined,
552
+ // Traversal should not continue past the `toState` predicate
553
+ // since the target state has already been reached at that point
554
+ stopWhen: traversalOptions?.toState,
555
+ ...resolvedDefaultOptions,
556
+ ...traversalOptions
557
+ };
558
+ return traversalConfig;
559
+ }
560
+ function joinPaths(headPath, tailPath) {
561
+ const secondPathSource = tailPath.steps[0].state;
562
+ if (secondPathSource !== headPath.state) {
563
+ throw new Error(`Paths cannot be joined`);
564
+ }
565
+ return {
566
+ state: tailPath.state,
567
+ // e.g. [A, B, C] + [C, D, E] = [A, B, C, D, E]
568
+ steps: headPath.steps.concat(tailPath.steps.slice(1)),
569
+ weight: headPath.weight + tailPath.weight
570
+ };
571
+ }
572
+
573
+ function getAdjacencyMap(logic, options) {
574
+ const {
575
+ transition
576
+ } = logic;
577
+ const {
578
+ serializeEvent,
579
+ serializeState,
580
+ events: getEvents,
581
+ limit,
582
+ fromState: customFromState,
583
+ stopWhen
584
+ } = resolveTraversalOptions(logic, options);
585
+ const actorScope = createMockActorScope();
586
+ const fromState = customFromState ?? logic.getInitialSnapshot(actorScope,
587
+ // TODO: fix this
588
+ options.input);
589
+ const adj = {};
590
+ let iterations = 0;
591
+ const queue = [{
592
+ nextState: fromState,
593
+ event: undefined,
594
+ prevState: undefined
595
+ }];
596
+ const stateMap = new Map();
597
+ while (queue.length) {
598
+ const {
599
+ nextState: state,
600
+ event,
601
+ prevState
602
+ } = queue.shift();
603
+ if (iterations++ > limit) {
604
+ throw new Error('Traversal limit exceeded');
605
+ }
606
+ const serializedState = serializeState(state, event, prevState);
607
+ if (adj[serializedState]) {
608
+ continue;
609
+ }
610
+ stateMap.set(serializedState, state);
611
+ adj[serializedState] = {
612
+ state,
613
+ transitions: {}
614
+ };
615
+ if (stopWhen && stopWhen(state)) {
616
+ continue;
617
+ }
618
+ const events = typeof getEvents === 'function' ? getEvents(state) : getEvents;
619
+ for (const nextEvent of events) {
620
+ const nextSnapshot = transition(state, nextEvent, actorScope);
621
+ adj[serializedState].transitions[serializeEvent(nextEvent)] = {
622
+ event: nextEvent,
623
+ state: nextSnapshot
624
+ };
625
+ queue.push({
626
+ nextState: nextSnapshot,
627
+ event: nextEvent,
628
+ prevState: state
629
+ });
630
+ }
631
+ }
632
+ return adj;
633
+ }
634
+ function adjacencyMapToArray(adjMap) {
635
+ const adjList = [];
636
+ for (const adjValue of Object.values(adjMap)) {
637
+ for (const transition of Object.values(adjValue.transitions)) {
638
+ adjList.push({
639
+ state: adjValue.state,
640
+ event: transition.event,
641
+ nextState: transition.state
642
+ });
643
+ }
644
+ }
645
+ return adjList;
646
+ }
647
+
648
+ // TODO: rewrite parts of the algorithm leading to this to make this function obsolete
649
+ function alterPath(path) {
650
+ let steps = [];
651
+ if (!path.steps.length) {
652
+ steps = [{
653
+ state: path.state,
654
+ event: {
655
+ type: 'xstate.init'
656
+ }
657
+ }];
658
+ } else {
659
+ for (let i = 0; i < path.steps.length; i++) {
660
+ const step = path.steps[i];
661
+ steps.push({
662
+ state: step.state,
663
+ event: i === 0 ? {
664
+ type: 'xstate.init'
665
+ } : path.steps[i - 1].event
666
+ });
667
+ }
668
+ steps.push({
669
+ state: path.state,
670
+ event: path.steps[path.steps.length - 1].event
671
+ });
672
+ }
673
+ return {
674
+ ...path,
675
+ steps
676
+ };
677
+ }
678
+
679
+ function isMachine(value) {
680
+ return !!value && '__xstatenode' in value;
681
+ }
682
+ function getPathsFromEvents(logic, events, options) {
683
+ const resolvedOptions = resolveTraversalOptions(logic, {
684
+ events,
685
+ ...options
686
+ }, isMachine(logic) ? createDefaultMachineOptions(logic) : createDefaultLogicOptions());
687
+ const actorScope = createMockActorScope();
688
+ const fromState = resolvedOptions.fromState ?? logic.getInitialSnapshot(actorScope,
689
+ // TODO: fix this
690
+ options?.input);
691
+ const {
692
+ serializeState,
693
+ serializeEvent
694
+ } = resolvedOptions;
695
+ const adjacency = getAdjacencyMap(logic, resolvedOptions);
696
+ const stateMap = new Map();
697
+ const steps = [];
698
+ const serializedFromState = serializeState(fromState, undefined, undefined);
699
+ stateMap.set(serializedFromState, fromState);
700
+ let stateSerial = serializedFromState;
701
+ let state = fromState;
702
+ for (const event of events) {
703
+ steps.push({
704
+ state: stateMap.get(stateSerial),
705
+ event
706
+ });
707
+ const eventSerial = serializeEvent(event);
708
+ const {
709
+ state: nextState,
710
+ event: _nextEvent
711
+ } = adjacency[stateSerial].transitions[eventSerial];
712
+ if (!nextState) {
713
+ throw new Error(`Invalid transition from ${stateSerial} with ${eventSerial}`);
714
+ }
715
+ const prevState = stateMap.get(stateSerial);
716
+ const nextStateSerial = serializeState(nextState, event, prevState);
717
+ stateMap.set(nextStateSerial, nextState);
718
+ stateSerial = nextStateSerial;
719
+ state = nextState;
720
+ }
721
+
722
+ // If it is expected to reach a specific state (`toState`) and that state
723
+ // isn't reached, there are no paths
724
+ if (resolvedOptions.toState && !resolvedOptions.toState(state)) {
725
+ return [];
726
+ }
727
+ return [alterPath({
728
+ state,
729
+ steps,
730
+ weight: steps.length
731
+ })];
732
+ }
733
+
734
+ function getShortestPaths(logic, options) {
735
+ const resolvedOptions = resolveTraversalOptions(logic, options);
736
+ const serializeState = resolvedOptions.serializeState;
737
+ const fromState = resolvedOptions.fromState ?? logic.getInitialSnapshot(createMockActorScope(), options?.input);
738
+ const adjacency = getAdjacencyMap(logic, resolvedOptions);
739
+
740
+ // weight, state, event
741
+ const weightMap = new Map();
742
+ const stateMap = new Map();
743
+ const serializedFromState = serializeState(fromState, undefined, undefined);
744
+ stateMap.set(serializedFromState, fromState);
745
+ weightMap.set(serializedFromState, {
746
+ weight: 0,
747
+ state: undefined,
748
+ event: undefined
749
+ });
750
+ const unvisited = new Set();
751
+ const visited = new Set();
752
+ unvisited.add(serializedFromState);
753
+ for (const serializedState of unvisited) {
754
+ const prevState = stateMap.get(serializedState);
755
+ const {
756
+ weight
757
+ } = weightMap.get(serializedState);
758
+ for (const event of Object.keys(adjacency[serializedState].transitions)) {
759
+ const {
760
+ state: nextState,
761
+ event: eventObject
762
+ } = adjacency[serializedState].transitions[event];
763
+ const nextSerializedState = serializeState(nextState, eventObject, prevState);
764
+ stateMap.set(nextSerializedState, nextState);
765
+ if (!weightMap.has(nextSerializedState)) {
766
+ weightMap.set(nextSerializedState, {
767
+ weight: weight + 1,
768
+ state: serializedState,
769
+ event: eventObject
770
+ });
771
+ } else {
772
+ const {
773
+ weight: nextWeight
774
+ } = weightMap.get(nextSerializedState);
775
+ if (nextWeight > weight + 1) {
776
+ weightMap.set(nextSerializedState, {
777
+ weight: weight + 1,
778
+ state: serializedState,
779
+ event: eventObject
780
+ });
781
+ }
782
+ }
783
+ if (!visited.has(nextSerializedState)) {
784
+ unvisited.add(nextSerializedState);
785
+ }
786
+ }
787
+ visited.add(serializedState);
788
+ unvisited.delete(serializedState);
789
+ }
790
+ const statePlanMap = {};
791
+ const paths = [];
792
+ weightMap.forEach(({
793
+ weight,
794
+ state: fromState,
795
+ event: fromEvent
796
+ }, stateSerial) => {
797
+ const state = stateMap.get(stateSerial);
798
+ const steps = !fromState ? [] : statePlanMap[fromState].paths[0].steps.concat({
799
+ state: stateMap.get(fromState),
800
+ event: fromEvent
801
+ });
802
+ paths.push({
803
+ state,
804
+ steps,
805
+ weight
806
+ });
807
+ statePlanMap[stateSerial] = {
808
+ state,
809
+ paths: [{
810
+ state,
811
+ steps,
812
+ weight
813
+ }]
814
+ };
815
+ });
816
+ if (resolvedOptions.toState) {
817
+ return paths.filter(path => resolvedOptions.toState(path.state)).map(alterPath);
818
+ }
819
+ return paths.map(alterPath);
820
+ }
821
+
822
+ function getSimplePaths(logic, options) {
823
+ const resolvedOptions = resolveTraversalOptions(logic, options);
824
+ const actorScope = createMockActorScope();
825
+ const fromState = resolvedOptions.fromState ?? logic.getInitialSnapshot(actorScope, options?.input);
826
+ const serializeState = resolvedOptions.serializeState;
827
+ const adjacency = getAdjacencyMap(logic, resolvedOptions);
828
+ const stateMap = new Map();
829
+ const visitCtx = {
830
+ vertices: new Set(),
831
+ edges: new Set()
832
+ };
833
+ const steps = [];
834
+ const pathMap = {};
835
+ function util(fromStateSerial, toStateSerial) {
836
+ const fromState = stateMap.get(fromStateSerial);
837
+ visitCtx.vertices.add(fromStateSerial);
838
+ if (fromStateSerial === toStateSerial) {
839
+ if (!pathMap[toStateSerial]) {
840
+ pathMap[toStateSerial] = {
841
+ state: stateMap.get(toStateSerial),
842
+ paths: []
843
+ };
844
+ }
845
+ const toStatePlan = pathMap[toStateSerial];
846
+ const path2 = {
847
+ state: fromState,
848
+ weight: steps.length,
849
+ steps: [...steps]
850
+ };
851
+ toStatePlan.paths.push(path2);
852
+ } else {
853
+ for (const serializedEvent of Object.keys(adjacency[fromStateSerial].transitions)) {
854
+ const {
855
+ state: nextState,
856
+ event: subEvent
857
+ } = adjacency[fromStateSerial].transitions[serializedEvent];
858
+ if (!(serializedEvent in adjacency[fromStateSerial].transitions)) {
859
+ continue;
860
+ }
861
+ const prevState = stateMap.get(fromStateSerial);
862
+ const nextStateSerial = serializeState(nextState, subEvent, prevState);
863
+ stateMap.set(nextStateSerial, nextState);
864
+ if (!visitCtx.vertices.has(nextStateSerial)) {
865
+ visitCtx.edges.add(serializedEvent);
866
+ steps.push({
867
+ state: stateMap.get(fromStateSerial),
868
+ event: subEvent
869
+ });
870
+ util(nextStateSerial, toStateSerial);
871
+ }
872
+ }
873
+ }
874
+ steps.pop();
875
+ visitCtx.vertices.delete(fromStateSerial);
876
+ }
877
+ const fromStateSerial = serializeState(fromState, undefined);
878
+ stateMap.set(fromStateSerial, fromState);
879
+ for (const nextStateSerial of Object.keys(adjacency)) {
880
+ util(fromStateSerial, nextStateSerial);
881
+ }
882
+ const simplePaths = Object.values(pathMap).flatMap(p => p.paths);
883
+ if (resolvedOptions.toState) {
884
+ return simplePaths.filter(path => resolvedOptions.toState(path.state)).map(alterPath);
885
+ }
886
+ return simplePaths.map(alterPath);
887
+ }
888
+
889
+ exports.TestModel = TestModel;
890
+ exports.adjacencyMapToArray = adjacencyMapToArray;
891
+ exports.createShortestPathsGen = createShortestPathsGen;
892
+ exports.createSimplePathsGen = createSimplePathsGen;
893
+ exports.createTestModel = createTestModel;
894
+ exports.getAdjacencyMap = getAdjacencyMap;
895
+ exports.getPathsFromEvents = getPathsFromEvents;
896
+ exports.getShortestPaths = getShortestPaths;
897
+ exports.getSimplePaths = getSimplePaths;
898
+ exports.getStateNodes = getStateNodes;
899
+ exports.joinPaths = joinPaths;
900
+ exports.serializeSnapshot = serializeSnapshot;
901
+ exports.toDirectedGraph = toDirectedGraph;