@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.
- package/CHANGELOG.md +698 -0
- package/LICENSE +202 -0
- package/bin/reventless-dev.mjs +21 -0
- package/package.json +54 -0
- package/rescript.json +36 -0
- package/src/.gwtignore +10 -0
- package/src/Automation_GWT.res +207 -0
- package/src/Automation_GWT.res.mjs +160 -0
- package/src/Behavior_GWT.res +603 -0
- package/src/Behavior_GWT.res.mjs +426 -0
- package/src/Bind.res +29 -0
- package/src/Bind.res.mjs +22 -0
- package/src/BuildClassifier.res +137 -0
- package/src/BuildClassifier.res.mjs +136 -0
- package/src/Cancellation.res +31 -0
- package/src/Cancellation.res.mjs +45 -0
- package/src/ChildProcess.res +84 -0
- package/src/ChildProcess.res.mjs +92 -0
- package/src/Cli.res +671 -0
- package/src/Cli.res.mjs +754 -0
- package/src/Collector.res +201 -0
- package/src/Collector.res.mjs +215 -0
- package/src/ComponentMeta.res +104 -0
- package/src/ComponentMeta.res.mjs +120 -0
- package/src/ComponentScan.res +103 -0
- package/src/ComponentScan.res.mjs +105 -0
- package/src/Delegate_GWT.res +303 -0
- package/src/Delegate_GWT.res.mjs +372 -0
- package/src/Diff.res +91 -0
- package/src/Diff.res.mjs +98 -0
- package/src/Discovery.res +105 -0
- package/src/Discovery.res.mjs +103 -0
- package/src/DomainDeadCode.res +93 -0
- package/src/DomainDeadCode.res.mjs +69 -0
- package/src/DomainGraph.res +0 -0
- package/src/DomainGraph.res.mjs +0 -0
- package/src/EventMapping_GWT.res +29 -0
- package/src/EventMapping_GWT.res.mjs +44 -0
- package/src/Filter.res +36 -0
- package/src/Filter.res.mjs +40 -0
- package/src/Flow_GWT.res +664 -0
- package/src/Flow_GWT.res.mjs +604 -0
- package/src/FormatterHuman.res +138 -0
- package/src/FormatterHuman.res.mjs +122 -0
- package/src/FormatterJson.res +257 -0
- package/src/FormatterJson.res.mjs +320 -0
- package/src/FormatterJunit.res +93 -0
- package/src/FormatterJunit.res.mjs +90 -0
- package/src/FormatterTap.res +139 -0
- package/src/FormatterTap.res.mjs +149 -0
- package/src/FormatterVsCode.res +362 -0
- package/src/FormatterVsCode.res.mjs +564 -0
- package/src/Hint.res +119 -0
- package/src/Hint.res.mjs +124 -0
- package/src/InboundTranslation_GWT.res +83 -0
- package/src/InboundTranslation_GWT.res.mjs +94 -0
- package/src/JestBind.res +70 -0
- package/src/JestBind.res.mjs +63 -0
- package/src/Loader.res +24 -0
- package/src/Loader.res.mjs +23 -0
- package/src/LocalHost.res +135 -0
- package/src/LocalHost.res.mjs +127 -0
- package/src/Mapping_GWT.res +474 -0
- package/src/Mapping_GWT.res.mjs +322 -0
- package/src/MultiSourceProjection_GWT.res +316 -0
- package/src/MultiSourceProjection_GWT.res.mjs +354 -0
- package/src/OutboundTranslation_GWT.res +238 -0
- package/src/OutboundTranslation_GWT.res.mjs +186 -0
- package/src/Outcome.res +161 -0
- package/src/Outcome.res.mjs +140 -0
- package/src/PackageScan.res +80 -0
- package/src/PackageScan.res.mjs +81 -0
- package/src/PlatformRunner.res +160 -0
- package/src/PlatformRunner.res.mjs +155 -0
- package/src/PlatformScan.res +138 -0
- package/src/PlatformScan.res.mjs +131 -0
- package/src/ProcessManager.res +91 -0
- package/src/ProcessManager.res.mjs +80 -0
- package/src/Projection_GWT.res +312 -0
- package/src/Projection_GWT.res.mjs +332 -0
- package/src/Query_GWT.res +379 -0
- package/src/Query_GWT.res.mjs +367 -0
- package/src/RenderRescript.res +125 -0
- package/src/RenderRescript.res.mjs +141 -0
- package/src/RunnerTypes.res +68 -0
- package/src/RunnerTypes.res.mjs +60 -0
- package/src/SideEffect_GWT.res +157 -0
- package/src/SideEffect_GWT.res.mjs +113 -0
- package/src/StateChange/ExternalAddCategorySlice.res +25 -0
- package/src/StateChange/ExternalAddCategorySlice.res.mjs +34 -0
- package/src/StateChange/ExternalAddCategorySlice_Behavior.res +19 -0
- package/src/StateChange/ExternalAddCategorySlice_Behavior.res.mjs +46 -0
- package/src/StateChange/WithFixtures.res +22 -0
- package/src/StateChange/WithFixtures.res.mjs +31 -0
- package/src/StateChange/WithFixtures_Behavior.res +17 -0
- package/src/StateChange/WithFixtures_Behavior.res.mjs +37 -0
- package/src/StateChange/WithManualOpen.res +23 -0
- package/src/StateChange/WithManualOpen.res.mjs +31 -0
- package/src/StateChange/WithManualOpen_Behavior.res +17 -0
- package/src/StateChange/WithManualOpen_Behavior.res.mjs +37 -0
- package/src/StubRuntime.res +53 -0
- package/src/StubRuntime.res.mjs +54 -0
- package/src/TestFixtures.res +16 -0
- package/src/TestFixtures.res.mjs +34 -0
- package/src/Watch.res +92 -0
- package/src/Watch.res.mjs +83 -0
- package/src/WatcherProbe.res +48 -0
- package/src/WatcherProbe.res.mjs +46 -0
- package/tests/AutomationGwtTest.res +71 -0
- package/tests/AutomationGwtTest.res.mjs +189 -0
- package/tests/BuildClassifierTest.res +94 -0
- package/tests/BuildClassifierTest.res.mjs +132 -0
- package/tests/ComponentMetaTest.res +52 -0
- package/tests/ComponentMetaTest.res.mjs +57 -0
- package/tests/DelegateGwtTest.res +487 -0
- package/tests/DelegateGwtTest.res.mjs +891 -0
- package/tests/DiscoveryTest.res +42 -0
- package/tests/DiscoveryTest.res.mjs +43 -0
- package/tests/DomainDeadCodeTest.res +116 -0
- package/tests/DomainDeadCodeTest.res.mjs +155 -0
- package/tests/DomainGraphTest.res +365 -0
- package/tests/DomainGraphTest.res.mjs +413 -0
- package/tests/FlowAggregateGwtTest.res +169 -0
- package/tests/FlowAggregateGwtTest.res.mjs +269 -0
- package/tests/FlowCrossPluginGwtTest.res +340 -0
- package/tests/FlowCrossPluginGwtTest.res.mjs +798 -0
- package/tests/FlowGwtTest.res +222 -0
- package/tests/FlowGwtTest.res.mjs +432 -0
- package/tests/FormatterVsCodeMessageTest.res +41 -0
- package/tests/FormatterVsCodeMessageTest.res.mjs +42 -0
- package/tests/InboundTranslationGwtTest.res +34 -0
- package/tests/InboundTranslationGwtTest.res.mjs +98 -0
- package/tests/LocalHostIntegration.res +70 -0
- package/tests/LocalHostIntegration.res.mjs +64 -0
- package/tests/LocalHostTest.res +71 -0
- package/tests/LocalHostTest.res.mjs +63 -0
- package/tests/MappingGwtTest.res +348 -0
- package/tests/MappingGwtTest.res.mjs +592 -0
- package/tests/OutboundTranslationGwtTest.res +59 -0
- package/tests/OutboundTranslationGwtTest.res.mjs +138 -0
- package/tests/PlatformRunnerTest.res +66 -0
- package/tests/PlatformRunnerTest.res.mjs +85 -0
- package/tests/PlatformScanTest.res +61 -0
- package/tests/PlatformScanTest.res.mjs +56 -0
- package/tests/QueryGwtTest.res +161 -0
- package/tests/QueryGwtTest.res.mjs +314 -0
- package/tests/QueryResolverGwtTest.res +71 -0
- package/tests/QueryResolverGwtTest.res.mjs +136 -0
- package/tests/RunnerUnitTest.res +219 -0
- package/tests/RunnerUnitTest.res.mjs +244 -0
- package/tests/SideEffect/FakeOrderNotification.res +23 -0
- package/tests/SideEffect/FakeOrderNotification.res.mjs +36 -0
- package/tests/SideEffect/FakeOrderNotificationGwtTest.res +35 -0
- package/tests/SideEffect/FakeOrderNotificationGwtTest.res.mjs +73 -0
- package/tests/SideEffect/MockEmail.res +18 -0
- package/tests/SideEffect/MockEmail.res.mjs +47 -0
- package/tests/StateChange/ExternalAddCategorySlice_GWT.res +27 -0
- package/tests/StateChange/ExternalAddCategorySlice_GWT.res.mjs +70 -0
- package/tests/StateChange/WithFixtures_Fixtures.res +9 -0
- package/tests/StateChange/WithFixtures_Fixtures.res.mjs +20 -0
- package/tests/StateChange/WithFixtures_GWT.res +23 -0
- package/tests/StateChange/WithFixtures_GWT.res.mjs +56 -0
- package/tests/StateChange/WithManualOpen_Fixtures.res +7 -0
- package/tests/StateChange/WithManualOpen_Fixtures.res.mjs +20 -0
- package/tests/StateChange/WithManualOpen_GWT.res +17 -0
- package/tests/StateChange/WithManualOpen_GWT.res.mjs +56 -0
- package/tests/StateChangeSliceGwtTest.res +160 -0
- package/tests/StateChangeSliceGwtTest.res.mjs +280 -0
- package/tests/StateViewSliceGwtTest.res +64 -0
- package/tests/StateViewSliceGwtTest.res.mjs +161 -0
- package/tests/WatchDebounceTest.res +78 -0
- 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
|
+
}
|