@reventlessdev/reventless-gwt 1.0.0-alpha.74

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/CHANGELOG.md +698 -0
  2. package/LICENSE +202 -0
  3. package/bin/reventless-dev.mjs +21 -0
  4. package/package.json +54 -0
  5. package/rescript.json +36 -0
  6. package/src/.gwtignore +10 -0
  7. package/src/Automation_GWT.res +207 -0
  8. package/src/Automation_GWT.res.mjs +160 -0
  9. package/src/Behavior_GWT.res +603 -0
  10. package/src/Behavior_GWT.res.mjs +426 -0
  11. package/src/Bind.res +29 -0
  12. package/src/Bind.res.mjs +22 -0
  13. package/src/BuildClassifier.res +137 -0
  14. package/src/BuildClassifier.res.mjs +136 -0
  15. package/src/Cancellation.res +31 -0
  16. package/src/Cancellation.res.mjs +45 -0
  17. package/src/ChildProcess.res +84 -0
  18. package/src/ChildProcess.res.mjs +92 -0
  19. package/src/Cli.res +671 -0
  20. package/src/Cli.res.mjs +754 -0
  21. package/src/Collector.res +201 -0
  22. package/src/Collector.res.mjs +215 -0
  23. package/src/ComponentMeta.res +104 -0
  24. package/src/ComponentMeta.res.mjs +120 -0
  25. package/src/ComponentScan.res +103 -0
  26. package/src/ComponentScan.res.mjs +105 -0
  27. package/src/Delegate_GWT.res +303 -0
  28. package/src/Delegate_GWT.res.mjs +372 -0
  29. package/src/Diff.res +91 -0
  30. package/src/Diff.res.mjs +98 -0
  31. package/src/Discovery.res +105 -0
  32. package/src/Discovery.res.mjs +103 -0
  33. package/src/DomainDeadCode.res +93 -0
  34. package/src/DomainDeadCode.res.mjs +69 -0
  35. package/src/DomainGraph.res +0 -0
  36. package/src/DomainGraph.res.mjs +0 -0
  37. package/src/EventMapping_GWT.res +29 -0
  38. package/src/EventMapping_GWT.res.mjs +44 -0
  39. package/src/Filter.res +36 -0
  40. package/src/Filter.res.mjs +40 -0
  41. package/src/Flow_GWT.res +664 -0
  42. package/src/Flow_GWT.res.mjs +604 -0
  43. package/src/FormatterHuman.res +138 -0
  44. package/src/FormatterHuman.res.mjs +122 -0
  45. package/src/FormatterJson.res +257 -0
  46. package/src/FormatterJson.res.mjs +320 -0
  47. package/src/FormatterJunit.res +93 -0
  48. package/src/FormatterJunit.res.mjs +90 -0
  49. package/src/FormatterTap.res +139 -0
  50. package/src/FormatterTap.res.mjs +149 -0
  51. package/src/FormatterVsCode.res +362 -0
  52. package/src/FormatterVsCode.res.mjs +564 -0
  53. package/src/Hint.res +119 -0
  54. package/src/Hint.res.mjs +124 -0
  55. package/src/InboundTranslation_GWT.res +83 -0
  56. package/src/InboundTranslation_GWT.res.mjs +94 -0
  57. package/src/JestBind.res +70 -0
  58. package/src/JestBind.res.mjs +63 -0
  59. package/src/Loader.res +24 -0
  60. package/src/Loader.res.mjs +23 -0
  61. package/src/LocalHost.res +135 -0
  62. package/src/LocalHost.res.mjs +127 -0
  63. package/src/Mapping_GWT.res +474 -0
  64. package/src/Mapping_GWT.res.mjs +322 -0
  65. package/src/MultiSourceProjection_GWT.res +316 -0
  66. package/src/MultiSourceProjection_GWT.res.mjs +354 -0
  67. package/src/OutboundTranslation_GWT.res +238 -0
  68. package/src/OutboundTranslation_GWT.res.mjs +186 -0
  69. package/src/Outcome.res +161 -0
  70. package/src/Outcome.res.mjs +140 -0
  71. package/src/PackageScan.res +80 -0
  72. package/src/PackageScan.res.mjs +81 -0
  73. package/src/PlatformRunner.res +160 -0
  74. package/src/PlatformRunner.res.mjs +155 -0
  75. package/src/PlatformScan.res +138 -0
  76. package/src/PlatformScan.res.mjs +131 -0
  77. package/src/ProcessManager.res +91 -0
  78. package/src/ProcessManager.res.mjs +80 -0
  79. package/src/Projection_GWT.res +312 -0
  80. package/src/Projection_GWT.res.mjs +332 -0
  81. package/src/Query_GWT.res +379 -0
  82. package/src/Query_GWT.res.mjs +367 -0
  83. package/src/RenderRescript.res +125 -0
  84. package/src/RenderRescript.res.mjs +141 -0
  85. package/src/RunnerTypes.res +68 -0
  86. package/src/RunnerTypes.res.mjs +60 -0
  87. package/src/SideEffect_GWT.res +157 -0
  88. package/src/SideEffect_GWT.res.mjs +113 -0
  89. package/src/StateChange/ExternalAddCategorySlice.res +25 -0
  90. package/src/StateChange/ExternalAddCategorySlice.res.mjs +34 -0
  91. package/src/StateChange/ExternalAddCategorySlice_Behavior.res +19 -0
  92. package/src/StateChange/ExternalAddCategorySlice_Behavior.res.mjs +46 -0
  93. package/src/StateChange/WithFixtures.res +22 -0
  94. package/src/StateChange/WithFixtures.res.mjs +31 -0
  95. package/src/StateChange/WithFixtures_Behavior.res +17 -0
  96. package/src/StateChange/WithFixtures_Behavior.res.mjs +37 -0
  97. package/src/StateChange/WithManualOpen.res +23 -0
  98. package/src/StateChange/WithManualOpen.res.mjs +31 -0
  99. package/src/StateChange/WithManualOpen_Behavior.res +17 -0
  100. package/src/StateChange/WithManualOpen_Behavior.res.mjs +37 -0
  101. package/src/StubRuntime.res +53 -0
  102. package/src/StubRuntime.res.mjs +54 -0
  103. package/src/TestFixtures.res +16 -0
  104. package/src/TestFixtures.res.mjs +34 -0
  105. package/src/Watch.res +92 -0
  106. package/src/Watch.res.mjs +83 -0
  107. package/src/WatcherProbe.res +48 -0
  108. package/src/WatcherProbe.res.mjs +46 -0
  109. package/tests/AutomationGwtTest.res +71 -0
  110. package/tests/AutomationGwtTest.res.mjs +189 -0
  111. package/tests/BuildClassifierTest.res +94 -0
  112. package/tests/BuildClassifierTest.res.mjs +132 -0
  113. package/tests/ComponentMetaTest.res +52 -0
  114. package/tests/ComponentMetaTest.res.mjs +57 -0
  115. package/tests/DelegateGwtTest.res +487 -0
  116. package/tests/DelegateGwtTest.res.mjs +891 -0
  117. package/tests/DiscoveryTest.res +42 -0
  118. package/tests/DiscoveryTest.res.mjs +43 -0
  119. package/tests/DomainDeadCodeTest.res +116 -0
  120. package/tests/DomainDeadCodeTest.res.mjs +155 -0
  121. package/tests/DomainGraphTest.res +365 -0
  122. package/tests/DomainGraphTest.res.mjs +413 -0
  123. package/tests/FlowAggregateGwtTest.res +169 -0
  124. package/tests/FlowAggregateGwtTest.res.mjs +269 -0
  125. package/tests/FlowCrossPluginGwtTest.res +340 -0
  126. package/tests/FlowCrossPluginGwtTest.res.mjs +798 -0
  127. package/tests/FlowGwtTest.res +222 -0
  128. package/tests/FlowGwtTest.res.mjs +432 -0
  129. package/tests/FormatterVsCodeMessageTest.res +41 -0
  130. package/tests/FormatterVsCodeMessageTest.res.mjs +42 -0
  131. package/tests/InboundTranslationGwtTest.res +34 -0
  132. package/tests/InboundTranslationGwtTest.res.mjs +98 -0
  133. package/tests/LocalHostIntegration.res +70 -0
  134. package/tests/LocalHostIntegration.res.mjs +64 -0
  135. package/tests/LocalHostTest.res +71 -0
  136. package/tests/LocalHostTest.res.mjs +63 -0
  137. package/tests/MappingGwtTest.res +348 -0
  138. package/tests/MappingGwtTest.res.mjs +592 -0
  139. package/tests/OutboundTranslationGwtTest.res +59 -0
  140. package/tests/OutboundTranslationGwtTest.res.mjs +138 -0
  141. package/tests/PlatformRunnerTest.res +66 -0
  142. package/tests/PlatformRunnerTest.res.mjs +85 -0
  143. package/tests/PlatformScanTest.res +61 -0
  144. package/tests/PlatformScanTest.res.mjs +56 -0
  145. package/tests/QueryGwtTest.res +161 -0
  146. package/tests/QueryGwtTest.res.mjs +314 -0
  147. package/tests/QueryResolverGwtTest.res +71 -0
  148. package/tests/QueryResolverGwtTest.res.mjs +136 -0
  149. package/tests/RunnerUnitTest.res +219 -0
  150. package/tests/RunnerUnitTest.res.mjs +244 -0
  151. package/tests/SideEffect/FakeOrderNotification.res +23 -0
  152. package/tests/SideEffect/FakeOrderNotification.res.mjs +36 -0
  153. package/tests/SideEffect/FakeOrderNotificationGwtTest.res +35 -0
  154. package/tests/SideEffect/FakeOrderNotificationGwtTest.res.mjs +73 -0
  155. package/tests/SideEffect/MockEmail.res +18 -0
  156. package/tests/SideEffect/MockEmail.res.mjs +47 -0
  157. package/tests/StateChange/ExternalAddCategorySlice_GWT.res +27 -0
  158. package/tests/StateChange/ExternalAddCategorySlice_GWT.res.mjs +70 -0
  159. package/tests/StateChange/WithFixtures_Fixtures.res +9 -0
  160. package/tests/StateChange/WithFixtures_Fixtures.res.mjs +20 -0
  161. package/tests/StateChange/WithFixtures_GWT.res +23 -0
  162. package/tests/StateChange/WithFixtures_GWT.res.mjs +56 -0
  163. package/tests/StateChange/WithManualOpen_Fixtures.res +7 -0
  164. package/tests/StateChange/WithManualOpen_Fixtures.res.mjs +20 -0
  165. package/tests/StateChange/WithManualOpen_GWT.res +17 -0
  166. package/tests/StateChange/WithManualOpen_GWT.res.mjs +56 -0
  167. package/tests/StateChangeSliceGwtTest.res +160 -0
  168. package/tests/StateChangeSliceGwtTest.res.mjs +280 -0
  169. package/tests/StateViewSliceGwtTest.res +64 -0
  170. package/tests/StateViewSliceGwtTest.res.mjs +161 -0
  171. package/tests/WatchDebounceTest.res +78 -0
  172. package/tests/WatchDebounceTest.res.mjs +97 -0
