Skip to content

Commit 40bb0af

Browse files
feat(overmind): allow root charts also on nested
BREAKING CHANGE: slightly changed API surface of statecharts
1 parent d96c5ed commit 40bb0af

File tree

3 files changed

+73
-47
lines changed

3 files changed

+73
-47
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
export { merge } from './merge'
22
export { namespaced } from './namespaced'
33
export { lazy } from './lazy'
4-
export { statecharts, Statechart } from './statecharts'
4+
export { statechart, Statechart } from './statechart'

packages/node_modules/overmind/src/config/statecharts.test.ts renamed to packages/node_modules/overmind/src/config/statechart.test.ts

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { IAction, createOvermind } from '../'
2-
import { Statechart, statecharts } from './'
2+
import { Statechart, statechart } from './'
33

44
describe('Statecharts', () => {
55
test('should wrap configs', () => {
@@ -13,7 +13,7 @@ describe('Statecharts', () => {
1313
foo: {},
1414
},
1515
}
16-
const instance = createOvermind(statecharts(config, {
16+
const instance = createOvermind(statechart(config, {
1717
id1: chart
1818
}))
1919

@@ -26,7 +26,7 @@ describe('Statecharts', () => {
2626
})).toEqual(true)
2727
})
2828

29-
test.only('should allow root chart', () => {
29+
test('should allow root chart', () => {
3030
const config = {}
3131

3232
const chart: Statechart<typeof config, {
@@ -37,9 +37,9 @@ describe('Statecharts', () => {
3737
foo: {},
3838
},
3939
}
40-
const instance = createOvermind(statecharts(config, chart))
40+
const instance = createOvermind(statechart(config, chart))
4141

42-
expect(instance.state.states).toEqual([['ROOT_CHART', 'foo']])
42+
expect(instance.state.states).toEqual([['CHART', 'foo']])
4343
expect(instance.state.actions).toEqual({})
4444
expect(instance.state.matches({
4545
foo: true
@@ -78,7 +78,7 @@ describe('Statecharts', () => {
7878
interface Action<Input = void, Output = void>
7979
extends IAction<typeof config, Input, Output> {}
8080

81-
const instance = createOvermind(statecharts(config, {
81+
const instance = createOvermind(statechart(config, {
8282
id1: chart
8383
}))
8484

@@ -130,7 +130,7 @@ describe('Statecharts', () => {
130130
interface Action<Input = void, Output = void>
131131
extends IAction<typeof config, Input, Output> {}
132132

133-
const instance = createOvermind(statecharts(config, {
133+
const instance = createOvermind(statechart(config, {
134134
id1: chart
135135
}))
136136

@@ -179,7 +179,7 @@ describe('Statecharts', () => {
179179
interface Action<Input = void, Output = void>
180180
extends IAction<typeof config, Input, Output> {}
181181

182-
const instance = createOvermind(statecharts(config, {
182+
const instance = createOvermind(statechart(config, {
183183
id1: chart
184184
}))
185185

@@ -247,7 +247,7 @@ describe('Statecharts', () => {
247247
interface Action<Input = void, Output = void>
248248
extends IAction<typeof config, Input, Output> {}
249249

250-
const instance = createOvermind(statecharts(config, {
250+
const instance = createOvermind(statechart(config, {
251251
id1: chart
252252
}))
253253

@@ -283,7 +283,7 @@ describe('Statecharts', () => {
283283
})).toEqual(true)
284284
})
285285

286-
test('should allow nesting', () => {
286+
test.only('should allow nesting', () => {
287287
const fooEntry: Action = ({ state }) => {
288288
state.transitions.push('fooEntry')
289289
}
@@ -337,9 +337,7 @@ describe('Statecharts', () => {
337337
}
338338

339339
const chart: Statechart<typeof config, {
340-
foo: {
341-
nested: typeof nestedChart
342-
},
340+
foo: typeof nestedChart,
343341
bar: void
344342
}> = {
345343
initial: 'foo',
@@ -350,7 +348,7 @@ describe('Statecharts', () => {
350348
on: {
351349
changeToBar: 'bar',
352350
},
353-
charts: {nested: nestedChart},
351+
chart: nestedChart
354352
},
355353
bar: {},
356354
},
@@ -359,11 +357,9 @@ describe('Statecharts', () => {
359357
interface Action<Input = void, Output = void>
360358
extends IAction<typeof config, Input, Output> {}
361359

362-
const instance = createOvermind(statecharts(config, {
363-
test: chart
364-
}))
360+
const instance = createOvermind(statechart(config, chart))
365361

366-
expect(instance.state.states).toEqual([['test', 'foo', 'nested', 'a']])
362+
expect(instance.state.states).toEqual([['CHART', 'foo', 'CHART', 'a']])
367363
expect(instance.state.actions).toEqual({
368364
fooEntry: false,
369365
fooExit: false,
@@ -376,7 +372,7 @@ describe('Statecharts', () => {
376372
expect(instance.state.transitions).toEqual(['aEntry', 'fooEntry'])
377373
instance.actions.changeToB()
378374

379-
expect(instance.state.states).toEqual([['test', 'foo', 'nested', 'b']])
375+
expect(instance.state.states).toEqual([['CHART', 'foo', 'CHART', 'b']])
380376
expect(instance.state.actions).toEqual({
381377
fooEntry: false,
382378
fooExit: false,
@@ -388,7 +384,7 @@ describe('Statecharts', () => {
388384
})
389385
expect(instance.state.transitions).toEqual(['aEntry', 'fooEntry', 'aExit'])
390386
instance.actions.changeToBar()
391-
expect(instance.state.states).toEqual([['test', 'bar']])
387+
expect(instance.state.states).toEqual([['CHART', 'bar']])
392388
expect(instance.state.actions).toEqual({
393389
fooEntry: false,
394390
fooExit: false,
@@ -458,7 +454,7 @@ describe('Statecharts', () => {
458454
extends IAction<typeof config, Input, Output> {}
459455

460456
const instance = createOvermind(
461-
statecharts(config, {chartA, chartB})
457+
statechart(config, {chartA, chartB})
462458
)
463459

464460
expect(instance.state.states).toEqual([['chartA', 'bar'], ['chartB', 'foo']])
@@ -557,7 +553,7 @@ describe('Statecharts', () => {
557553
increaseCount: null,
558554
changeToBar: 'bar',
559555
},
560-
charts: {nestedChartB},
556+
chart: {nestedChartB},
561557
},
562558
bar: {},
563559
},
@@ -567,7 +563,7 @@ describe('Statecharts', () => {
567563
extends IAction<typeof config, Input, Output> {}
568564

569565
const instance = createOvermind(
570-
statecharts(config, {parallelChartA, parallelChartB})
566+
statechart(config, {parallelChartA, parallelChartB})
571567
)
572568

573569
expect(instance.state.states).toEqual([['parallelChartA', 'bar'], ['parallelChartB', 'foo', 'nestedChartB', 'foo']])
@@ -663,7 +659,7 @@ describe('Statecharts', () => {
663659
on: {
664660
changeToBar: 'bar',
665661
},
666-
charts: {chart, chartB},
662+
chart: {chart, chartB},
667663
},
668664
bar: {},
669665
},
@@ -672,7 +668,7 @@ describe('Statecharts', () => {
672668
interface Action<Input = void, Output = void>
673669
extends IAction<typeof config, Input, Output> {}
674670

675-
const instance = createOvermind(statecharts(config, {mainChart}))
671+
const instance = createOvermind(statechart(config, {mainChart}))
676672

677673
expect(instance.state.states).toEqual([['mainChart', 'foo', 'chart', 'baz'], ['mainChart', 'foo', 'chartB', 'baz']])
678674
expect(instance.state.actions).toEqual({ increaseCount: false, changeToBar: true })
@@ -704,7 +700,7 @@ describe('Statecharts', () => {
704700
expect(instance.state.count).toBe(3)
705701
})
706702

707-
test.only('should run entry and exit actions when transitioning within a transition', async () => {
703+
test('should run entry and exit actions when transitioning within a transition', async () => {
708704
const step: AsyncAction = async ({ state, actions }) => {
709705
state.actionEvents.push('step')
710706
await Promise.resolve()
@@ -760,7 +756,7 @@ describe('Statecharts', () => {
760756
interface AsyncAction<Input = void, Output = void>
761757
extends IAction<typeof config, Input, Promise<Output>> {}
762758

763-
const instance = createOvermind(statecharts(config, {
759+
const instance = createOvermind(statechart(config, {
764760
id1: chart
765761
}))
766762

packages/node_modules/overmind/src/config/statecharts.ts renamed to packages/node_modules/overmind/src/config/statechart.ts

Lines changed: 49 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,17 @@ import {
99
} from '../'
1010

1111
const ACTIONS = 'ACTIONS'
12-
const ROOT_CHART = 'ROOT_CHART'
12+
const CHART = 'CHART'
1313

1414
export interface Statechart<C extends IConfiguration, S extends {
15-
[state: string]: Statecharts | void
15+
[state: string]: Statecharts | Statechart<any, any> | void
1616
}> {
1717
initial: keyof S
1818
states: {
1919
[N in keyof S]: {
2020
entry?: keyof C['actions']
2121
exit?: keyof C['actions']
22-
charts?: S[N],
22+
chart?: S[N],
2323
on?: {
2424
[N in keyof C['actions']]?:
2525
| keyof S
@@ -37,6 +37,36 @@ interface Statecharts {
3737
[id: string]: Statechart<any, any>
3838
}
3939

40+
function isRootChart(chart) {
41+
return ('initial' in chart && 'states' in chart)
42+
}
43+
44+
function forceNestedCharts(charts: Statecharts | Statechart<any, any>) {
45+
if (isRootChart(charts)) {
46+
charts = { [CHART]: charts } as Statecharts
47+
}
48+
49+
return Object.keys(charts).reduce((aggr, chartKey) => {
50+
aggr[chartKey] = {
51+
...charts[chartKey],
52+
states: Object.keys(charts[chartKey].states).reduce((statesAggr, stateKey) => {
53+
if (charts[chartKey].states[stateKey].chart) {
54+
statesAggr[stateKey] = {
55+
...charts[chartKey].states[stateKey],
56+
chart: forceNestedCharts(charts[chartKey].states[stateKey].chart)
57+
}
58+
} else {
59+
statesAggr[stateKey] = charts[chartKey].states[stateKey]
60+
}
61+
62+
return statesAggr
63+
}, {})
64+
}
65+
66+
return aggr
67+
}, {})
68+
}
69+
4070
function getActionTransitions(
4171
actionName: string,
4272
charts: Statecharts,
@@ -118,8 +148,13 @@ function getInitialStates(charts: Statecharts, paths: Array<string[]> = [[]]) {
118148
paths[paths.length - 1].push(chartKey)
119149
paths[paths.length - 1].push(chart.initial as string)
120150

121-
if (chart.states[chart.initial as string].charts) {
122-
getInitialStates(chart.states[chart.initial as string].charts as Statecharts, paths)
151+
const nestedChart = chart.states[chart.initial as string].chart
152+
if (nestedChart && isRootChart(nestedChart)) {
153+
getInitialStates({
154+
[CHART]: nestedChart
155+
} as Statecharts, paths)
156+
} else if (nestedChart) {
157+
getInitialStates(nestedChart as Statecharts, paths)
123158
}
124159
})
125160

@@ -168,7 +203,7 @@ function getStateTarget(charts, path) {
168203
return path.reduce(
169204
(aggr, key, index) => {
170205
const isChart = index % 2
171-
206+
172207
if (!isChart) {
173208
return aggr[key]
174209
}
@@ -177,24 +212,24 @@ function getStateTarget(charts, path) {
177212
return aggr.states[key]
178213
}
179214

180-
return aggr.states[key].charts
215+
return aggr.states[key].chart
181216
},
182217
charts
183218
)
184219
}
185220

186221
type Match<T extends Statecharts | Statechart<any, any>> = T extends Statecharts ? {
187222
[I in keyof T]?: {
188-
[S in keyof T[I]["states"]]?: T[I]["states"][S]["charts"] extends void ? boolean : boolean | Match<T[I]["states"][S]["charts"]>
223+
[S in keyof T[I]["states"]]?: T[I]["states"][S]["chart"] extends void ? boolean : boolean | Match<T[I]["states"][S]["chart"]>
189224
}
190225
} : T extends Statechart<any, any> ? {
191-
[S in keyof T["states"]]?: T["states"][S]["charts"] extends void ? boolean : boolean | Match<T["states"][S]["charts"]>
226+
[S in keyof T["states"]]?: T["states"][S]["chart"] extends void ? boolean : boolean | Match<T["states"][S]["chart"]>
192227
} : never
193228

194229

195-
export function statecharts<C extends IConfiguration, Charts extends Statecharts | Statechart<any, any>>(
230+
export function statechart<C extends IConfiguration, Charts extends Statecharts | Statechart<any, any>>(
196231
config: C,
197-
chartsDefinition: Charts
232+
chartDefinition: Charts
198233
): IConfig<{
199234
onInitialize: C['onInitialize']
200235
state: C['state'] & {
@@ -209,8 +244,7 @@ export function statecharts<C extends IConfiguration, Charts extends Statecharts
209244
} {
210245
let currentInstance
211246

212-
const IS_ROOT_CHART = ('initial' in chartsDefinition && 'states' in chartsDefinition)
213-
const charts = (IS_ROOT_CHART ? { [ROOT_CHART]: chartsDefinition} : chartsDefinition) as Statecharts
247+
const charts = forceNestedCharts(chartDefinition)
214248
const actions = config.actions || {}
215249
const state = config.state || {}
216250

@@ -276,17 +310,13 @@ export function statecharts<C extends IConfiguration, Charts extends Statecharts
276310
states: getInitialStates(charts),
277311
actions: ((state) => getCanTransitionActions(actions, charts, state)) as any,
278312
matches: (state) => (match) => {
279-
if (IS_ROOT_CHART) {
280-
match = {
281-
[ROOT_CHART]: match
282-
}
283-
}
284313
const matchPaths = getMatchPaths(match)
314+
const statesWithoutRootChartIndicator = state.states.map((statePath) => statePath.filter((path) => path !== CHART))
285315

286316
for (let x = 0; x < matchPaths.length; x++) {
287317
const matchPath = matchPaths[x]
288318
const shouldMatch = matchPath.reduce((aggr, key) => aggr[key], match)
289-
const hasMatch = state.states.reduce((aggr, statePath) => {
319+
const hasMatch = statesWithoutRootChartIndicator.reduce((aggr, statePath) => {
290320
if (aggr) {
291321
return aggr
292322
}

0 commit comments

Comments
 (0)