@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,354 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
|
|
5
|
+
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
6
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
7
|
+
import * as Stream from "effect/Stream";
|
|
8
|
+
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
9
|
+
import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
|
|
10
|
+
import * as Outcome$ReventlessGwt from "./Outcome.res.mjs";
|
|
11
|
+
import * as JestBind$ReventlessGwt from "./JestBind.res.mjs";
|
|
12
|
+
import * as Message$ReventlessCore from "@reventlessdev/reventless-core/src/Message.res.mjs";
|
|
13
|
+
import * as Projection$ReventlessCore from "@reventlessdev/reventless-core/src/Projection.res.mjs";
|
|
14
|
+
import * as TestFixtures$ReventlessGwt from "./TestFixtures.res.mjs";
|
|
15
|
+
|
|
16
|
+
function Make(Projection) {
|
|
17
|
+
S.enableJson();
|
|
18
|
+
let testId = {
|
|
19
|
+
contents: TestFixtures$ReventlessGwt.id
|
|
20
|
+
};
|
|
21
|
+
let meta = {
|
|
22
|
+
contents: TestFixtures$ReventlessGwt.meta
|
|
23
|
+
};
|
|
24
|
+
let describeWithId = (description, id, fn) => {
|
|
25
|
+
testId.contents = id;
|
|
26
|
+
JestBind$ReventlessGwt.describe(description, fn);
|
|
27
|
+
};
|
|
28
|
+
let test = (name, timeout, body) => JestBind$ReventlessGwt.testPromise(Projection.sourceName, name, timeout, body);
|
|
29
|
+
let getSubId = state => Stdlib_Option.map(Projection.subIdConfig, param => param.getSubId(state));
|
|
30
|
+
let hasSubId = (state, subId) => Stdlib_Option.getOrThrow(getSubId(state), undefined) === subId;
|
|
31
|
+
let states = (store, id) => Stdlib_Option.getOr(store[id], []);
|
|
32
|
+
let setStates = (store, id, states) => {
|
|
33
|
+
store[id] = states;
|
|
34
|
+
};
|
|
35
|
+
let updateState = (store, id, subId, newState) => states(store, id).map(state => {
|
|
36
|
+
if (hasSubId(state, subId)) {
|
|
37
|
+
return newState;
|
|
38
|
+
} else {
|
|
39
|
+
return state;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
let deleteStates = (store, id) => {
|
|
43
|
+
store[id] = [];
|
|
44
|
+
};
|
|
45
|
+
let deleteSubState = (store, id, subId, getSubId) => {
|
|
46
|
+
store[id] = Stdlib_Option.getOr(Stdlib_Option.map(store[id], states => states.filter(state => Primitive_object.notequal(getSubId(state), subId))), []);
|
|
47
|
+
};
|
|
48
|
+
let update = async (store, events$p) => {
|
|
49
|
+
let actions = events$p.map(event$p => Projection.project(event$p));
|
|
50
|
+
await Projection$ReventlessCore.handleActions(actions, {
|
|
51
|
+
load: extra => Promise.resolve({
|
|
52
|
+
TAG: "Ok",
|
|
53
|
+
_0: states(store, extra)
|
|
54
|
+
}),
|
|
55
|
+
loadStream: id => Stream.fromIterable(states(store, id)),
|
|
56
|
+
save: (extra, extra$1, extra$2, extra$3) => {
|
|
57
|
+
let match = states(store, extra);
|
|
58
|
+
let exit = 0;
|
|
59
|
+
switch (extra$2) {
|
|
60
|
+
case "Init" :
|
|
61
|
+
case "Overwrite" :
|
|
62
|
+
exit = 2;
|
|
63
|
+
break;
|
|
64
|
+
case "Any" :
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
if (exit === 2) {
|
|
68
|
+
let len = match.length;
|
|
69
|
+
if (len !== 1) {
|
|
70
|
+
if (len !== 0) {
|
|
71
|
+
return Promise.resolve({
|
|
72
|
+
TAG: "Error",
|
|
73
|
+
_0: "StaleState"
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
if (extra$2 !== "Init") {
|
|
77
|
+
return Promise.resolve({
|
|
78
|
+
TAG: "Error",
|
|
79
|
+
_0: "StaleState"
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
} else if (extra$2 === "Init") {
|
|
83
|
+
return Promise.resolve({
|
|
84
|
+
TAG: "Error",
|
|
85
|
+
_0: "StaleState"
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
setStates(store, extra, [extra$1]);
|
|
90
|
+
return Promise.resolve({
|
|
91
|
+
TAG: "Ok",
|
|
92
|
+
_0: undefined
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
saveBatch: extra => {
|
|
96
|
+
extra.forEach(param => {
|
|
97
|
+
let id = param[0];
|
|
98
|
+
let newState = param[1];
|
|
99
|
+
let subId = getSubId(newState);
|
|
100
|
+
let match = subId !== undefined ? (
|
|
101
|
+
states(store, id).some(state => hasSubId(state, subId)) ? [
|
|
102
|
+
updateState(store, id, subId, newState),
|
|
103
|
+
[]
|
|
104
|
+
] : [
|
|
105
|
+
states(store, id),
|
|
106
|
+
[newState]
|
|
107
|
+
]
|
|
108
|
+
) : [
|
|
109
|
+
states(store, id),
|
|
110
|
+
[newState]
|
|
111
|
+
];
|
|
112
|
+
store[id] = match[0].concat(match[1]);
|
|
113
|
+
});
|
|
114
|
+
return Promise.resolve({
|
|
115
|
+
TAG: "Ok",
|
|
116
|
+
_0: undefined
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
count: async (param, param$1, param$2) => ({
|
|
120
|
+
TAG: "Ok",
|
|
121
|
+
_0: 0
|
|
122
|
+
}),
|
|
123
|
+
delete: (extra, extra$1) => {
|
|
124
|
+
let match = Projection.subIdConfig;
|
|
125
|
+
if (extra$1 !== undefined) {
|
|
126
|
+
if (match !== undefined) {
|
|
127
|
+
deleteSubState(store, extra, extra$1[1], match.getSubId);
|
|
128
|
+
return Promise.resolve({
|
|
129
|
+
TAG: "Ok",
|
|
130
|
+
_0: undefined
|
|
131
|
+
});
|
|
132
|
+
} else {
|
|
133
|
+
return Promise.resolve({
|
|
134
|
+
TAG: "Ok",
|
|
135
|
+
_0: undefined
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
deleteStates(store, extra);
|
|
140
|
+
return Promise.resolve({
|
|
141
|
+
TAG: "Ok",
|
|
142
|
+
_0: undefined
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
deleteBatch: extra => {
|
|
147
|
+
extra.forEach(param => {
|
|
148
|
+
let subId = param[1];
|
|
149
|
+
let id = param[0];
|
|
150
|
+
let match = Projection.subIdConfig;
|
|
151
|
+
if (subId !== undefined) {
|
|
152
|
+
if (match !== undefined) {
|
|
153
|
+
return deleteSubState(store, id, subId[1], match.getSubId);
|
|
154
|
+
} else {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
return deleteStates(store, id);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
return Promise.resolve({
|
|
162
|
+
TAG: "Ok",
|
|
163
|
+
_0: undefined
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}, Projection.subIdConfig);
|
|
167
|
+
if (Projection.subIdConfig !== undefined) {
|
|
168
|
+
return Stdlib_Dict.mapValues(store, states => states.toSorted((state1, state2) => Primitive_string.compare(getSubId(state1), getSubId(state2))));
|
|
169
|
+
} else {
|
|
170
|
+
return store;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
let givenEvents = events => update({}, events.map(event => ({
|
|
174
|
+
id: testId.contents,
|
|
175
|
+
meta: meta.contents,
|
|
176
|
+
event: event
|
|
177
|
+
})));
|
|
178
|
+
let givenEventsWithTime = events => update({}, events.map(param => {
|
|
179
|
+
let newrecord = {...meta.contents};
|
|
180
|
+
return {
|
|
181
|
+
id: testId.contents,
|
|
182
|
+
meta: (newrecord.time = param[0], newrecord),
|
|
183
|
+
event: param[1]
|
|
184
|
+
};
|
|
185
|
+
}));
|
|
186
|
+
let whenEvent = (store, event) => (() => (async () => await update(await store, [{
|
|
187
|
+
id: testId.contents,
|
|
188
|
+
meta: meta.contents,
|
|
189
|
+
event: event
|
|
190
|
+
}]))());
|
|
191
|
+
let whenEvents = (store, events) => (() => (async () => await update(await store, events.map(event => ({
|
|
192
|
+
id: testId.contents,
|
|
193
|
+
meta: meta.contents,
|
|
194
|
+
event: event
|
|
195
|
+
}))))());
|
|
196
|
+
let whenEventWithTime = (store, time, event) => (() => (async () => {
|
|
197
|
+
let newrecord = {...meta.contents};
|
|
198
|
+
return await update(await store, [{
|
|
199
|
+
id: testId.contents,
|
|
200
|
+
meta: (newrecord.time = time, newrecord),
|
|
201
|
+
event: event
|
|
202
|
+
}]);
|
|
203
|
+
})());
|
|
204
|
+
let whenEventsWithTime = (store, events) => (() => (async () => await update(await store, events.map(param => {
|
|
205
|
+
let newrecord = {...meta.contents};
|
|
206
|
+
return {
|
|
207
|
+
id: testId.contents,
|
|
208
|
+
meta: (newrecord.time = param[0], newrecord),
|
|
209
|
+
event: param[1]
|
|
210
|
+
};
|
|
211
|
+
})))());
|
|
212
|
+
let encState = s => Message$ReventlessCore.encode(s, Projection.targetStateSchema);
|
|
213
|
+
let statesEq = (a, b) => {
|
|
214
|
+
if (a.length === b.length) {
|
|
215
|
+
return Stdlib_Array.zip(a, b).every(param => {
|
|
216
|
+
let a = param[0];
|
|
217
|
+
let b = param[1];
|
|
218
|
+
return JSON.stringify(Message$ReventlessCore.encode(a, Projection.targetStateSchema)) === JSON.stringify(Message$ReventlessCore.encode(b, Projection.targetStateSchema));
|
|
219
|
+
});
|
|
220
|
+
} else {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
let storeEq = (a, b) => {
|
|
225
|
+
let ka = Object.keys(a).toSorted(Primitive_string.compare);
|
|
226
|
+
let kb = Object.keys(b).toSorted(Primitive_string.compare);
|
|
227
|
+
if (Primitive_object.equal(ka, kb)) {
|
|
228
|
+
return ka.every(key => statesEq(Stdlib_Option.getOr(a[key], []), Stdlib_Option.getOr(b[key], [])));
|
|
229
|
+
} else {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
let encStore = s => {
|
|
234
|
+
let obj = {};
|
|
235
|
+
Object.entries(s).forEach(param => {
|
|
236
|
+
obj[param[0]] = param[1].map(encState);
|
|
237
|
+
});
|
|
238
|
+
return obj;
|
|
239
|
+
};
|
|
240
|
+
let thenStates = async (thunk, expectedStates) => {
|
|
241
|
+
let store = await thunk();
|
|
242
|
+
let keys = Object.keys(store);
|
|
243
|
+
let actualId = keys[0];
|
|
244
|
+
let actualStates = Stdlib_Option.getOr(Object.values(store)[0], []);
|
|
245
|
+
if (keys.length === 1 && Primitive_object.equal(actualId, testId.contents) && statesEq(actualStates, expectedStates)) {
|
|
246
|
+
return Outcome$ReventlessGwt.pass;
|
|
247
|
+
} else {
|
|
248
|
+
return Outcome$ReventlessGwt.fail({
|
|
249
|
+
TAG: "StateMismatch",
|
|
250
|
+
key: testId.contents,
|
|
251
|
+
expected: expectedStates.map(encState),
|
|
252
|
+
actual: encStore(store)
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
let thenStatesWithId = async (thunk, id, expectedStates) => {
|
|
257
|
+
let store = await thunk();
|
|
258
|
+
let keys = Object.keys(store);
|
|
259
|
+
let actualId = keys[0];
|
|
260
|
+
let actualStates = Stdlib_Option.getOr(Object.values(store)[0], []);
|
|
261
|
+
if (keys.length === 1 && Primitive_object.equal(actualId, id) && statesEq(actualStates, expectedStates)) {
|
|
262
|
+
return Outcome$ReventlessGwt.pass;
|
|
263
|
+
} else {
|
|
264
|
+
return Outcome$ReventlessGwt.fail({
|
|
265
|
+
TAG: "StateMismatch",
|
|
266
|
+
key: id,
|
|
267
|
+
expected: expectedStates.map(encState),
|
|
268
|
+
actual: encStore(store)
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
let thenAllStates = async (thunk, expectedStore) => {
|
|
273
|
+
let store = await thunk();
|
|
274
|
+
if (storeEq(store, expectedStore)) {
|
|
275
|
+
return Outcome$ReventlessGwt.pass;
|
|
276
|
+
} else {
|
|
277
|
+
return Outcome$ReventlessGwt.fail({
|
|
278
|
+
TAG: "StateMismatch",
|
|
279
|
+
key: "<all>",
|
|
280
|
+
expected: encStore(expectedStore),
|
|
281
|
+
actual: encStore(store)
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
let thenState = (thunk, expectedState) => thenStates(thunk, [expectedState]);
|
|
286
|
+
let thenStateWithId = (thunk, id, expectedState) => thenStatesWithId(thunk, id, [expectedState]);
|
|
287
|
+
let thenNoState = async thunk => {
|
|
288
|
+
let store = await thunk();
|
|
289
|
+
let total = Stdlib_Array.reduce(Object.values(store), 0, (acc, states) => acc + states.length | 0);
|
|
290
|
+
if (total === 0) {
|
|
291
|
+
return Outcome$ReventlessGwt.pass;
|
|
292
|
+
} else {
|
|
293
|
+
return Outcome$ReventlessGwt.fail({
|
|
294
|
+
TAG: "StateMismatch",
|
|
295
|
+
key: "<any>",
|
|
296
|
+
expected: undefined,
|
|
297
|
+
actual: encStore(store)
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
let thenThrow = async thunk => {
|
|
302
|
+
let val;
|
|
303
|
+
try {
|
|
304
|
+
val = await thunk();
|
|
305
|
+
} catch (exn) {
|
|
306
|
+
return Outcome$ReventlessGwt.pass;
|
|
307
|
+
}
|
|
308
|
+
return Outcome$ReventlessGwt.fail({
|
|
309
|
+
TAG: "Throw",
|
|
310
|
+
error: "Expected thunk to throw but it returned normally",
|
|
311
|
+
stack: ""
|
|
312
|
+
});
|
|
313
|
+
};
|
|
314
|
+
let thenFail = async thunk => {
|
|
315
|
+
let val;
|
|
316
|
+
try {
|
|
317
|
+
val = await thunk();
|
|
318
|
+
} catch (exn) {
|
|
319
|
+
return Outcome$ReventlessGwt.pass;
|
|
320
|
+
}
|
|
321
|
+
return Outcome$ReventlessGwt.fail({
|
|
322
|
+
TAG: "Throw",
|
|
323
|
+
error: "Expected failure but thunk returned normally",
|
|
324
|
+
stack: ""
|
|
325
|
+
});
|
|
326
|
+
};
|
|
327
|
+
return {
|
|
328
|
+
describe: JestBind$ReventlessGwt.describe,
|
|
329
|
+
describeWithId: describeWithId,
|
|
330
|
+
test: test,
|
|
331
|
+
givenEvents: givenEvents,
|
|
332
|
+
givenEventsWithTime: givenEventsWithTime,
|
|
333
|
+
whenEvent: whenEvent,
|
|
334
|
+
whenEvents: whenEvents,
|
|
335
|
+
whenEventWithTime: whenEventWithTime,
|
|
336
|
+
whenEventsWithTime: whenEventsWithTime,
|
|
337
|
+
thenStates: thenStates,
|
|
338
|
+
thenStatesWithId: thenStatesWithId,
|
|
339
|
+
thenAllStates: thenAllStates,
|
|
340
|
+
thenState: thenState,
|
|
341
|
+
thenStateWithId: thenStateWithId,
|
|
342
|
+
thenNoState: thenNoState,
|
|
343
|
+
thenThrow: thenThrow,
|
|
344
|
+
thenFail: thenFail
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
let handleActions = Projection$ReventlessCore.handleActions;
|
|
349
|
+
|
|
350
|
+
export {
|
|
351
|
+
handleActions,
|
|
352
|
+
Make,
|
|
353
|
+
}
|
|
354
|
+
/* S Not a pure module */
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
open ReventlessCore
|
|
2
|
+
|
|
3
|
+
// Minimal inline spec for OutboundTranslationSlice. `translate` is async and
|
|
4
|
+
// supplied at test-time via `whenTranslateMocked` so the external service is
|
|
5
|
+
// never hit. The spec's own `translate` is not part of the GWT — it's
|
|
6
|
+
// exercised only through the runtime tests.
|
|
7
|
+
module type SliceSpec = {
|
|
8
|
+
let name: string
|
|
9
|
+
|
|
10
|
+
@schema
|
|
11
|
+
type consumedEvent
|
|
12
|
+
|
|
13
|
+
@schema
|
|
14
|
+
type outboundItem
|
|
15
|
+
|
|
16
|
+
@schema
|
|
17
|
+
type inboundCommand
|
|
18
|
+
|
|
19
|
+
let collect: consumedEvent => array<(string, outboundItem)>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Status enum reflecting how the slice's runtime labels a TODO after a
|
|
23
|
+
// translate call. A successful translate (Ok) marks the item #Completed;
|
|
24
|
+
// a failure (Error) leaves it #Pending for retry up to `maxRetries`.
|
|
25
|
+
type todoStatus = [#Completed | #Pending]
|
|
26
|
+
|
|
27
|
+
module type T = {
|
|
28
|
+
module Spec: SliceSpec
|
|
29
|
+
|
|
30
|
+
let describe: (string, unit => unit) => unit
|
|
31
|
+
let test: (string, ~timeout: int=?, unit => promise<Outcome.outcome>) => unit
|
|
32
|
+
// Sync companion for the `collect` leg, whose combinators return
|
|
33
|
+
// `Outcome.outcome` directly (no Promise). The async `test` works for
|
|
34
|
+
// collect bodies too, but only after a manual `Promise.resolve` wrapper.
|
|
35
|
+
let testSync: (string, unit => Outcome.outcome) => unit
|
|
36
|
+
|
|
37
|
+
type translateResult =
|
|
38
|
+
result<option<(string, Spec.inboundCommand)>, string>
|
|
39
|
+
|
|
40
|
+
// The state the pipeline carries after a translate attempt. `retries` counts
|
|
41
|
+
// the number of failed re-attempts (0 for a single `whenTranslateMocked`).
|
|
42
|
+
type attempt = {
|
|
43
|
+
id: string,
|
|
44
|
+
item: Spec.outboundItem,
|
|
45
|
+
result: translateResult,
|
|
46
|
+
retries: int,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Unit — collect
|
|
50
|
+
let givenEvent: Spec.consumedEvent => Spec.consumedEvent
|
|
51
|
+
let whenCollect: Spec.consumedEvent => array<(string, Spec.outboundItem)>
|
|
52
|
+
let thenTodos: (
|
|
53
|
+
array<(string, Spec.outboundItem)>,
|
|
54
|
+
array<(string, Spec.outboundItem)>,
|
|
55
|
+
) => Outcome.outcome
|
|
56
|
+
|
|
57
|
+
// Unit — translate
|
|
58
|
+
let givenTodo: (string, Spec.outboundItem) => (string, Spec.outboundItem)
|
|
59
|
+
let whenTranslateMocked: (
|
|
60
|
+
(string, Spec.outboundItem),
|
|
61
|
+
(string, Spec.outboundItem) => promise<translateResult>,
|
|
62
|
+
) => promise<attempt>
|
|
63
|
+
// Re-invokes the mock on each `Error` up to `maxRetries` times (or until it
|
|
64
|
+
// returns `Ok`), tracking how many retries were spent — the real counter
|
|
65
|
+
// `thenRetryRecorded` asserts against.
|
|
66
|
+
let whenTranslateRetrying: (
|
|
67
|
+
(string, Spec.outboundItem),
|
|
68
|
+
~maxRetries: int,
|
|
69
|
+
(string, Spec.outboundItem) => promise<translateResult>,
|
|
70
|
+
) => promise<attempt>
|
|
71
|
+
let thenCommand: (
|
|
72
|
+
promise<attempt>,
|
|
73
|
+
string,
|
|
74
|
+
Spec.inboundCommand,
|
|
75
|
+
) => promise<Outcome.outcome>
|
|
76
|
+
let thenNoCommand: promise<attempt> => promise<Outcome.outcome>
|
|
77
|
+
let thenRetryRecorded: (promise<attempt>, int) => promise<Outcome.outcome>
|
|
78
|
+
let thenTodoStatus: (promise<attempt>, string, todoStatus) => promise<Outcome.outcome>
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module Make = (Spec: SliceSpec): (T with module Spec = Spec) => {
|
|
82
|
+
module Spec = Spec
|
|
83
|
+
|
|
84
|
+
S.enableJson()
|
|
85
|
+
|
|
86
|
+
let describe = JestBind.describe
|
|
87
|
+
let test = (name, ~timeout=?, body) =>
|
|
88
|
+
JestBind.testPromise(~slice=Spec.name, name, ~timeout?, body)
|
|
89
|
+
let testSync = (name, body) => JestBind.test(~slice=Spec.name, name, body)
|
|
90
|
+
|
|
91
|
+
type translateResult =
|
|
92
|
+
result<option<(string, Spec.inboundCommand)>, string>
|
|
93
|
+
|
|
94
|
+
type attempt = {
|
|
95
|
+
id: string,
|
|
96
|
+
item: Spec.outboundItem,
|
|
97
|
+
result: translateResult,
|
|
98
|
+
retries: int,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let encItem = (i: Spec.outboundItem) => i->Message.encode(Spec.outboundItemSchema)
|
|
102
|
+
let encItems = (arr: array<(string, Spec.outboundItem)>) =>
|
|
103
|
+
arr->Array.map(((id, i)) => (id, encItem(i)))
|
|
104
|
+
let encInbound = (c: Spec.inboundCommand) =>
|
|
105
|
+
c->Message.encode(Spec.inboundCommandSchema)
|
|
106
|
+
|
|
107
|
+
// Unit: collect
|
|
108
|
+
let givenEvent = e => e
|
|
109
|
+
let whenCollect = e => e->Spec.collect
|
|
110
|
+
let thenTodos = (actual, expected) =>
|
|
111
|
+
if actual == expected {
|
|
112
|
+
Outcome.pass
|
|
113
|
+
} else {
|
|
114
|
+
Outcome.fail(
|
|
115
|
+
TodoMismatch({
|
|
116
|
+
expected: encItems(expected),
|
|
117
|
+
actual: encItems(actual),
|
|
118
|
+
}),
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Unit: translate
|
|
123
|
+
let givenTodo = (id, item) => (id, item)
|
|
124
|
+
let whenTranslateMocked = async ((id, item), mock) => {
|
|
125
|
+
let result = await mock(id, item)
|
|
126
|
+
{id, item, result, retries: 0}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let whenTranslateRetrying = async ((id, item), ~maxRetries, mock) => {
|
|
130
|
+
let result = ref(await mock(id, item))
|
|
131
|
+
let retries = ref(0)
|
|
132
|
+
let failed = () =>
|
|
133
|
+
switch result.contents {
|
|
134
|
+
| Error(_) => true
|
|
135
|
+
| Ok(_) => false
|
|
136
|
+
}
|
|
137
|
+
while failed() && retries.contents < maxRetries {
|
|
138
|
+
retries := retries.contents + 1
|
|
139
|
+
result := await mock(id, item)
|
|
140
|
+
}
|
|
141
|
+
{id, item, result: result.contents, retries: retries.contents}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let commandPairJson = (id, cmd) => {
|
|
145
|
+
let d = Dict.make()
|
|
146
|
+
d->Dict.set("id", JSON.Encode.string(id))
|
|
147
|
+
d->Dict.set("command", encInbound(cmd))
|
|
148
|
+
JSON.Encode.object(d)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let thenCommand = async (pending, expectedId, expectedCmd) => {
|
|
152
|
+
let {result, _} = await pending
|
|
153
|
+
switch result {
|
|
154
|
+
| Ok(Some((id, cmd))) if id == expectedId && cmd == expectedCmd => Outcome.pass
|
|
155
|
+
| Ok(Some((id, cmd))) =>
|
|
156
|
+
Outcome.fail(
|
|
157
|
+
EventsMismatch({
|
|
158
|
+
expected: [commandPairJson(expectedId, expectedCmd)],
|
|
159
|
+
actual: [commandPairJson(id, cmd)],
|
|
160
|
+
}),
|
|
161
|
+
)
|
|
162
|
+
| Ok(None) =>
|
|
163
|
+
Outcome.fail(
|
|
164
|
+
EventsMismatch({
|
|
165
|
+
expected: [commandPairJson(expectedId, expectedCmd)],
|
|
166
|
+
actual: [],
|
|
167
|
+
}),
|
|
168
|
+
)
|
|
169
|
+
| Error(msg) =>
|
|
170
|
+
Outcome.fail(
|
|
171
|
+
TranslateError({expected: "(command)", actual: Some(msg)}),
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
let thenNoCommand = async pending => {
|
|
177
|
+
let {result, _} = await pending
|
|
178
|
+
switch result {
|
|
179
|
+
| Ok(None) => Outcome.pass
|
|
180
|
+
| Ok(Some((id, cmd))) =>
|
|
181
|
+
Outcome.fail(NoEventExpected({actual: [commandPairJson(id, cmd)]}))
|
|
182
|
+
| Error(msg) =>
|
|
183
|
+
Outcome.fail(
|
|
184
|
+
TranslateError({expected: "(no command)", actual: Some(msg)}),
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// `thenRetryRecorded(n)` — asserts the harness spent exactly `n` retries.
|
|
190
|
+
// Pair with `whenTranslateRetrying(~maxRetries)`, which re-invokes the mock on
|
|
191
|
+
// each failure and tracks the count; a single `whenTranslateMocked` records 0.
|
|
192
|
+
let thenRetryRecorded = async (pending, expectedRetries) => {
|
|
193
|
+
let {retries, _} = await pending
|
|
194
|
+
if retries == expectedRetries {
|
|
195
|
+
Outcome.pass
|
|
196
|
+
} else {
|
|
197
|
+
Outcome.fail(
|
|
198
|
+
StateMismatch({
|
|
199
|
+
key: "retries",
|
|
200
|
+
expected: Some(JSON.Encode.int(expectedRetries)),
|
|
201
|
+
actual: Some(JSON.Encode.int(retries)),
|
|
202
|
+
}),
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let statusToString = (s: todoStatus) =>
|
|
208
|
+
switch s {
|
|
209
|
+
| #Completed => "#Completed"
|
|
210
|
+
| #Pending => "#Pending"
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
let thenTodoStatus = async (pending, expectedId, expectedStatus) => {
|
|
214
|
+
let {id, result, _} = await pending
|
|
215
|
+
let actualStatus: todoStatus =
|
|
216
|
+
switch result {
|
|
217
|
+
| Ok(_) => #Completed
|
|
218
|
+
| Error(_) => #Pending
|
|
219
|
+
}
|
|
220
|
+
if id == expectedId && actualStatus == expectedStatus {
|
|
221
|
+
Outcome.pass
|
|
222
|
+
} else {
|
|
223
|
+
let encOne = (rid, rstatus) => {
|
|
224
|
+
let d = Dict.make()
|
|
225
|
+
d->Dict.set("id", JSON.Encode.string(rid))
|
|
226
|
+
d->Dict.set("status", JSON.Encode.string(statusToString(rstatus)))
|
|
227
|
+
JSON.Encode.object(d)
|
|
228
|
+
}
|
|
229
|
+
Outcome.fail(
|
|
230
|
+
StateMismatch({
|
|
231
|
+
key: id,
|
|
232
|
+
expected: Some(encOne(expectedId, expectedStatus)),
|
|
233
|
+
actual: Some(encOne(id, actualStatus)),
|
|
234
|
+
}),
|
|
235
|
+
)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|