@@ -0,0 +1,80 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
4
+ import * as ChildProcess$ReventlessGwt from "./ChildProcess.res.mjs";
5
+
6
+ let running = {};
7
+
8
+ function isManaging(dir) {
9
+ return running[dir] !== undefined;
10
+ }
11
+
12
+ function spawnWatcher(pkg, onStdout) {
13
+ let match = running[pkg.dir];
14
+ if (match !== undefined) {
15
+ return;
16
+ }
17
+ let proc = ChildProcess$ReventlessGwt.spawn("pnpm", [
18
+ "run",
19
+ "start"
20
+ ], pkg.dir, undefined);
21
+ ChildProcess$ReventlessGwt.onStdoutLine(proc, line => onStdout(pkg.dir, line));
22
+ running[pkg.dir] = {
23
+ dir: pkg.dir,
24
+ proc: proc
25
+ };
26
+ }
27
+
28
+ function stop(dir) {
29
+ let m = running[dir];
30
+ if (m !== undefined) {
31
+ ChildProcess$ReventlessGwt.killTree(m.proc, "SIGTERM");
32
+ Stdlib_Dict.$$delete(running, dir);
33
+ return true;
34
+ } else {
35
+ return false;
36
+ }
37
+ }
38
+
39
+ function cleanRebuild(pkg, onStdout, onDone) {
40
+ let wasManaged = stop(pkg.dir);
41
+ let step = (cmd, args, next) => {
42
+ let proc = ChildProcess$ReventlessGwt.spawn(cmd, args, pkg.dir, undefined);
43
+ ChildProcess$ReventlessGwt.onStdoutLine(proc, line => onStdout(pkg.dir, line));
44
+ ChildProcess$ReventlessGwt.onClose(proc, param => next());
45
+ };
46
+ step("pnpm", [
47
+ "exec",
48
+ "rescript",
49
+ "clean"
50
+ ], () => step("pnpm", [
51
+ "exec",
52
+ "rescript",
53
+ "build"
54
+ ], () => {
55
+ if (wasManaged) {
56
+ spawnWatcher(pkg, onStdout);
57
+ }
58
+ onDone();
59
+ }));
60
+ }
61
+
62
+ function killAll() {
63
+ Object.values(running).forEach(m => ChildProcess$ReventlessGwt.killTree(m.proc, "SIGTERM"));
64
+ Object.keys(running).forEach(k => Stdlib_Dict.$$delete(running, k));
65
+ }
66
+
67
+ function managedCount() {
68
+ return Object.keys(running).length;
69
+ }
70
+
71
+ export {
72
+ running,
73
+ isManaging,
74
+ spawnWatcher,
75
+ stop,
76
+ cleanRebuild,
77
+ killAll,
78
+ managedCount,
79
+ }
80
+ /* ChildProcess-ReventlessGwt Not a pure module */
@@ -0,0 +1,312 @@
1
+ open ReventlessCore
2
+
3
+ // Plan 02 — single-source projection GWT for StateViewSlice (and any future
4
+ // single-source projection target). Promoted from the legacy
5
+ // `StateViewSlice_GWT` so the DSL name now matches the implementation kind
6
+ // emitted by the PPX (`Projection_GWT.Make(Spec, X_Projection)`).
7
+ //
8
+ // Multi-source ReadModel projections (the `FooProjections.res + Mapping.Make`
9
+ // pattern) live in `MultiSourceProjection_GWT` — same DSL surface, different
10
+ // source description.
11
+
12
+ // Minimal inline Spec — mirrors `Reventless.StateViewSlice.Spec` minus the
13
+ // fields the GWT DSL doesn't read (`config`). Declared inline so sury-ppx
14
+ // processes `@schema` in this compilation unit.
15
+ module type Spec = {
16
+ let name: string
17
+
18
+ @schema
19
+ type state
20
+
21
+ let stateSchema: S.t<state>
22
+
23
+ @schema
24
+ type consumedEvent
25
+
26
+ let subIdConfig: option<Reventless.ReadModel.subIdConfig<state>>
27
+ }
28
+
29
+ // Implementation half — one binding (`project`).
30
+ module type Projection = {
31
+ module Spec: Spec
32
+
33
+ let project: Spec.consumedEvent => array<Reventless.Projection.action<string, Spec.state>>
34
+ }
35
+
36
+ module type T = {
37
+ module Spec: Spec
38
+
39
+ let describe: (string, unit => unit) => unit
40
+ let describeWithId: (string, string, unit => unit) => unit
41
+ let todo: string => unit
42
+ let test: (string, ~timeout: int=?, unit => promise<Outcome.outcome>) => unit
43
+
44
+ type store = dict<array<Spec.state>>
45
+ type storeThunk = unit => promise<store>
46
+
47
+ let givenEvents: array<Spec.consumedEvent> => promise<store>
48
+ let whenEvent: (promise<store>, Spec.consumedEvent) => storeThunk
49
+ let whenEvents: (promise<store>, array<Spec.consumedEvent>) => storeThunk
50
+ let thenStates: (storeThunk, array<Spec.state>) => promise<Outcome.outcome>
51
+ let thenStatesWithId: (storeThunk, string, array<Spec.state>) => promise<Outcome.outcome>
52
+ let thenAllStates: (storeThunk, store) => promise<Outcome.outcome>
53
+ let thenState: (storeThunk, Spec.state) => promise<Outcome.outcome>
54
+ let thenStateWithId: (storeThunk, string, Spec.state) => promise<Outcome.outcome>
55
+ let thenNoState: storeThunk => promise<Outcome.outcome>
56
+ let thenThrow: storeThunk => promise<Outcome.outcome>
57
+ let thenFail: storeThunk => promise<Outcome.outcome>
58
+ }
59
+
60
+ let handleActions = Projection.handleActions // local alias to avoid shadowing
61
+
62
+ module Make = (
63
+ Spec: Spec,
64
+ Projection: Projection with module Spec := Spec,
65
+ ): (T with module Spec = Spec) => {
66
+ module Spec = Spec
67
+
68
+ S.enableJson()
69
+
70
+ let testId = ref(TestFixtures.id)
71
+
72
+ let describe = JestBind.describe
73
+ let todo = JestBind.todo
74
+ let describeWithId = (description, id, fn) => {
75
+ testId := id
76
+ JestBind.describe(description, fn)
77
+ }
78
+ let test = (name, ~timeout=?, body) =>
79
+ JestBind.testPromise(~slice=Spec.name, name, ~timeout?, body)
80
+
81
+ type store = dict<array<Spec.state>>
82
+ type storeThunk = unit => promise<store>
83
+
84
+ let getSubId = state => Spec.subIdConfig->Option.map(({getSubId}) => state->getSubId)
85
+ let hasSubId = (state, subId) => state->getSubId->Option.getOrThrow == subId
86
+ let states = (store, id) => store->Dict.get(id)->Option.getOr([])
87
+ let setStates = (store, id, states) => store->Dict.set(id, states)
88
+ let updateState = (store, id, subId, newState) =>
89
+ store->states(id)->Array.map(state => state->hasSubId(subId) ? newState : state)
90
+ let addState = (store, id, newState) => {
91
+ let (updatedStates, newStates) = switch newState->getSubId {
92
+ | Some(subId) =>
93
+ store->states(id)->Array.some(state => state->hasSubId(subId))
94
+ ? (store->updateState(id, subId, newState), [])
95
+ : (store->states(id), [newState])
96
+ | None => (store->states(id), [newState])
97
+ }
98
+ store->Dict.set(id, Array.concat(updatedStates, newStates))
99
+ }
100
+ let deleteStates = (store, id) => store->Dict.set(id, [])
101
+ let deleteSubState = (store, id, subId, getSubId) =>
102
+ store->Dict.set(
103
+ id,
104
+ store
105
+ ->Dict.get(id)
106
+ ->Option.map(states => states->Array.filter(state => state->getSubId != subId))
107
+ ->Option.getOr([]),
108
+ )
109
+
110
+ let load = (store, id) => store->states(id)->Ok->Promise.resolve
111
+ let save = (store, id, state, saveMode: QueryDb.saveMode, _ttl) =>
112
+ switch (store->states(id), saveMode, Spec.subIdConfig) {
113
+ | (_, Any, Some(_))
114
+ | (_, Init, Some(_)) =>
115
+ store->addState(id, state)
116
+ Ok()->Promise.resolve
117
+ | (_, Any, None)
118
+ | ([], Init, _)
119
+ | ([_], Overwrite, _) =>
120
+ store->setStates(id, [state])
121
+ Ok()->Promise.resolve
122
+ | _ => Error(ReventlessInfra.QueryDb.StaleState)->Promise.resolve
123
+ }
124
+ let saveBatch = (store, batch) => {
125
+ batch->Array.forEach(((id, state, _ttl)) => store->addState(id, state))
126
+ Ok()->Promise.resolve
127
+ }
128
+ let delete = (store, id, subId) =>
129
+ switch (subId, Spec.subIdConfig) {
130
+ | (None, _) =>
131
+ store->deleteStates(id)
132
+ Ok()->Promise.resolve
133
+ | (Some((_, subId)), Some({Reventless.ReadModel.getSubId: getSubId})) =>
134
+ store->deleteSubState(id, subId, getSubId)
135
+ Ok()->Promise.resolve
136
+ | _ => Ok()->Promise.resolve
137
+ }
138
+ let deleteBatch = (store, ids) => {
139
+ ids->Array.forEach(((id, subId)) =>
140
+ switch (subId, Spec.subIdConfig) {
141
+ | (None, _) => store->deleteStates(id)
142
+ | (Some((_, subId)), Some({Reventless.ReadModel.getSubId: getSubId})) =>
143
+ store->deleteSubState(id, subId, getSubId)
144
+ | _ => ()
145
+ }
146
+ )
147
+ Ok()->Promise.resolve
148
+ }
149
+
150
+ let runActions = (actions, operations) =>
151
+ actions->handleActions(operations, Spec.subIdConfig)
152
+
153
+ let sortStore = store =>
154
+ switch Spec.subIdConfig {
155
+ | None => store
156
+ | Some(_) =>
157
+ Dict.mapValues(store, states =>
158
+ states->Array.toSorted((s1, s2) =>
159
+ String.compare(s1->getSubId->Option.getUnsafe, s2->getSubId->Option.getUnsafe)
160
+ )
161
+ )
162
+ }
163
+
164
+ let update = async (store, events) => {
165
+ // Projection.project produces an array of actions per event — flatten and
166
+ // feed them through handleActions against the dict store.
167
+ let actions =
168
+ events
169
+ ->Array.map(ev => ev->Projection.project)
170
+ ->Array.flat
171
+ await actions->runActions({
172
+ load: load(store, ...),
173
+ loadStream: id => store->states(id)->Stream.fromIterable,
174
+ save: save(store, ...),
175
+ saveBatch: saveBatch(store, ...),
176
+ count: async (_, _, _) => Ok(0),
177
+ delete: delete(store, ...),
178
+ deleteBatch: deleteBatch(store, ...),
179
+ })
180
+
181
+ store->sortStore
182
+ }
183
+
184
+ let givenEvents = events => Dict.make()->update(events)
185
+
186
+ let whenEvent = (store, event): storeThunk =>
187
+ () => (async () => await (await store)->update([event]))()
188
+ let whenEvents = (store, events): storeThunk =>
189
+ () => (async () => await (await store)->update(events))()
190
+
191
+ let encState = (s: Spec.state) => s->Message.encode(Spec.stateSchema)
192
+ let encStates = (arr: array<Spec.state>) => arr->Array.map(encState)
193
+
194
+ let stateEq = (a: Spec.state, b: Spec.state) =>
195
+ JSON.stringify(encState(a)) == JSON.stringify(encState(b))
196
+ let statesEq = (a, b) =>
197
+ Array.length(a) == Array.length(b) &&
198
+ Array.zip(a, b)->Array.every(((x, y)) => stateEq(x, y))
199
+ let storeEq = (a: store, b: store) => {
200
+ let ka = a->Dict.keysToArray->Array.toSorted(String.compare)
201
+ let kb = b->Dict.keysToArray->Array.toSorted(String.compare)
202
+ ka == kb &&
203
+ ka->Array.every(key =>
204
+ statesEq(a->Dict.get(key)->Option.getOr([]), b->Dict.get(key)->Option.getOr([]))
205
+ )
206
+ }
207
+
208
+ let encStore = (s: store) => {
209
+ let obj = Dict.make()
210
+ s
211
+ ->Dict.toArray
212
+ ->Array.forEach(((k, v)) => obj->Dict.set(k, JSON.Encode.array(v->encStates)))
213
+ JSON.Encode.object(obj)
214
+ }
215
+
216
+ let thenStates = async (thunk, expectedStates) => {
217
+ let store = await thunk()
218
+ let keys = store->Dict.keysToArray
219
+ let actualId = keys->Array.get(0)
220
+ let actualStates = store->Dict.valuesToArray->Array.get(0)->Option.getOr([])
221
+ if (
222
+ keys->Array.length == 1 &&
223
+ actualId == Some(testId.contents) &&
224
+ statesEq(actualStates, expectedStates)
225
+ ) {
226
+ Outcome.pass
227
+ } else {
228
+ Outcome.fail(
229
+ StateMismatch({
230
+ key: testId.contents,
231
+ expected: Some(JSON.Encode.array(expectedStates->encStates)),
232
+ actual: Some(encStore(store)),
233
+ }),
234
+ )
235
+ }
236
+ }
237
+
238
+ let thenStatesWithId = async (thunk, id, expectedStates) => {
239
+ let store = await thunk()
240
+ let keys = store->Dict.keysToArray
241
+ let actualId = keys->Array.get(0)
242
+ let actualStates = store->Dict.valuesToArray->Array.get(0)->Option.getOr([])
243
+ if keys->Array.length == 1 && actualId == Some(id) && statesEq(actualStates, expectedStates) {
244
+ Outcome.pass
245
+ } else {
246
+ Outcome.fail(
247
+ StateMismatch({
248
+ key: id,
249
+ expected: Some(JSON.Encode.array(expectedStates->encStates)),
250
+ actual: Some(encStore(store)),
251
+ }),
252
+ )
253
+ }
254
+ }
255
+
256
+ let thenAllStates = async (thunk, expectedStore: store) => {
257
+ let store = await thunk()
258
+ if storeEq(store, expectedStore) {
259
+ Outcome.pass
260
+ } else {
261
+ Outcome.fail(
262
+ StateMismatch({
263
+ key: "<all>",
264
+ expected: Some(encStore(expectedStore)),
265
+ actual: Some(encStore(store)),
266
+ }),
267
+ )
268
+ }
269
+ }
270
+
271
+ let thenState = (thunk, expectedState) => thenStates(thunk, [expectedState])
272
+ let thenStateWithId = (thunk, id, expectedState) =>
273
+ thenStatesWithId(thunk, id, [expectedState])
274
+
275
+ let thenNoState = async thunk => {
276
+ let store = await thunk()
277
+ let total =
278
+ store->Dict.valuesToArray->Array.reduce(0, (acc, states) => acc + states->Array.length)
279
+ if total == 0 {
280
+ Outcome.pass
281
+ } else {
282
+ Outcome.fail(
283
+ StateMismatch({
284
+ key: "<any>",
285
+ expected: None,
286
+ actual: Some(encStore(store)),
287
+ }),
288
+ )
289
+ }
290
+ }
291
+
292
+ let thenThrow = async thunk =>
293
+ switch await thunk() {
294
+ | _ =>
295
+ Outcome.fail(
296
+ Throw({
297
+ error: "Expected thunk to throw but it returned normally",
298
+ stack: "",
299
+ }),
300
+ )
301
+ | exception _ => Outcome.pass
302
+ }
303
+
304
+ let thenFail = async thunk =>
305
+ switch await thunk() {
306
+ | _ =>
307
+ Outcome.fail(
308
+ Throw({error: "Expected failure but thunk returned normally", stack: ""}),
309
+ )
310
+ | exception _ => Outcome.pass
311
+ }
312
+ }