Skip to content

Commit 377d0ce

Browse files
feat(overmind): added statemachines
1 parent a3f4218 commit 377d0ce

File tree

4 files changed

+218
-8
lines changed

4 files changed

+218
-8
lines changed

packages/node_modules/overmind/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ export { MODE_DEFAULT, MODE_TEST, MODE_SSR } from './utils'
7070

7171
export { SERIALIZE, rehydrate } from './rehydrate'
7272

73+
export { Statemachine, statemachine } from './statemachine'
74+
7375
/** This type can be overwriten by app developers if they want to avoid
7476
* typing and then they can import `Action`, `Operation` etc. directly from
7577
* overmind.

packages/node_modules/overmind/src/ssr.test.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,12 @@ describe('Mock', () => {
4444

4545
overmind.state.foo = 'bar2'
4646

47-
expect(overmind.hydrate()).toEqual([
48-
{
49-
method: 'set',
50-
path: 'foo',
51-
args: ['bar2'],
52-
},
53-
])
47+
const mutations = overmind.hydrate()
48+
expect(mutations[0].method).toBe('set')
49+
expect(mutations[0].path).toBe('foo')
50+
expect(mutations[0].args).toEqual(['bar2'])
5451
})
55-
test.only('should rehydrate mutation', () => {
52+
test('should rehydrate mutation', () => {
5653
type State = {
5754
foo: string
5855
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import { IAction , createOvermind, createOvermindMock } from './'
2+
3+
import { Statemachine, statemachine } from './statemachine'
4+
5+
describe('Statemachine', () => {
6+
test('should set initial state', () => {
7+
8+
type State = {
9+
machine: Statemachine<'FOO' | 'BAR'>
10+
}
11+
const state: State = {
12+
machine: statemachine<'FOO' | 'BAR'>({
13+
initial: 'FOO',
14+
states: {
15+
FOO: ['BAR'],
16+
BAR: ['FOO']
17+
}
18+
})
19+
}
20+
const config = {
21+
state,
22+
}
23+
24+
const overmind = createOvermindMock(config)
25+
26+
expect(overmind.state.machine.current).toBe('FOO')
27+
})
28+
test('should transition state', () => {
29+
30+
type State = {
31+
machine: Statemachine<'FOO' | 'BAR'>
32+
}
33+
34+
const state: State = {
35+
machine: statemachine<'FOO' | 'BAR'>({
36+
initial: 'FOO',
37+
states: {
38+
FOO: ['BAR'],
39+
BAR: ['FOO']
40+
}
41+
})
42+
}
43+
const transition: Action = ({ state }) => {
44+
state.machine.BAR()
45+
}
46+
47+
const config = {
48+
state,
49+
actions: {
50+
transition
51+
}
52+
}
53+
54+
interface Action extends IAction<typeof config, void, void> {}
55+
56+
const overmind = createOvermindMock(config)
57+
overmind.actions.transition()
58+
expect(overmind.state.machine.current).toBe('BAR')
59+
})
60+
test('should ignore transition to invalid state', () => {
61+
62+
type State = {
63+
machine: Statemachine<'FOO' | 'BAR'>
64+
}
65+
66+
const state: State = {
67+
machine: statemachine<'FOO' | 'BAR'>({
68+
initial: 'FOO',
69+
states: {
70+
FOO: [],
71+
BAR: ['FOO']
72+
}
73+
})
74+
}
75+
const transition: Action = ({ state }) => {
76+
state.machine.BAR()
77+
}
78+
79+
const config = {
80+
state,
81+
actions: {
82+
transition
83+
}
84+
}
85+
86+
interface Action extends IAction<typeof config, void, void> {}
87+
88+
const overmind = createOvermindMock(config)
89+
overmind.actions.transition()
90+
expect(overmind.state.machine.current).toBe('FOO')
91+
})
92+
test('should run entry and exit transition', () => {
93+
expect.assertions(3)
94+
type State = {
95+
machine: Statemachine<'FOO' | 'BAR'>
96+
}
97+
98+
const state: State = {
99+
machine: statemachine<'FOO' | 'BAR'>({
100+
initial: 'FOO',
101+
states: {
102+
FOO: ['BAR'],
103+
BAR: ['FOO']
104+
}
105+
})
106+
}
107+
const transition: Action = ({ state }) => {
108+
state.machine.BAR(() => {
109+
expect(state.machine.current).toBe('BAR')
110+
state.machine.FOO()
111+
}, () => {
112+
expect(state.machine.current).toBe('BAR')
113+
})
114+
}
115+
116+
const config = {
117+
state,
118+
actions: {
119+
transition
120+
}
121+
}
122+
123+
interface Action extends IAction<typeof config, void, void> {}
124+
125+
const overmind = createOvermindMock(config)
126+
overmind.actions.transition()
127+
expect(overmind.state.machine.current).toBe('FOO')
128+
})
129+
test('should flush changes to transitions', () => {
130+
expect.assertions(1)
131+
132+
type State = {
133+
machine: Statemachine<'FOO' | 'BAR'>
134+
}
135+
136+
const state: State = {
137+
machine: statemachine<'FOO' | 'BAR'>({
138+
initial: 'FOO',
139+
states: {
140+
FOO: ['BAR'],
141+
BAR: ['FOO']
142+
}
143+
})
144+
}
145+
const transition: Action = ({ state }) => {
146+
state.machine.BAR()
147+
}
148+
149+
const config = {
150+
state,
151+
actions: {
152+
transition
153+
}
154+
}
155+
156+
interface Action extends IAction<typeof config, void, void> {}
157+
158+
const overmind = createOvermind(config)
159+
overmind.reaction((state) => state.machine.current, (value) => {
160+
expect(value).toBe('BAR')
161+
})
162+
overmind.actions.transition()
163+
})
164+
})
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { Derive } from './'
2+
3+
export type StatemachineDefinition<States extends string> = {
4+
initial: States,
5+
states: {
6+
[State in States]: Array<States>
7+
}
8+
}
9+
10+
export type Statemachine<States extends string> = {
11+
current: States
12+
reset: () => void
13+
} & {
14+
[State in States]: Derive<any, <T>(entry?: () => T, exit?: () => void) => T>
15+
}
16+
17+
export function statemachine<States extends string>(chart: StatemachineDefinition<States>): Statemachine<States> {
18+
let currentExit
19+
20+
return {
21+
current: chart.initial,
22+
...Object.keys(chart.states).reduce((aggr, key) => {
23+
aggr[key] = () => {
24+
return function (entry, exit) {
25+
if (chart.states[this.current].includes(key as any)) {
26+
if (currentExit) currentExit()
27+
currentExit = exit
28+
this.current = key
29+
return entry && entry()
30+
}
31+
}
32+
}
33+
34+
return aggr
35+
}, {}),
36+
reset: () => {
37+
return function() {
38+
const caller = this
39+
caller.current = chart.initial
40+
if (currentExit) {
41+
currentExit()
42+
currentExit = null
43+
}
44+
}
45+
}
46+
} as any
47+
}

0 commit comments

Comments
 (0)