@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,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
+ }