Skip to content

Commit 9e04d36

Browse files
committed
fix(statechart, overmind): await async exit actions, fix devtools actions, fix throttle stale value
1 parent c36dfef commit 9e04d36

File tree

5 files changed

+419
-37
lines changed

5 files changed

+419
-37
lines changed

packages/overmind-statechart/src/index.test.ts

Lines changed: 310 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// @ts-nocheck
2-
import { IContext, createOvermind } from 'overmind'
2+
import { IContext, createOvermind, pipe, wait } from 'overmind'
33

44
import { Statechart, statechart } from './'
55

@@ -1081,4 +1081,313 @@ describe('Statecharts', () => {
10811081
'step2',
10821082
])
10831083
})
1084+
1085+
test('should work with operator-based action using pipe', async () => {
1086+
const increaseCount = pipe(function step({ state }: any) {
1087+
state.count++
1088+
})
1089+
1090+
const state = {
1091+
count: 0,
1092+
}
1093+
const actions = {
1094+
increaseCount,
1095+
}
1096+
1097+
const config = {
1098+
state,
1099+
actions,
1100+
}
1101+
1102+
const chart: Statechart<
1103+
typeof config,
1104+
{
1105+
foo: void
1106+
bar: void
1107+
}
1108+
> = {
1109+
initial: 'foo',
1110+
states: {
1111+
foo: {
1112+
on: {
1113+
increaseCount: 'bar',
1114+
},
1115+
},
1116+
bar: {},
1117+
},
1118+
}
1119+
1120+
const instance = createOvermind(
1121+
statechart(config, {
1122+
id1: chart,
1123+
})
1124+
)
1125+
1126+
expect(instance.state.states).toEqual([['id1', 'foo']])
1127+
expect(instance.state.actions).toEqual({ increaseCount: true })
1128+
1129+
await instance.actions.increaseCount()
1130+
1131+
expect(instance.state.states).toEqual([['id1', 'bar']])
1132+
expect(instance.state.actions).toEqual({ increaseCount: false })
1133+
expect(instance.state.count).toBe(1)
1134+
})
1135+
1136+
test('should work with multi-step operator pipe action', async () => {
1137+
const doTransition = pipe(
1138+
function addFirst({ state }: any) {
1139+
state.steps.push('first')
1140+
},
1141+
function addSecond({ state }: any) {
1142+
state.steps.push('second')
1143+
}
1144+
)
1145+
1146+
const state = {
1147+
steps: [] as string[],
1148+
}
1149+
const actions = {
1150+
doTransition,
1151+
}
1152+
1153+
const config = {
1154+
state,
1155+
actions,
1156+
}
1157+
1158+
const chart: Statechart<
1159+
typeof config,
1160+
{
1161+
foo: void
1162+
bar: void
1163+
}
1164+
> = {
1165+
initial: 'foo',
1166+
states: {
1167+
foo: {
1168+
on: {
1169+
doTransition: 'bar',
1170+
},
1171+
},
1172+
bar: {},
1173+
},
1174+
}
1175+
1176+
const instance = createOvermind(
1177+
statechart(config, {
1178+
id1: chart,
1179+
})
1180+
)
1181+
1182+
await instance.actions.doTransition()
1183+
1184+
expect(instance.state.states).toEqual([['id1', 'bar']])
1185+
expect(instance.state.steps).toEqual(['first', 'second'])
1186+
})
1187+
1188+
test('should await operator-based exit action with async steps before main action', async () => {
1189+
const exitAction = pipe(wait(50), function onExit({ state }: any) {
1190+
state.events.push('exit')
1191+
})
1192+
1193+
const doTransition = pipe(function step({ state }: any) {
1194+
state.events.push('main')
1195+
})
1196+
1197+
const state = {
1198+
events: [] as string[],
1199+
}
1200+
const actions = {
1201+
exitAction,
1202+
doTransition,
1203+
}
1204+
1205+
const config = {
1206+
state,
1207+
actions,
1208+
}
1209+
1210+
const chart: Statechart<
1211+
typeof config,
1212+
{
1213+
foo: void
1214+
bar: void
1215+
}
1216+
> = {
1217+
initial: 'foo',
1218+
states: {
1219+
foo: {
1220+
exit: 'exitAction',
1221+
on: {
1222+
doTransition: 'bar',
1223+
},
1224+
},
1225+
bar: {},
1226+
},
1227+
}
1228+
1229+
const instance = createOvermind(
1230+
statechart(config, {
1231+
id1: chart,
1232+
})
1233+
)
1234+
1235+
await instance.actions.doTransition()
1236+
1237+
expect(instance.state.states).toEqual([['id1', 'bar']])
1238+
expect(instance.state.events).toEqual(['exit', 'main'])
1239+
})
1240+
1241+
test('should block operator-based action not allowed by statechart', async () => {
1242+
const increaseCount = pipe(function step({ state }: any) {
1243+
state.count++
1244+
})
1245+
1246+
const state = {
1247+
count: 0,
1248+
}
1249+
const actions = {
1250+
increaseCount,
1251+
}
1252+
1253+
const config = {
1254+
state,
1255+
actions,
1256+
}
1257+
1258+
const chart: Statechart<
1259+
typeof config,
1260+
{
1261+
foo: void
1262+
bar: void
1263+
}
1264+
> = {
1265+
initial: 'foo',
1266+
states: {
1267+
foo: {},
1268+
bar: {
1269+
on: {
1270+
increaseCount: null,
1271+
},
1272+
},
1273+
},
1274+
}
1275+
1276+
const instance = createOvermind(
1277+
statechart(config, {
1278+
id1: chart,
1279+
})
1280+
)
1281+
1282+
expect(instance.state.actions).toEqual({ increaseCount: false })
1283+
1284+
await instance.actions.increaseCount()
1285+
1286+
expect(instance.state.count).toBe(0)
1287+
})
1288+
1289+
test('should work with operator-based action without transition target', async () => {
1290+
const increaseCount = pipe(function step({ state }: any) {
1291+
state.count++
1292+
})
1293+
1294+
const state = {
1295+
count: 0,
1296+
}
1297+
const actions = {
1298+
increaseCount,
1299+
}
1300+
1301+
const config = {
1302+
state,
1303+
actions,
1304+
}
1305+
1306+
const chart: Statechart<
1307+
typeof config,
1308+
{
1309+
foo: void
1310+
}
1311+
> = {
1312+
initial: 'foo',
1313+
states: {
1314+
foo: {
1315+
on: {
1316+
increaseCount: null,
1317+
},
1318+
},
1319+
},
1320+
}
1321+
1322+
const instance = createOvermind(
1323+
statechart(config, {
1324+
id1: chart,
1325+
})
1326+
)
1327+
1328+
expect(instance.state.actions).toEqual({ increaseCount: true })
1329+
1330+
await instance.actions.increaseCount()
1331+
1332+
expect(instance.state.states).toEqual([['id1', 'foo']])
1333+
expect(instance.state.count).toBe(1)
1334+
})
1335+
1336+
test('should complete async exit action then execute transition', async () => {
1337+
// Verifies that async exit actions fully complete before the
1338+
// main action and transition execute
1339+
const exitAction = async ({ state }: any) => {
1340+
state.events.push('exit-start')
1341+
await new Promise((resolve) => setTimeout(resolve, 10))
1342+
state.events.push('exit-end')
1343+
}
1344+
1345+
const doTransition = ({ state }: any) => {
1346+
state.events.push('main')
1347+
}
1348+
1349+
const state = {
1350+
events: [] as string[],
1351+
}
1352+
const actions = {
1353+
exitAction,
1354+
doTransition,
1355+
}
1356+
1357+
const config = {
1358+
state,
1359+
actions,
1360+
}
1361+
1362+
const chart: Statechart<
1363+
typeof config,
1364+
{
1365+
foo: void
1366+
bar: void
1367+
}
1368+
> = {
1369+
initial: 'foo',
1370+
states: {
1371+
foo: {
1372+
exit: 'exitAction',
1373+
on: {
1374+
doTransition: 'bar',
1375+
},
1376+
},
1377+
bar: {},
1378+
},
1379+
}
1380+
1381+
const instance = createOvermind(
1382+
statechart(config, {
1383+
id1: chart,
1384+
})
1385+
)
1386+
1387+
await instance.actions.doTransition()
1388+
1389+
expect(instance.state.states).toEqual([['id1', 'bar']])
1390+
// Exit must fully complete (both start and end) before main action
1391+
expect(instance.state.events).toEqual(['exit-start', 'exit-end', 'main'])
1392+
})
10841393
})

0 commit comments

Comments
 (0)