Skip to content

Commit 01ba71e

Browse files
fix(overmind): fix order of entry exists in statecharts
1 parent 93d3e4a commit 01ba71e

File tree

2 files changed

+80
-86
lines changed

2 files changed

+80
-86
lines changed

packages/node_modules/overmind/src/config/statecharts.test.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ describe('Statecharts', () => {
115115
}))
116116

117117
expect(instance.state.states).toEqual([['id1', 'foo']])
118-
expect(instance.state.actions).toEqual({ increaseCount: true })
118+
expect(instance.state.actions).toEqual({ increaseCount: false })
119119
expect(instance.state.matches({
120120
id1: {
121121
foo: true
@@ -165,7 +165,7 @@ describe('Statecharts', () => {
165165

166166
expect(instance.state.states).toEqual([['id1', 'foo']])
167167
expect(instance.state.actions).toEqual({
168-
increaseCount: true,
168+
increaseCount: false,
169169
changeToBar: true,
170170
})
171171
expect(instance.state.matches({
@@ -345,10 +345,10 @@ describe('Statecharts', () => {
345345

346346
expect(instance.state.states).toEqual([['test', 'foo', 'nested', 'a']])
347347
expect(instance.state.actions).toEqual({
348-
fooEntry: true,
349-
fooExit: true,
350-
aEntry: true,
351-
aExit: true,
348+
fooEntry: false,
349+
fooExit: false,
350+
aEntry: false,
351+
aExit: false,
352352
bExit: false,
353353
changeToBar: true,
354354
changeToB: true,
@@ -358,11 +358,11 @@ describe('Statecharts', () => {
358358

359359
expect(instance.state.states).toEqual([['test', 'foo', 'nested', 'b']])
360360
expect(instance.state.actions).toEqual({
361-
fooEntry: true,
362-
fooExit: true,
361+
fooEntry: false,
362+
fooExit: false,
363363
aEntry: false,
364364
aExit: false,
365-
bExit: true,
365+
bExit: false,
366366
changeToBar: true,
367367
changeToB: false,
368368
})
@@ -655,7 +655,7 @@ describe('Statecharts', () => {
655655
const instance = createOvermind(statecharts(config, {mainChart}))
656656

657657
expect(instance.state.states).toEqual([['mainChart', 'foo', 'chart', 'baz'], ['mainChart', 'foo', 'chartB', 'baz']])
658-
expect(instance.state.actions).toEqual({ increaseCount: true, changeToBar: true })
658+
expect(instance.state.actions).toEqual({ increaseCount: false, changeToBar: true })
659659
expect(instance.state.matches({
660660
mainChart: {
661661
foo: {

packages/node_modules/overmind/src/config/statecharts.ts

Lines changed: 70 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -37,60 +37,48 @@ interface Statecharts {
3737
}
3838

3939
function getActionTransitions(
40-
key: string,
40+
actionName: string,
4141
charts: Statecharts,
4242
state: { states: Array<(string | number)[]> }
4343
) {
44-
const transitions: (string | boolean)[] = []
45-
state.states.forEach((statePath) => {
44+
const transitions: Array<{index: number, target?: string}> = []
45+
46+
state.states.forEach((statePath, index) => {
4647
const path = statePath.slice()
4748

4849
while (path.length) {
4950
const target = getStateTarget(charts, path)
5051

51-
if (target.entry === key) {
52-
transitions.push('entry')
53-
return
54-
}
55-
if (target.exit === key) {
56-
transitions.push('exit')
52+
if (target.on &&
53+
target.on[actionName] === null
54+
) {
55+
transitions.push({index})
5756
return
5857
}
59-
if (target.on && target.on[key] === null) {
60-
transitions.push(true)
61-
return
62-
}
63-
if (target.on && typeof target.on[key] === 'string') {
64-
// If we have already transition to this path it means
65-
// we are moving away from multiple paths (parallel)
66-
if (transitions.includes(target.on[key])) {
67-
return
68-
}
69-
transitions.push(target.on[key])
58+
59+
if (target.on && typeof target.on[actionName] === 'string' && !transitions.find((transition) => transition.target === target.on[actionName])) {
60+
transitions.push({index, target: target.on[actionName]})
7061
return
7162
}
72-
if (target.on && target.on[key] && target.on[key].condition(state)) {
73-
transitions.push(target.on[key].target)
63+
64+
if (target.on && target.on[actionName] && target.on[actionName].target && !transitions.find((transition) => transition.target === target.on[actionName].target) && target.on[actionName].condition(state)) {
65+
transitions.push({
66+
index,
67+
target: target.on[actionName].target
68+
})
7469
return
7570
}
7671

7772
path.pop()
7873
}
79-
80-
transitions.push(false)
8174
})
8275

8376
return transitions
8477
}
8578

86-
// We check if any transitions returned are not "false",
87-
// which means any entry, exit or state transition should occur on
88-
// any of existing state paths active
8979
function getCanTransitionActions(actions, charts, state) {
9080
return Object.keys(actions || {}).reduce((aggr, key) => {
91-
aggr[key] = getActionTransitions(key, charts, state).some(
92-
(transition) => transition !== false
93-
)
81+
aggr[key] = Boolean(getActionTransitions(key, charts, state).length)
9482

9583
return aggr
9684
}, {})
@@ -242,20 +230,17 @@ export function statecharts<C extends IConfiguration, Charts extends Statecharts
242230
context.state,
243231
context.execution.namespacePath
244232
)
245-
const actions = getTarget(
246-
context.actions,
247-
context.execution.namespacePath
248-
)
249233

250234
const statePaths = stateTarget.states.slice()
251235

236+
// Run entry actions of initial state
252237
statePaths.forEach((statePath) => {
253238
const state = statePath.slice()
254239
while (state.length) {
255240
const target = getStateTarget(charts, state)
256241

257-
if (target.entry) {
258-
actions[target.entry]()
242+
if (config.actions && config.actions[target.entry]) {
243+
config.actions[target.entry](context)
259244
}
260245

261246
state.pop()
@@ -276,8 +261,7 @@ export function statecharts<C extends IConfiguration, Charts extends Statecharts
276261
}) as any,
277262
state: Object.assign(state, {
278263
states: getInitialStates(charts),
279-
actions: ((state) =>
280-
getCanTransitionActions(actions, charts, state)) as any,
264+
actions: ((state) => getCanTransitionActions(actions, charts, state)) as any,
281265
matches: (state) => (match) => {
282266
const matchPaths = getMatchPaths(match)
283267

@@ -312,42 +296,39 @@ export function statecharts<C extends IConfiguration, Charts extends Statecharts
312296
const stateTarget = getTarget(state, execution.namespacePath)
313297

314298
return {
315-
transitions: getActionTransitions(key, charts, stateTarget),
299+
canTransition: stateTarget.actions[key],
316300
payload,
317301
}
318302
}),
319-
filter(function canTransition(_, { transitions }) {
320-
return transitions.some((transition) => transition !== false)
303+
filter(function canTransition(_, { canTransition }) {
304+
return canTransition
321305
}),
322-
mutate(function runAction(context: any, { transitions, payload }) {
306+
mutate(function runAction(context: any, { payload }) {
323307
const stateTarget = getTarget(
324308
context.state,
325309
context.execution.namespacePath
326310
)
327-
const actionsTarget = getTarget(
328-
context.actions,
329-
context.execution.namespacePath
330-
)
311+
const transitionActions = getActionTransitions(key, charts, stateTarget)
331312

332-
const resolvedActions = getTarget(
333-
context.actions,
334-
context.execution.namespacePath
335-
)
336-
const newStates: Array<(string | number)[]> = []
337-
338-
if (transitions.includes('entry') || transitions.includes('exit')) {
339-
actionsTarget[ACTIONS][key](payload)
313+
// If there are no new transition target, just drop moving on, just run the action
314+
if (!transitionActions.some((transitionAction) => transitionAction.target)) {
315+
if (config.actions) {
316+
config.actions[key](context, payload)
317+
}
318+
return
340319
}
341320

342-
const actionsToRun = transitions.reduce((aggr, transition, index) => {
343-
const oldStatePath = stateTarget.states[index].slice()
321+
const exitActions: string[] = []
322+
const entryActions: string[] = []
323+
const newStates: Array<string[]> = []
344324

345-
if (transition === 'entry' || transition === 'exit') {
346-
newStates.push(oldStatePath)
347-
return aggr
325+
transitionActions.forEach((transitionAction) => {
326+
// It is an action that does not cause a transition
327+
if (!transitionAction.target) {
328+
return
348329
}
349330

350-
const currentStatePath = stateTarget.states[index].slice()
331+
const currentStatePath = stateTarget.states[transitionAction.index].slice()
351332
const stateTransitions = currentStatePath.map(() => null)
352333

353334
// Build new transition path
@@ -367,20 +348,22 @@ export function statecharts<C extends IConfiguration, Charts extends Statecharts
367348
stateTarget.states,
368349
stateTransitions,
369350
charts,
370-
index
351+
transitionAction.index
371352
)
372353

373354
// Go down old path and trigger exits where the state has changed
374-
const traverseOldPath = oldStatePath.slice()
355+
const traverseOldPath = stateTarget.states[transitionAction.index].slice()
356+
375357
while (traverseOldPath.length) {
376358
const target = getStateTarget(charts, traverseOldPath)
377359

378360
if (
379361
target.exit &&
380362
newStatePath[traverseOldPath.length - 1] !==
381-
oldStatePath[traverseOldPath.length - 1]
363+
traverseOldPath[traverseOldPath.length - 1]
382364
) {
383-
resolvedActions[target.exit]()
365+
366+
exitActions.push(target.exit)
384367
}
385368

386369
traverseOldPath.pop()
@@ -396,26 +379,37 @@ export function statecharts<C extends IConfiguration, Charts extends Statecharts
396379
if (
397380
target.entry &&
398381
newStatePath[traverseNewPath.length - 1] !==
399-
oldStatePath[traverseNewPath.length - 1]
382+
traverseNewPath[traverseNewPath.length - 1]
400383
) {
401-
resolvedActions[target.entry]()
384+
entryActions.push(target.entry)
402385
}
403386

404387
traverseNewPath.pop()
405388
}
406389

407-
408-
if (transition) {
409-
return aggr.concat(() => actionsTarget[ACTIONS][key](payload))
410-
}
411-
412-
return aggr
413-
}, [] as Function[])
414-
415-
// Actually change what has changed
390+
})
391+
392+
// Run exits
393+
exitActions.forEach((exitAction) => {
394+
if (config.actions) {
395+
config.actions[exitAction](context, payload)
396+
}
397+
})
398+
399+
// Transition to new state
416400
stateTarget.states = newStates
417401

418-
actionsToRun.forEach((action)=> action())
402+
if (config.actions) {
403+
config.actions[key](context, payload)
404+
}
405+
406+
// Run enters
407+
408+
entryActions.forEach((entryAction) => {
409+
if (config.actions) {
410+
config.actions[entryAction](context, payload)
411+
}
412+
})
419413

420414
if (process.env.NODE_ENV === 'development' && currentInstance.devtools) {
421415
currentInstance.devtools.send({

0 commit comments

Comments
 (0)