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