xstate
Version:
Finite State Machines and Statecharts for the Modern Web.
902 lines (876 loc) • 28.6 kB
JavaScript
Object.defineProperty(exports, '__esModule', { value: true });
var actors_dist_xstateActors = require('../../actors/dist/xstate-actors.cjs.js');
var StateMachine = require('../../dist/StateMachine-1cda96d3.cjs.js');
var guards_dist_xstateGuards = require('../../dist/raise-5872b9e8.cjs.js');
require('../../dev/dist/xstate-dev.cjs.js');
require('../../dist/assign-e9c344ea.cjs.js');
function simpleStringify(value) {
return JSON.stringify(value);
}
function formatPathTestResult(path, testPathResult, options) {
const resolvedOptions = {
formatColor: (_color, string) => string,
serializeState: simpleStringify,
serializeEvent: simpleStringify,
...options
};
const {
formatColor,
serializeState,
serializeEvent
} = resolvedOptions;
const {
state
} = path;
const targetStateString = serializeState(state, path.steps.length ? path.steps[path.steps.length - 1].event : undefined);
let errMessage = '';
let hasFailed = false;
errMessage += '\nPath:\n' + testPathResult.steps.map((s, i, steps) => {
const stateString = serializeState(s.step.state, i > 0 ? steps[i - 1].step.event : undefined);
const eventString = serializeEvent(s.step.event);
const stateResult = `\tState: ${hasFailed ? formatColor('gray', stateString) : s.state.error ? (hasFailed = true, formatColor('redBright', stateString)) : formatColor('greenBright', stateString)}`;
const eventResult = `\tEvent: ${hasFailed ? formatColor('gray', eventString) : s.event.error ? (hasFailed = true, formatColor('red', eventString)) : formatColor('green', eventString)}`;
return [stateResult, eventResult].join('\n');
}).concat(`\tState: ${hasFailed ? formatColor('gray', targetStateString) : testPathResult.state.error ? formatColor('red', targetStateString) : formatColor('green', targetStateString)}`).join('\n\n');
return errMessage;
}
function getDescription(snapshot) {
const contextString = !Object.keys(snapshot.context).length ? '' : `(${JSON.stringify(snapshot.context)})`;
const stateStrings = snapshot._nodes.filter(sn => sn.type === 'atomic' || sn.type === 'final').map(({
id,
path
}) => {
const meta = snapshot.getMeta()[id];
if (!meta) {
return `"${path.join('.')}"`;
}
const {
description
} = meta;
if (typeof description === 'function') {
return description(snapshot);
}
return description ? `"${description}"` : JSON.stringify(snapshot.value);
});
return `state${stateStrings.length === 1 ? '' : 's'} ` + stateStrings.join(', ') + ` ${contextString}`.trim();
}
/**
* Deduplicates your paths so that A -> B is not executed separately to A -> B
* -> C
*/
const deduplicatePaths = (paths, serializeEvent = simpleStringify) => {
/** Put all paths on the same level so we can dedup them */
const allPathsWithEventSequence = [];
paths.forEach(path => {
allPathsWithEventSequence.push({
path,
eventSequence: path.steps.map(step => serializeEvent(step.event))
});
});
// Sort by path length, descending
allPathsWithEventSequence.sort((a, z) => z.path.steps.length - a.path.steps.length);
const superpathsWithEventSequence = [];
/** Filter out the paths that are subpaths of superpaths */
pathLoop: for (const pathWithEventSequence of allPathsWithEventSequence) {
// Check each existing superpath to see if the path is a subpath of it
superpathLoop: for (const superpathWithEventSequence of superpathsWithEventSequence) {
// eslint-disable-next-line @typescript-eslint/no-for-in-array
for (const i in pathWithEventSequence.eventSequence) {
// Check event sequence to determine if path is subpath, e.g.:
//
// This will short-circuit the check
// ['a', 'b', 'c', 'd'] (superpath)
// ['a', 'b', 'x'] (path)
//
// This will not short-circuit; path is subpath
// ['a', 'b', 'c', 'd'] (superpath)
// ['a', 'b', 'c'] (path)
if (pathWithEventSequence.eventSequence[i] !== superpathWithEventSequence.eventSequence[i]) {
// If the path is different from the superpath,
// continue to the next superpath
continue superpathLoop;
}
}
// If we reached here, path is subpath of superpath
// Continue & do not add path to superpaths
continue pathLoop;
}
// If we reached here, path is not a subpath of any existing superpaths
// So add it to the superpaths
superpathsWithEventSequence.push(pathWithEventSequence);
}
return superpathsWithEventSequence.map(path => path.path);
};
const createShortestPathsGen = () => (logic, defaultOptions) => {
const paths = getShortestPaths(logic, defaultOptions);
return paths;
};
const createSimplePathsGen = () => (logic, defaultOptions) => {
const paths = getSimplePaths(logic, defaultOptions);
return paths;
};
const validateState = state => {
if (state.invoke.length > 0) {
throw new Error('Invocations on test machines are not supported');
}
if (state.after.length > 0) {
throw new Error('After events on test machines are not supported');
}
// TODO: this doesn't account for always transitions
[...state.entry, ...state.exit, ...[...state.transitions.values()].flatMap(t => t.flatMap(t => t.actions))].forEach(action => {
// TODO: this doesn't check referenced actions, only the inline ones
if (typeof action === 'function' && 'resolve' in action && typeof action.delay === 'number') {
throw new Error('Delayed actions on test machines are not supported');
}
});
for (const child of Object.values(state.states)) {
validateState(child);
}
};
const validateMachine = machine => {
validateState(machine.root);
};
/**
* Creates a test model that represents an abstract model of a system under test
* (SUT).
*
* The test model is used to generate test paths, which are used to verify that
* states in the model are reachable in the SUT.
*/
class TestModel {
getDefaultOptions() {
return {
serializeState: state => simpleStringify(state),
serializeEvent: event => simpleStringify(event),
// For non-state-machine test models, we cannot identify
// separate transitions, so just use event type
serializeTransition: (state, event) => `${simpleStringify(state)}|${event?.type}`,
events: [],
stateMatcher: (_, stateKey) => stateKey === '*',
logger: {
log: console.log.bind(console),
error: console.error.bind(console)
}
};
}
constructor(testLogic, options) {
this.testLogic = testLogic;
this.options = void 0;
this.defaultTraversalOptions = void 0;
this._toTestPath = statePath => {
function formatEvent(event) {
const {
type,
...other
} = event;
const propertyString = Object.keys(other).length ? ` (${JSON.stringify(other)})` : '';
return `${type}${propertyString}`;
}
const eventsString = statePath.steps.map(s => formatEvent(s.event)).join(' → ');
return {
...statePath,
test: params => this.testPath(statePath, params),
description: guards_dist_xstateGuards.isMachineSnapshot(statePath.state) ? `Reaches ${getDescription(statePath.state).trim()}: ${eventsString}` : JSON.stringify(statePath.state)
};
};
this.options = {
...this.getDefaultOptions(),
...options
};
}
getPaths(pathGenerator, options) {
const allowDuplicatePaths = options?.allowDuplicatePaths ?? false;
const paths = pathGenerator(this.testLogic, this._resolveOptions(options));
return (allowDuplicatePaths ? paths : deduplicatePaths(paths)).map(this._toTestPath);
}
getShortestPaths(options) {
return this.getPaths(createShortestPathsGen(), options);
}
getShortestPathsFrom(paths, options) {
const resultPaths = [];
for (const path of paths) {
const shortestPaths = this.getShortestPaths({
...options,
fromState: path.state
});
for (const shortestPath of shortestPaths) {
resultPaths.push(this._toTestPath(joinPaths(path, shortestPath)));
}
}
return resultPaths;
}
getSimplePaths(options) {
return this.getPaths(createSimplePathsGen(), options);
}
getSimplePathsFrom(paths, options) {
const resultPaths = [];
for (const path of paths) {
const shortestPaths = this.getSimplePaths({
...options,
fromState: path.state
});
for (const shortestPath of shortestPaths) {
resultPaths.push(this._toTestPath(joinPaths(path, shortestPath)));
}
}
return resultPaths;
}
getPathsFromEvents(events, options) {
const paths = getPathsFromEvents(this.testLogic, events, options);
return paths.map(this._toTestPath);
}
/**
* An array of adjacencies, which are objects that represent each `state` with
* the `nextState` given the `event`.
*/
getAdjacencyMap() {
const adjMap = getAdjacencyMap(this.testLogic, this.options);
return adjMap;
}
async testPath(path, params, options) {
const testPathResult = {
steps: [],
state: {
error: null
}
};
try {
for (const step of path.steps) {
const testStepResult = {
step,
state: {
error: null
},
event: {
error: null
}
};
testPathResult.steps.push(testStepResult);
try {
await this.testTransition(params, step);
} catch (err) {
testStepResult.event.error = err;
throw err;
}
try {
await this.testState(params, step.state, options);
} catch (err) {
testStepResult.state.error = err;
throw err;
}
}
} catch (err) {
// TODO: make option
err.message += formatPathTestResult(path, testPathResult, this.options);
throw err;
}
return testPathResult;
}
async testState(params, state, options) {
const resolvedOptions = this._resolveOptions(options);
const stateTestKeys = this._getStateTestKeys(params, state, resolvedOptions);
for (const stateTestKey of stateTestKeys) {
await params.states?.[stateTestKey](state);
}
}
_getStateTestKeys(params, state, resolvedOptions) {
const states = params.states || {};
const stateTestKeys = Object.keys(states).filter(stateKey => {
return resolvedOptions.stateMatcher(state, stateKey);
});
// Fallthrough state tests
if (!stateTestKeys.length && '*' in states) {
stateTestKeys.push('*');
}
return stateTestKeys;
}
_getEventExec(params, step) {
const eventExec = params.events?.[step.event.type];
return eventExec;
}
async testTransition(params, step) {
const eventExec = this._getEventExec(params, step);
await eventExec?.(step);
}
_resolveOptions(options) {
return {
...this.defaultTraversalOptions,
...this.options,
...options
};
}
}
function stateValuesEqual(a, b) {
if (a === b) {
return true;
}
if (a === undefined || b === undefined) {
return false;
}
if (typeof a === 'string' || typeof b === 'string') {
return a === b;
}
const aKeys = Object.keys(a);
const bKeys = Object.keys(b);
return aKeys.length === bKeys.length && aKeys.every(key => stateValuesEqual(a[key], b[key]));
}
function serializeMachineTransition(snapshot, event, previousSnapshot, {
serializeEvent
}) {
// TODO: the stateValuesEqual check here is very likely not exactly correct
// but I'm not sure what the correct check is and what this is trying to do
if (!event || previousSnapshot && stateValuesEqual(previousSnapshot.value, snapshot.value)) {
return '';
}
const prevStateString = previousSnapshot ? ` from ${simpleStringify(previousSnapshot.value)}` : '';
return ` via ${serializeEvent(event)}${prevStateString}`;
}
/**
* Creates a test model that represents an abstract model of a system under test
* (SUT).
*
* The test model is used to generate test paths, which are used to verify that
* states in the `machine` are reachable in the SUT.
*
* @example
*
* ```js
* const toggleModel = createModel(toggleMachine).withEvents({
* TOGGLE: {
* exec: async (page) => {
* await page.click('input');
* }
* }
* });
* ```
*
* @param machine The state machine used to represent the abstract model.
* @param options Options for the created test model:
*
* - `events`: an object mapping string event types (e.g., `SUBMIT`) to an event
* test config (e.g., `{exec: () => {...}, cases: [...]}`)
*/
function createTestModel(machine, options) {
validateMachine(machine);
const serializeEvent = options?.serializeEvent ?? simpleStringify;
const serializeTransition = options?.serializeTransition ?? serializeMachineTransition;
const {
events: getEvents,
...otherOptions
} = options ?? {};
const testModel = new TestModel(machine, {
serializeState: (state, event, prevState) => {
// Only consider the `state` if `serializeTransition()` is opted out (empty string)
return `${serializeSnapshot(state)}${serializeTransition(state, event, prevState, {
serializeEvent
})}`;
},
stateMatcher: (state, key) => {
return key.startsWith('#') ? state._nodes.includes(machine.getStateNodeById(key)) : state.matches(key);
},
events: state => {
const events = typeof getEvents === 'function' ? getEvents(state) : getEvents ?? [];
return guards_dist_xstateGuards.getAllOwnEventDescriptors(state).flatMap(eventType => {
if (events.some(e => e.type === eventType)) {
return events.filter(e => e.type === eventType);
}
return [{
type: eventType
}]; // TODO: fix types
});
},
...otherOptions
});
return testModel;
}
function createMockActorScope() {
const emptyActor = actors_dist_xstateActors.createEmptyActor();
return {
self: emptyActor,
logger: console.log,
id: '',
sessionId: Math.random().toString(32).slice(2),
defer: () => {},
system: emptyActor.system,
// TODO: mock system?
stopChild: () => {},
emit: () => {},
actionExecutor: () => {}
};
}
/**
* Returns all state nodes of the given `node`.
*
* @param stateNode State node to recursively get child state nodes from
*/
function getStateNodes(stateNode) {
const {
states
} = stateNode;
const nodes = Object.keys(states).reduce((accNodes, stateKey) => {
const childStateNode = states[stateKey];
const childStateNodes = getStateNodes(childStateNode);
accNodes.push(childStateNode, ...childStateNodes);
return accNodes;
}, []);
return nodes;
}
function getChildren(stateNode) {
if (!stateNode.states) {
return [];
}
const children = Object.keys(stateNode.states).map(key => {
return stateNode.states[key];
});
return children;
}
function serializeSnapshot(snapshot) {
const {
value,
context
} = snapshot;
return JSON.stringify({
value,
context: Object.keys(context ?? {}).length ? context : undefined
});
}
function serializeEvent(event) {
return JSON.stringify(event);
}
function createDefaultMachineOptions(machine, options) {
const {
events: getEvents,
...otherOptions
} = options ?? {};
const traversalOptions = {
serializeState: serializeSnapshot,
serializeEvent,
events: state => {
const events = typeof getEvents === 'function' ? getEvents(state) : getEvents ?? [];
return guards_dist_xstateGuards.getAllOwnEventDescriptors(state).flatMap(type => {
const matchingEvents = events.filter(ev => ev.type === type);
if (matchingEvents.length) {
return matchingEvents;
}
return [{
type
}];
});
},
fromState: machine.getInitialSnapshot(createMockActorScope(), options?.input),
...otherOptions
};
return traversalOptions;
}
function createDefaultLogicOptions() {
return {
serializeState: state => JSON.stringify(state),
serializeEvent
};
}
function toDirectedGraph(stateMachine) {
const stateNode = stateMachine instanceof StateMachine.StateMachine ? stateMachine.root : stateMachine; // TODO: accept only machines
const edges = [...stateNode.transitions.values()].flat().flatMap((t, transitionIndex) => {
const targets = t.target ? t.target : [stateNode];
return targets.map((target, targetIndex) => {
const edge = {
id: `${stateNode.id}:${transitionIndex}:${targetIndex}`,
source: stateNode,
target: target,
transition: t,
label: {
text: t.eventType,
toJSON: () => ({
text: t.eventType
})
},
toJSON: () => {
const {
label
} = edge;
return {
source: stateNode.id,
target: target.id,
label
};
}
};
return edge;
});
});
const graph = {
id: stateNode.id,
stateNode: stateNode,
children: getChildren(stateNode).map(toDirectedGraph),
edges,
toJSON: () => {
const {
id,
children,
edges: graphEdges
} = graph;
return {
id,
children,
edges: graphEdges
};
}
};
return graph;
}
function isMachineLogic(logic) {
return 'getStateNodeById' in logic;
}
function resolveTraversalOptions(logic, traversalOptions, defaultOptions) {
const resolvedDefaultOptions = defaultOptions ?? (isMachineLogic(logic) ? createDefaultMachineOptions(logic, traversalOptions) : undefined);
const serializeState = traversalOptions?.serializeState ?? resolvedDefaultOptions?.serializeState ?? (state => JSON.stringify(state));
const traversalConfig = {
serializeState,
serializeEvent,
events: [],
limit: Infinity,
fromState: undefined,
toState: undefined,
// Traversal should not continue past the `toState` predicate
// since the target state has already been reached at that point
stopWhen: traversalOptions?.toState,
...resolvedDefaultOptions,
...traversalOptions
};
return traversalConfig;
}
function joinPaths(headPath, tailPath) {
const secondPathSource = tailPath.steps[0].state;
if (secondPathSource !== headPath.state) {
throw new Error(`Paths cannot be joined`);
}
return {
state: tailPath.state,
// e.g. [A, B, C] + [C, D, E] = [A, B, C, D, E]
steps: headPath.steps.concat(tailPath.steps.slice(1)),
weight: headPath.weight + tailPath.weight
};
}
function getAdjacencyMap(logic, options) {
const {
transition
} = logic;
const {
serializeEvent,
serializeState,
events: getEvents,
limit,
fromState: customFromState,
stopWhen
} = resolveTraversalOptions(logic, options);
const actorScope = createMockActorScope();
const fromState = customFromState ?? logic.getInitialSnapshot(actorScope,
// TODO: fix this
options.input);
const adj = {};
let iterations = 0;
const queue = [{
nextState: fromState,
event: undefined,
prevState: undefined
}];
const stateMap = new Map();
while (queue.length) {
const {
nextState: state,
event,
prevState
} = queue.shift();
if (iterations++ > limit) {
throw new Error('Traversal limit exceeded');
}
const serializedState = serializeState(state, event, prevState);
if (adj[serializedState]) {
continue;
}
stateMap.set(serializedState, state);
adj[serializedState] = {
state,
transitions: {}
};
if (stopWhen && stopWhen(state)) {
continue;
}
const events = typeof getEvents === 'function' ? getEvents(state) : getEvents;
for (const nextEvent of events) {
const nextSnapshot = transition(state, nextEvent, actorScope);
adj[serializedState].transitions[serializeEvent(nextEvent)] = {
event: nextEvent,
state: nextSnapshot
};
queue.push({
nextState: nextSnapshot,
event: nextEvent,
prevState: state
});
}
}
return adj;
}
function adjacencyMapToArray(adjMap) {
const adjList = [];
for (const adjValue of Object.values(adjMap)) {
for (const transition of Object.values(adjValue.transitions)) {
adjList.push({
state: adjValue.state,
event: transition.event,
nextState: transition.state
});
}
}
return adjList;
}
// TODO: rewrite parts of the algorithm leading to this to make this function obsolete
function alterPath(path) {
let steps = [];
if (!path.steps.length) {
steps = [{
state: path.state,
event: {
type: 'xstate.init'
}
}];
} else {
for (let i = 0; i < path.steps.length; i++) {
const step = path.steps[i];
steps.push({
state: step.state,
event: i === 0 ? {
type: 'xstate.init'
} : path.steps[i - 1].event
});
}
steps.push({
state: path.state,
event: path.steps[path.steps.length - 1].event
});
}
return {
...path,
steps
};
}
function isMachine(value) {
return !!value && '__xstatenode' in value;
}
function getPathsFromEvents(logic, events, options) {
const resolvedOptions = resolveTraversalOptions(logic, {
events,
...options
}, isMachine(logic) ? createDefaultMachineOptions(logic) : createDefaultLogicOptions());
const actorScope = createMockActorScope();
const fromState = resolvedOptions.fromState ?? logic.getInitialSnapshot(actorScope,
// TODO: fix this
options?.input);
const {
serializeState,
serializeEvent
} = resolvedOptions;
const adjacency = getAdjacencyMap(logic, resolvedOptions);
const stateMap = new Map();
const steps = [];
const serializedFromState = serializeState(fromState, undefined, undefined);
stateMap.set(serializedFromState, fromState);
let stateSerial = serializedFromState;
let state = fromState;
for (const event of events) {
steps.push({
state: stateMap.get(stateSerial),
event
});
const eventSerial = serializeEvent(event);
const {
state: nextState,
event: _nextEvent
} = adjacency[stateSerial].transitions[eventSerial];
if (!nextState) {
throw new Error(`Invalid transition from ${stateSerial} with ${eventSerial}`);
}
const prevState = stateMap.get(stateSerial);
const nextStateSerial = serializeState(nextState, event, prevState);
stateMap.set(nextStateSerial, nextState);
stateSerial = nextStateSerial;
state = nextState;
}
// If it is expected to reach a specific state (`toState`) and that state
// isn't reached, there are no paths
if (resolvedOptions.toState && !resolvedOptions.toState(state)) {
return [];
}
return [alterPath({
state,
steps,
weight: steps.length
})];
}
function getShortestPaths(logic, options) {
const resolvedOptions = resolveTraversalOptions(logic, options);
const serializeState = resolvedOptions.serializeState;
const fromState = resolvedOptions.fromState ?? logic.getInitialSnapshot(createMockActorScope(), options?.input);
const adjacency = getAdjacencyMap(logic, resolvedOptions);
// weight, state, event
const weightMap = new Map();
const stateMap = new Map();
const serializedFromState = serializeState(fromState, undefined, undefined);
stateMap.set(serializedFromState, fromState);
weightMap.set(serializedFromState, {
weight: 0,
state: undefined,
event: undefined
});
const unvisited = new Set();
const visited = new Set();
unvisited.add(serializedFromState);
for (const serializedState of unvisited) {
const prevState = stateMap.get(serializedState);
const {
weight
} = weightMap.get(serializedState);
for (const event of Object.keys(adjacency[serializedState].transitions)) {
const {
state: nextState,
event: eventObject
} = adjacency[serializedState].transitions[event];
const nextSerializedState = serializeState(nextState, eventObject, prevState);
stateMap.set(nextSerializedState, nextState);
if (!weightMap.has(nextSerializedState)) {
weightMap.set(nextSerializedState, {
weight: weight + 1,
state: serializedState,
event: eventObject
});
} else {
const {
weight: nextWeight
} = weightMap.get(nextSerializedState);
if (nextWeight > weight + 1) {
weightMap.set(nextSerializedState, {
weight: weight + 1,
state: serializedState,
event: eventObject
});
}
}
if (!visited.has(nextSerializedState)) {
unvisited.add(nextSerializedState);
}
}
visited.add(serializedState);
unvisited.delete(serializedState);
}
const statePlanMap = {};
const paths = [];
weightMap.forEach(({
weight,
state: fromState,
event: fromEvent
}, stateSerial) => {
const state = stateMap.get(stateSerial);
const steps = !fromState ? [] : statePlanMap[fromState].paths[0].steps.concat({
state: stateMap.get(fromState),
event: fromEvent
});
paths.push({
state,
steps,
weight
});
statePlanMap[stateSerial] = {
state,
paths: [{
state,
steps,
weight
}]
};
});
if (resolvedOptions.toState) {
return paths.filter(path => resolvedOptions.toState(path.state)).map(alterPath);
}
return paths.map(alterPath);
}
function getSimplePaths(logic, options) {
const resolvedOptions = resolveTraversalOptions(logic, options);
const actorScope = createMockActorScope();
const fromState = resolvedOptions.fromState ?? logic.getInitialSnapshot(actorScope, options?.input);
const serializeState = resolvedOptions.serializeState;
const adjacency = getAdjacencyMap(logic, resolvedOptions);
const stateMap = new Map();
const visitCtx = {
vertices: new Set(),
edges: new Set()
};
const steps = [];
const pathMap = {};
function util(fromStateSerial, toStateSerial) {
const fromState = stateMap.get(fromStateSerial);
visitCtx.vertices.add(fromStateSerial);
if (fromStateSerial === toStateSerial) {
if (!pathMap[toStateSerial]) {
pathMap[toStateSerial] = {
state: stateMap.get(toStateSerial),
paths: []
};
}
const toStatePlan = pathMap[toStateSerial];
const path2 = {
state: fromState,
weight: steps.length,
steps: [...steps]
};
toStatePlan.paths.push(path2);
} else {
for (const serializedEvent of Object.keys(adjacency[fromStateSerial].transitions)) {
const {
state: nextState,
event: subEvent
} = adjacency[fromStateSerial].transitions[serializedEvent];
if (!(serializedEvent in adjacency[fromStateSerial].transitions)) {
continue;
}
const prevState = stateMap.get(fromStateSerial);
const nextStateSerial = serializeState(nextState, subEvent, prevState);
stateMap.set(nextStateSerial, nextState);
if (!visitCtx.vertices.has(nextStateSerial)) {
visitCtx.edges.add(serializedEvent);
steps.push({
state: stateMap.get(fromStateSerial),
event: subEvent
});
util(nextStateSerial, toStateSerial);
}
}
}
steps.pop();
visitCtx.vertices.delete(fromStateSerial);
}
const fromStateSerial = serializeState(fromState, undefined);
stateMap.set(fromStateSerial, fromState);
for (const nextStateSerial of Object.keys(adjacency)) {
util(fromStateSerial, nextStateSerial);
}
const simplePaths = Object.values(pathMap).flatMap(p => p.paths);
if (resolvedOptions.toState) {
return simplePaths.filter(path => resolvedOptions.toState(path.state)).map(alterPath);
}
return simplePaths.map(alterPath);
}
exports.TestModel = TestModel;
exports.adjacencyMapToArray = adjacencyMapToArray;
exports.createShortestPathsGen = createShortestPathsGen;
exports.createSimplePathsGen = createSimplePathsGen;
exports.createTestModel = createTestModel;
exports.getAdjacencyMap = getAdjacencyMap;
exports.getPathsFromEvents = getPathsFromEvents;
exports.getShortestPaths = getShortestPaths;
exports.getSimplePaths = getSimplePaths;
exports.getStateNodes = getStateNodes;
exports.joinPaths = joinPaths;
exports.serializeSnapshot = serializeSnapshot;
exports.toDirectedGraph = toDirectedGraph;
;