@reventlessdev/reventless-gwt 1.0.0-alpha.207 → 1.0.0-alpha.209
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 +16 -0
- package/package.json +6 -6
- package/src/Automation_GWT.res +7 -17
- package/src/Behavior_GWT.res +16 -35
- package/src/Delegate_GWT.res +51 -50
- package/src/Diff.res +14 -27
- package/src/EventMapping_GWT.res +1 -3
- package/src/Flow_GWT.res +53 -57
- package/src/Hint.res +1 -4
- package/src/InboundTranslation_GWT.res +4 -10
- package/src/LocalHost.res +13 -9
- package/src/Mapping_GWT.res +6 -23
- package/src/MismatchRender.res +8 -2
- package/src/MultiSourceProjection_GWT.res +6 -15
- package/src/OutboundTranslation_GWT.res +12 -28
- package/src/Outcome.res +15 -15
- package/src/Projection_GWT.res +9 -17
- package/src/Query_GWT.res +28 -19
- package/src/RenderRescript.res +1 -2
- package/src/SideEffect_GWT.res +0 -1
- package/src/StateChange/ExternalAddCategorySlice.res +2 -4
- package/src/StateChange/WithFixtures.res +3 -6
- package/src/StateChange/WithManualOpen.res +3 -6
- package/tests/AutomationGwtTest.res +9 -7
- package/tests/DelegateGwtTest.res +0 -1
- package/tests/FlowAggregateGwtTest.res +2 -5
- package/tests/FlowCrossPluginGwtTest.res +65 -40
- package/tests/FlowGwtTest.res +0 -1
- package/tests/InboundTranslationGwtTest.res +7 -4
- package/tests/LocalHostIntegration.res +29 -12
- package/tests/LocalHostTest.res +15 -8
- package/tests/MappingGwtTest.res +5 -20
- package/tests/OutboundTranslationGwtTest.res +7 -4
- package/tests/QueryGwtTest.res +10 -12
- package/tests/QueryResolverGwtTest.res +4 -4
- package/tests/SideEffect/FakeOrderNotification.res +1 -2
- package/tests/SideEffect/FakeOrderNotificationGwtTest.res +6 -10
- package/tests/StateChange/WithFixtures_GWT.res +0 -1
- package/tests/StateChangeSliceGwtTest.res +22 -21
- package/tests/StateViewSliceGwtTest.res +5 -17
package/src/LocalHost.res
CHANGED
|
@@ -51,7 +51,10 @@ type pluginExports = {"Make": platform => builtPlugin}
|
|
|
51
51
|
let packageNameToPluginName = Reventless.PluginName.fromPackageName
|
|
52
52
|
|
|
53
53
|
let strField = (json, key) =>
|
|
54
|
-
json
|
|
54
|
+
json
|
|
55
|
+
->JSON.Decode.object
|
|
56
|
+
->Option.flatMap(d => d->Dict.get(key))
|
|
57
|
+
->Option.flatMap(JSON.Decode.string)
|
|
55
58
|
|
|
56
59
|
let readJson = path =>
|
|
57
60
|
try Some(NodeFs.readFileSync(path)->JSON.parseOrThrow) catch {
|
|
@@ -63,13 +66,13 @@ let readJson = path =>
|
|
|
63
66
|
// reads the two raw fields with the local node bindings.
|
|
64
67
|
let derivePluginName = (~pluginSrcDir: string): string => {
|
|
65
68
|
let pluginJson = NodePath.join([pluginSrcDir, "plugin.json"])
|
|
66
|
-
let pluginJsonName =
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
: None
|
|
69
|
+
let pluginJsonName = NodeFs.existsSync(pluginJson)
|
|
70
|
+
? readJson(pluginJson)->Option.flatMap(j => strField(j, "name"))
|
|
71
|
+
: None
|
|
70
72
|
let packageJsonName =
|
|
71
|
-
readJson(NodePath.join([NodePath.dirname(pluginSrcDir), "package.json"]))
|
|
72
|
-
|
|
73
|
+
readJson(NodePath.join([NodePath.dirname(pluginSrcDir), "package.json"]))->Option.flatMap(j =>
|
|
74
|
+
strField(j, "name")
|
|
75
|
+
)
|
|
73
76
|
Reventless.PluginName.resolve(~pluginJsonName, ~packageJsonName)
|
|
74
77
|
}
|
|
75
78
|
|
|
@@ -92,8 +95,9 @@ let discover = (~packageDirs: array<string>): array<pluginRef> =>
|
|
|
92
95
|
let localPlatformSpecifier = "@reventlessdev/reventless-local/src/Platform.res.mjs"
|
|
93
96
|
let resolveLocalPlatform = (~fromPackageDir: string): option<string> =>
|
|
94
97
|
try Some(
|
|
95
|
-
NodeModule.createRequire(
|
|
96
|
-
|
|
98
|
+
NodeModule.createRequire(
|
|
99
|
+
NodePath.join([fromPackageDir, "package.json"]),
|
|
100
|
+
)->NodeModule.requireResolve(localPlatformSpecifier),
|
|
97
101
|
) catch {
|
|
98
102
|
| _ => None
|
|
99
103
|
}
|
package/src/Mapping_GWT.res
CHANGED
|
@@ -137,10 +137,7 @@ module type T = {
|
|
|
137
137
|
// A scenario carries the source history plus the target per-id history
|
|
138
138
|
// through the pipe chain. Pipe-first (`->`) places it as the first arg of
|
|
139
139
|
// every subsequent combinator, so the chain reads top-to-bottom.
|
|
140
|
-
type scenario = (
|
|
141
|
-
array<Source.consumedEvent>,
|
|
142
|
-
array<(string, array<Target.consumedEvent>)>,
|
|
143
|
-
)
|
|
140
|
+
type scenario = (array<Source.consumedEvent>, array<(string, array<Target.consumedEvent>)>)
|
|
144
141
|
|
|
145
142
|
let describe: (string, unit => unit) => unit
|
|
146
143
|
let test: (string, ~timeout: int=?, unit => promise<Outcome.outcome>) => unit
|
|
@@ -184,11 +181,7 @@ module Make = (M: Mapping): (T with module Source = M.Source and module Target =
|
|
|
184
181
|
module Source = M.Source
|
|
185
182
|
module Target = M.Target
|
|
186
183
|
|
|
187
|
-
type scenario = (
|
|
188
|
-
array<Source.consumedEvent>,
|
|
189
|
-
array<(string, array<Target.consumedEvent>)>,
|
|
190
|
-
)
|
|
191
|
-
|
|
184
|
+
type scenario = (array<Source.consumedEvent>, array<(string, array<Target.consumedEvent>)>)
|
|
192
185
|
|
|
193
186
|
let describe = JestBind.describe
|
|
194
187
|
let sliceName = `${Source.name}→${Target.name}`
|
|
@@ -377,8 +370,7 @@ module Make = (M: Mapping): (T with module Source = M.Source and module Target =
|
|
|
377
370
|
let ok =
|
|
378
371
|
pairs->Array.length == 1 &&
|
|
379
372
|
switch pairs->Array.get(0) {
|
|
380
|
-
| Some((actualId, [actualEvent])) =>
|
|
381
|
-
actualId == id && actualEvent == expectedTargetEvent
|
|
373
|
+
| Some((actualId, [actualEvent])) => actualId == id && actualEvent == expectedTargetEvent
|
|
382
374
|
| _ => false
|
|
383
375
|
}
|
|
384
376
|
if ok {
|
|
@@ -401,10 +393,7 @@ module Make = (M: Mapping): (T with module Source = M.Source and module Target =
|
|
|
401
393
|
let actualEvents = encDict(actualDict)
|
|
402
394
|
let expectedJson = encSourceError(expectedError)
|
|
403
395
|
switch sourceErrors.contents->Array.get(0) {
|
|
404
|
-
| None =>
|
|
405
|
-
Outcome.fail(
|
|
406
|
-
ErrorMismatch({expected: expectedJson, actual: None, actualEvents}),
|
|
407
|
-
)
|
|
396
|
+
| None => Outcome.fail(ErrorMismatch({expected: expectedJson, actual: None, actualEvents}))
|
|
408
397
|
| Some(actual) if actual != expectedError =>
|
|
409
398
|
Outcome.fail(
|
|
410
399
|
ErrorMismatch({
|
|
@@ -422,10 +411,7 @@ module Make = (M: Mapping): (T with module Source = M.Source and module Target =
|
|
|
422
411
|
let actualEvents = encDict(actualDict)
|
|
423
412
|
let expectedJson = encTargetError(expectedError)
|
|
424
413
|
switch targetErrors.contents->Array.get(0) {
|
|
425
|
-
| None =>
|
|
426
|
-
Outcome.fail(
|
|
427
|
-
ErrorMismatch({expected: expectedJson, actual: None, actualEvents}),
|
|
428
|
-
)
|
|
414
|
+
| None => Outcome.fail(ErrorMismatch({expected: expectedJson, actual: None, actualEvents}))
|
|
429
415
|
| Some(actual) if actual != expectedError =>
|
|
430
416
|
Outcome.fail(
|
|
431
417
|
ErrorMismatch({
|
|
@@ -443,10 +429,7 @@ module Make = (M: Mapping): (T with module Source = M.Source and module Target =
|
|
|
443
429
|
let actualEvents = encDict(actualDict)
|
|
444
430
|
let expectedErrorJson = encTargetError(expectedError)
|
|
445
431
|
switch targetErrors.contents->Array.get(0) {
|
|
446
|
-
| None =>
|
|
447
|
-
Outcome.fail(
|
|
448
|
-
ErrorMismatch({expected: expectedErrorJson, actual: None, actualEvents}),
|
|
449
|
-
)
|
|
432
|
+
| None => Outcome.fail(ErrorMismatch({expected: expectedErrorJson, actual: None, actualEvents}))
|
|
450
433
|
| Some(actual) if actual != expectedError =>
|
|
451
434
|
Outcome.fail(
|
|
452
435
|
ErrorMismatch({
|
package/src/MismatchRender.res
CHANGED
|
@@ -33,7 +33,10 @@ let renderOption = RenderRescript.renderOption
|
|
|
33
33
|
|
|
34
34
|
let normalize = (m: Outcome.mismatch): normalized => {
|
|
35
35
|
let fields = switch m {
|
|
36
|
-
| EventsMismatch({expected, actual}) => [
|
|
36
|
+
| EventsMismatch({expected, actual}) => [
|
|
37
|
+
Expected(renderMany(expected)),
|
|
38
|
+
Actual(renderMany(actual)),
|
|
39
|
+
]
|
|
37
40
|
| ErrorMismatch({expected, actual, actualEvents}) => [
|
|
38
41
|
Expected(`Error(${render(expected)})`),
|
|
39
42
|
Actual(
|
|
@@ -61,7 +64,10 @@ let normalize = (m: Outcome.mismatch): normalized => {
|
|
|
61
64
|
Expected(expected),
|
|
62
65
|
Actual(actual->Option.getOr("(none)")),
|
|
63
66
|
]
|
|
64
|
-
| QueryRowsMismatch({expected, actual}) => [
|
|
67
|
+
| QueryRowsMismatch({expected, actual}) => [
|
|
68
|
+
Expected(renderMany(expected)),
|
|
69
|
+
Actual(renderMany(actual)),
|
|
70
|
+
]
|
|
65
71
|
| PublishedActionsMismatch({expected, actual}) => [
|
|
66
72
|
Expected(renderMany(expected)),
|
|
67
73
|
Actual(renderMany(actual)),
|
|
@@ -32,7 +32,6 @@ let handleActions = Projection.handleActions // create alias to avoid shadowing
|
|
|
32
32
|
module Make = (Projection: Reventless.Projection.Mapping): (
|
|
33
33
|
T with type sourceEvent := Projection.sourceEvent and type targetState := Projection.targetState
|
|
34
34
|
) => {
|
|
35
|
-
|
|
36
35
|
let testId = ref(TestFixtures.id)
|
|
37
36
|
let meta = ref(TestFixtures.meta)
|
|
38
37
|
|
|
@@ -130,10 +129,7 @@ module Make = (Projection: Reventless.Projection.Mapping): (
|
|
|
130
129
|
->Array.map(event' =>
|
|
131
130
|
event'
|
|
132
131
|
->Projection.project
|
|
133
|
-
->ReventlessCore.Projection.rewriteTrail(
|
|
134
|
-
~at=event'.meta.time,
|
|
135
|
-
Projection.targetStateSchema,
|
|
136
|
-
)
|
|
132
|
+
->ReventlessCore.Projection.rewriteTrail(~at=event'.meta.time, Projection.targetStateSchema)
|
|
137
133
|
)
|
|
138
134
|
->handleActions({
|
|
139
135
|
load: load(store, ...),
|
|
@@ -205,8 +201,7 @@ module Make = (Projection: Reventless.Projection.Mapping): (
|
|
|
205
201
|
let stateEq = (a: Projection.targetState, b: Projection.targetState) =>
|
|
206
202
|
JSON.stringify(encState(a)) == JSON.stringify(encState(b))
|
|
207
203
|
let statesEq = (a, b) =>
|
|
208
|
-
Array.length(a) == Array.length(b) &&
|
|
209
|
-
Array.zip(a, b)->Array.every(((x, y)) => stateEq(x, y))
|
|
204
|
+
Array.length(a) == Array.length(b) && Array.zip(a, b)->Array.every(((x, y)) => stateEq(x, y))
|
|
210
205
|
let storeEq = (a: store, b: store) => {
|
|
211
206
|
let ka = a->Dict.keysToArray->Array.toSorted(String.compare)
|
|
212
207
|
let kb = b->Dict.keysToArray->Array.toSorted(String.compare)
|
|
@@ -231,8 +226,8 @@ module Make = (Projection: Reventless.Projection.Mapping): (
|
|
|
231
226
|
let actualStates = store->Dict.valuesToArray->Array.get(0)->Option.getOr([])
|
|
232
227
|
if (
|
|
233
228
|
keys->Array.length == 1 &&
|
|
234
|
-
|
|
235
|
-
|
|
229
|
+
actualId == Some(testId.contents) &&
|
|
230
|
+
statesEq(actualStates, expectedStates)
|
|
236
231
|
) {
|
|
237
232
|
Outcome.pass
|
|
238
233
|
} else {
|
|
@@ -281,8 +276,7 @@ module Make = (Projection: Reventless.Projection.Mapping): (
|
|
|
281
276
|
|
|
282
277
|
let thenState = (thunk, expectedState) => thenStates(thunk, [expectedState])
|
|
283
278
|
|
|
284
|
-
let thenStateWithId = (thunk, id, expectedState) =>
|
|
285
|
-
thenStatesWithId(thunk, id, [expectedState])
|
|
279
|
+
let thenStateWithId = (thunk, id, expectedState) => thenStatesWithId(thunk, id, [expectedState])
|
|
286
280
|
|
|
287
281
|
let thenNoState = async thunk => {
|
|
288
282
|
let store = await thunk()
|
|
@@ -315,10 +309,7 @@ module Make = (Projection: Reventless.Projection.Mapping): (
|
|
|
315
309
|
|
|
316
310
|
let thenFail = async thunk =>
|
|
317
311
|
switch await thunk() {
|
|
318
|
-
| _ =>
|
|
319
|
-
Outcome.fail(
|
|
320
|
-
Throw({error: "Expected failure but thunk returned normally", stack: ""}),
|
|
321
|
-
)
|
|
312
|
+
| _ => Outcome.fail(Throw({error: "Expected failure but thunk returned normally", stack: ""}))
|
|
322
313
|
| exception _ => Outcome.pass
|
|
323
314
|
}
|
|
324
315
|
}
|
|
@@ -34,8 +34,7 @@ module type T = {
|
|
|
34
34
|
// collect bodies too, but only after a manual `Promise.resolve` wrapper.
|
|
35
35
|
let testSync: (string, unit => Outcome.outcome) => unit
|
|
36
36
|
|
|
37
|
-
type translateResult =
|
|
38
|
-
result<option<(string, Spec.inboundCommand)>, string>
|
|
37
|
+
type translateResult = result<option<(string, Spec.inboundCommand)>, string>
|
|
39
38
|
|
|
40
39
|
// The state the pipeline carries after a translate attempt. `retries` counts
|
|
41
40
|
// the number of failed re-attempts (0 for a single `whenTranslateMocked`).
|
|
@@ -68,11 +67,7 @@ module type T = {
|
|
|
68
67
|
~maxRetries: int,
|
|
69
68
|
(string, Spec.outboundItem) => promise<translateResult>,
|
|
70
69
|
) => promise<attempt>
|
|
71
|
-
let thenCommand: (
|
|
72
|
-
promise<attempt>,
|
|
73
|
-
string,
|
|
74
|
-
Spec.inboundCommand,
|
|
75
|
-
) => promise<Outcome.outcome>
|
|
70
|
+
let thenCommand: (promise<attempt>, string, Spec.inboundCommand) => promise<Outcome.outcome>
|
|
76
71
|
let thenNoCommand: promise<attempt> => promise<Outcome.outcome>
|
|
77
72
|
let thenRetryRecorded: (promise<attempt>, int) => promise<Outcome.outcome>
|
|
78
73
|
let thenTodoStatus: (promise<attempt>, string, todoStatus) => promise<Outcome.outcome>
|
|
@@ -81,14 +76,12 @@ module type T = {
|
|
|
81
76
|
module Make = (Spec: SliceSpec): (T with module Spec = Spec) => {
|
|
82
77
|
module Spec = Spec
|
|
83
78
|
|
|
84
|
-
|
|
85
79
|
let describe = JestBind.describe
|
|
86
80
|
let test = (name, ~timeout=?, body) =>
|
|
87
81
|
JestBind.testPromise(~slice=Spec.name, name, ~timeout?, body)
|
|
88
82
|
let testSync = (name, body) => JestBind.test(~slice=Spec.name, name, body)
|
|
89
83
|
|
|
90
|
-
type translateResult =
|
|
91
|
-
result<option<(string, Spec.inboundCommand)>, string>
|
|
84
|
+
type translateResult = result<option<(string, Spec.inboundCommand)>, string>
|
|
92
85
|
|
|
93
86
|
type attempt = {
|
|
94
87
|
id: string,
|
|
@@ -100,8 +93,7 @@ module Make = (Spec: SliceSpec): (T with module Spec = Spec) => {
|
|
|
100
93
|
let encItem = (i: Spec.outboundItem) => i->Message.encode(Spec.outboundItemSchema)
|
|
101
94
|
let encItems = (arr: array<(string, Spec.outboundItem)>) =>
|
|
102
95
|
arr->Array.map(((id, i)) => (id, encItem(i)))
|
|
103
|
-
let encInbound = (c: Spec.inboundCommand) =>
|
|
104
|
-
c->Message.encode(Spec.inboundCommandSchema)
|
|
96
|
+
let encInbound = (c: Spec.inboundCommand) => c->Message.encode(Spec.inboundCommandSchema)
|
|
105
97
|
|
|
106
98
|
// Unit: collect
|
|
107
99
|
//
|
|
@@ -139,7 +131,7 @@ module Make = (Spec: SliceSpec): (T with module Spec = Spec) => {
|
|
|
139
131
|
}
|
|
140
132
|
while failed() && retries.contents < maxRetries {
|
|
141
133
|
retries := retries.contents + 1
|
|
142
|
-
result := await mock(id, item)
|
|
134
|
+
result := (await mock(id, item))
|
|
143
135
|
}
|
|
144
136
|
{id, item, result: result.contents, retries: retries.contents}
|
|
145
137
|
}
|
|
@@ -169,10 +161,7 @@ module Make = (Spec: SliceSpec): (T with module Spec = Spec) => {
|
|
|
169
161
|
actual: [],
|
|
170
162
|
}),
|
|
171
163
|
)
|
|
172
|
-
| Error(msg) =>
|
|
173
|
-
Outcome.fail(
|
|
174
|
-
TranslateError({expected: "(command)", actual: Some(msg)}),
|
|
175
|
-
)
|
|
164
|
+
| Error(msg) => Outcome.fail(TranslateError({expected: "(command)", actual: Some(msg)}))
|
|
176
165
|
}
|
|
177
166
|
}
|
|
178
167
|
|
|
@@ -180,12 +169,8 @@ module Make = (Spec: SliceSpec): (T with module Spec = Spec) => {
|
|
|
180
169
|
let {result, _} = await pending
|
|
181
170
|
switch result {
|
|
182
171
|
| Ok(None) => Outcome.pass
|
|
183
|
-
| Ok(Some((id, cmd))) =>
|
|
184
|
-
|
|
185
|
-
| Error(msg) =>
|
|
186
|
-
Outcome.fail(
|
|
187
|
-
TranslateError({expected: "(no command)", actual: Some(msg)}),
|
|
188
|
-
)
|
|
172
|
+
| Ok(Some((id, cmd))) => Outcome.fail(NoEventExpected({actual: [commandPairJson(id, cmd)]}))
|
|
173
|
+
| Error(msg) => Outcome.fail(TranslateError({expected: "(no command)", actual: Some(msg)}))
|
|
189
174
|
}
|
|
190
175
|
}
|
|
191
176
|
|
|
@@ -215,11 +200,10 @@ module Make = (Spec: SliceSpec): (T with module Spec = Spec) => {
|
|
|
215
200
|
|
|
216
201
|
let thenTodoStatus = async (pending, expectedId, expectedStatus) => {
|
|
217
202
|
let {id, result, _} = await pending
|
|
218
|
-
let actualStatus: todoStatus =
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
203
|
+
let actualStatus: todoStatus = switch result {
|
|
204
|
+
| Ok(_) => #Completed
|
|
205
|
+
| Error(_) => #Pending
|
|
206
|
+
}
|
|
223
207
|
if id == expectedId && actualStatus == expectedStatus {
|
|
224
208
|
Outcome.pass
|
|
225
209
|
} else {
|
package/src/Outcome.res
CHANGED
|
@@ -10,11 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
type mismatch =
|
|
12
12
|
| EventsMismatch({expected: array<JSON.t>, actual: array<JSON.t>})
|
|
13
|
-
| ErrorMismatch({
|
|
14
|
-
expected: JSON.t,
|
|
15
|
-
actual: option<JSON.t>,
|
|
16
|
-
actualEvents: array<JSON.t>,
|
|
17
|
-
})
|
|
13
|
+
| ErrorMismatch({expected: JSON.t, actual: option<JSON.t>, actualEvents: array<JSON.t>})
|
|
18
14
|
| StateMismatch({key: string, expected: option<JSON.t>, actual: option<JSON.t>})
|
|
19
15
|
| NoEventExpected({actual: array<JSON.t>})
|
|
20
16
|
| TodoMismatch({expected: array<(string, JSON.t)>, actual: array<(string, JSON.t)>})
|
|
@@ -71,9 +67,11 @@ let format = (m: mismatch) =>
|
|
|
71
67
|
actual,
|
|
72
68
|
)}`
|
|
73
69
|
| ErrorMismatch({expected, actual, actualEvents}) =>
|
|
74
|
-
`ErrorMismatch:\n expected error: ${stringifyJson(
|
|
75
|
-
|
|
76
|
-
)}\n actual events: ${stringifyJsonArray(
|
|
70
|
+
`ErrorMismatch:\n expected error: ${stringifyJson(
|
|
71
|
+
expected,
|
|
72
|
+
)}\n actual error: ${stringifyOptJson(actual)}\n actual events: ${stringifyJsonArray(
|
|
73
|
+
actualEvents,
|
|
74
|
+
)}`
|
|
77
75
|
| StateMismatch({key, expected, actual}) =>
|
|
78
76
|
`StateMismatch (key: ${key}):\n expected: ${stringifyOptJson(
|
|
79
77
|
expected,
|
|
@@ -94,14 +92,16 @@ let format = (m: mismatch) =>
|
|
|
94
92
|
| TranslateError({expected, actual}) =>
|
|
95
93
|
`TranslateError:\n expected: ${expected}\n actual: ${actual->Option.getOr("(none)")}`
|
|
96
94
|
| QueryRowsMismatch({expected, actual}) =>
|
|
97
|
-
`QueryRowsMismatch:\n expected: ${stringifyJsonArray(
|
|
98
|
-
|
|
99
|
-
)}`
|
|
95
|
+
`QueryRowsMismatch:\n expected: ${stringifyJsonArray(
|
|
96
|
+
expected,
|
|
97
|
+
)}\n actual: ${stringifyJsonArray(actual)}`
|
|
100
98
|
| PublishedActionsMismatch({expected, actual}) =>
|
|
101
|
-
`PublishedActionsMismatch:\n expected: ${stringifyJsonArray(
|
|
102
|
-
|
|
103
|
-
)}`
|
|
99
|
+
`PublishedActionsMismatch:\n expected: ${stringifyJsonArray(
|
|
100
|
+
expected,
|
|
101
|
+
)}\n actual: ${stringifyJsonArray(actual)}`
|
|
104
102
|
| ScopeDegraded({boundary, dropped, ambiguities}) =>
|
|
105
|
-
`ScopeDegraded:\n boundary: ${boundary}\n dropped: ${dropped->Array.join(
|
|
103
|
+
`ScopeDegraded:\n boundary: ${boundary}\n dropped: ${dropped->Array.join(
|
|
104
|
+
", ",
|
|
105
|
+
)}\n cause: ${ambiguities->Array.join(" | ")}`
|
|
106
106
|
| Throw({error, stack}) => `Throw: ${error}\n${stack}`
|
|
107
107
|
}
|
package/src/Projection_GWT.res
CHANGED
|
@@ -61,13 +61,11 @@ module type T = {
|
|
|
61
61
|
|
|
62
62
|
let handleActions = Projection.handleActions // local alias to avoid shadowing
|
|
63
63
|
|
|
64
|
-
module Make = (
|
|
65
|
-
Spec
|
|
66
|
-
|
|
67
|
-
): (T with module Spec = Spec) => {
|
|
64
|
+
module Make = (Spec: Spec, Projection: Projection with module Spec := Spec): (
|
|
65
|
+
T with module Spec = Spec
|
|
66
|
+
) => {
|
|
68
67
|
module Spec = Spec
|
|
69
68
|
|
|
70
|
-
|
|
71
69
|
let testId = ref(TestFixtures.id)
|
|
72
70
|
|
|
73
71
|
let describe = JestBind.describe
|
|
@@ -148,8 +146,7 @@ module Make = (
|
|
|
148
146
|
Ok()->Promise.resolve
|
|
149
147
|
}
|
|
150
148
|
|
|
151
|
-
let runActions = (actions, operations) =>
|
|
152
|
-
actions->handleActions(operations, Spec.subIdConfig)
|
|
149
|
+
let runActions = (actions, operations) => actions->handleActions(operations, Spec.subIdConfig)
|
|
153
150
|
|
|
154
151
|
let sortStore = store =>
|
|
155
152
|
switch Spec.subIdConfig {
|
|
@@ -210,8 +207,7 @@ module Make = (
|
|
|
210
207
|
let stateEq = (a: Spec.state, b: Spec.state) =>
|
|
211
208
|
JSON.stringify(encState(a)) == JSON.stringify(encState(b))
|
|
212
209
|
let statesEq = (a, b) =>
|
|
213
|
-
Array.length(a) == Array.length(b) &&
|
|
214
|
-
Array.zip(a, b)->Array.every(((x, y)) => stateEq(x, y))
|
|
210
|
+
Array.length(a) == Array.length(b) && Array.zip(a, b)->Array.every(((x, y)) => stateEq(x, y))
|
|
215
211
|
let storeEq = (a: store, b: store) => {
|
|
216
212
|
let ka = a->Dict.keysToArray->Array.toSorted(String.compare)
|
|
217
213
|
let kb = b->Dict.keysToArray->Array.toSorted(String.compare)
|
|
@@ -236,8 +232,8 @@ module Make = (
|
|
|
236
232
|
let actualStates = store->Dict.valuesToArray->Array.get(0)->Option.getOr([])
|
|
237
233
|
if (
|
|
238
234
|
keys->Array.length == 1 &&
|
|
239
|
-
|
|
240
|
-
|
|
235
|
+
actualId == Some(testId.contents) &&
|
|
236
|
+
statesEq(actualStates, expectedStates)
|
|
241
237
|
) {
|
|
242
238
|
Outcome.pass
|
|
243
239
|
} else {
|
|
@@ -285,8 +281,7 @@ module Make = (
|
|
|
285
281
|
}
|
|
286
282
|
|
|
287
283
|
let thenState = (thunk, expectedState) => thenStates(thunk, [expectedState])
|
|
288
|
-
let thenStateWithId = (thunk, id, expectedState) =>
|
|
289
|
-
thenStatesWithId(thunk, id, [expectedState])
|
|
284
|
+
let thenStateWithId = (thunk, id, expectedState) => thenStatesWithId(thunk, id, [expectedState])
|
|
290
285
|
|
|
291
286
|
let thenNoState = async thunk => {
|
|
292
287
|
let store = await thunk()
|
|
@@ -319,10 +314,7 @@ module Make = (
|
|
|
319
314
|
|
|
320
315
|
let thenFail = async thunk =>
|
|
321
316
|
switch await thunk() {
|
|
322
|
-
| _ =>
|
|
323
|
-
Outcome.fail(
|
|
324
|
-
Throw({error: "Expected failure but thunk returned normally", stack: ""}),
|
|
325
|
-
)
|
|
317
|
+
| _ => Outcome.fail(Throw({error: "Expected failure but thunk returned normally", stack: ""}))
|
|
326
318
|
| exception _ => Outcome.pass
|
|
327
319
|
}
|
|
328
320
|
}
|
package/src/Query_GWT.res
CHANGED
|
@@ -87,7 +87,6 @@ module type T = {
|
|
|
87
87
|
module Make = (Spec: QueryableSpec): (T with module Spec = Spec) => {
|
|
88
88
|
module Spec = Spec
|
|
89
89
|
|
|
90
|
-
|
|
91
90
|
let describe = JestBind.describe
|
|
92
91
|
let test = (name, body) => JestBind.test(~slice=Spec.name, name, body)
|
|
93
92
|
|
|
@@ -97,8 +96,7 @@ module Make = (Spec: QueryableSpec): (T with module Spec = Spec) => {
|
|
|
97
96
|
let encState = (s: Spec.state): JSON.t => s->Reventless.Util_Sury.toJson(Spec.stateSchema)
|
|
98
97
|
let encStates = arr => arr->Array.map(encState)
|
|
99
98
|
|
|
100
|
-
let givenStore = pairs =>
|
|
101
|
-
pairs->Array.map(((id, state)) => {id, subId: None, state})
|
|
99
|
+
let givenStore = pairs => pairs->Array.map(((id, state)) => {id, subId: None, state})
|
|
102
100
|
|
|
103
101
|
let givenCompositeStore = triples =>
|
|
104
102
|
triples->Array.map(((id, subId, state)) => {id, subId: Some(subId), state})
|
|
@@ -106,7 +104,13 @@ module Make = (Spec: QueryableSpec): (T with module Spec = Spec) => {
|
|
|
106
104
|
// `whenQueryById` ignores `subId` — returns the first row with the given id.
|
|
107
105
|
// Callers who want composite-id lookup go through `whenQueryByCompositeId`.
|
|
108
106
|
let whenQueryById = (s: store, id) =>
|
|
109
|
-
s->Array.findMap(r =>
|
|
107
|
+
s->Array.findMap(r =>
|
|
108
|
+
if r.id == id {
|
|
109
|
+
Some(r.state)
|
|
110
|
+
} else {
|
|
111
|
+
None
|
|
112
|
+
}
|
|
113
|
+
)
|
|
110
114
|
|
|
111
115
|
let whenQueryByCompositeId = (s: store, {id, subId}: compositeId): result<
|
|
112
116
|
option<Spec.state>,
|
|
@@ -116,9 +120,11 @@ module Make = (Spec: QueryableSpec): (T with module Spec = Spec) => {
|
|
|
116
120
|
| None =>
|
|
117
121
|
Error(
|
|
118
122
|
Outcome.QueryRowsMismatch({
|
|
119
|
-
expected: [
|
|
120
|
-
|
|
121
|
-
|
|
123
|
+
expected: [
|
|
124
|
+
JSON.Encode.string(
|
|
125
|
+
`composite-id lookup requires subIdConfig = Some({subIdField, getSubId}) in ${Spec.name}.config`,
|
|
126
|
+
),
|
|
127
|
+
],
|
|
122
128
|
actual: [],
|
|
123
129
|
}),
|
|
124
130
|
)
|
|
@@ -219,9 +225,7 @@ module Make = (Spec: QueryableSpec): (T with module Spec = Spec) => {
|
|
|
219
225
|
if actual == expected {
|
|
220
226
|
Outcome.pass
|
|
221
227
|
} else {
|
|
222
|
-
Outcome.fail(
|
|
223
|
-
QueryRowsMismatch({expected: encStates(expected), actual: encStates(actual)}),
|
|
224
|
-
)
|
|
228
|
+
Outcome.fail(QueryRowsMismatch({expected: encStates(expected), actual: encStates(actual)}))
|
|
225
229
|
}
|
|
226
230
|
}
|
|
227
231
|
|
|
@@ -267,7 +271,6 @@ module Make = (Spec: QueryableSpec): (T with module Spec = Spec) => {
|
|
|
267
271
|
// field — so a test fails if the `@resolves` annotation is missing, exactly as
|
|
268
272
|
// `whenQuery` validates a named index.
|
|
269
273
|
module MakeResolver = (Primary: QueryableSpec, Target: QueryableSpec) => {
|
|
270
|
-
|
|
271
274
|
let describe = JestBind.describe
|
|
272
275
|
let sliceName = `${Primary.name}→${Target.name}`
|
|
273
276
|
let test = (name, body) => JestBind.test(~slice=sliceName, name, body)
|
|
@@ -282,13 +285,11 @@ module MakeResolver = (Primary: QueryableSpec, Target: QueryableSpec) => {
|
|
|
282
285
|
let encTarget = (s: Target.state): JSON.t => s->Reventless.Util_Sury.toJson(Target.stateSchema)
|
|
283
286
|
let encTargets = arr => arr->Array.map(encTarget)
|
|
284
287
|
|
|
285
|
-
let resolverFor = field =>
|
|
286
|
-
Primary.config.idResolvers->Array.find(r => r.source.idField == field)
|
|
288
|
+
let resolverFor = field => Primary.config.idResolvers->Array.find(r => r.source.idField == field)
|
|
287
289
|
let resolverManyFor = field =>
|
|
288
290
|
Primary.config.idsResolvers->Array.find(r => r.source.idsField == field)
|
|
289
291
|
|
|
290
|
-
let missing = msg =>
|
|
291
|
-
Outcome.QueryRowsMismatch({expected: [JSON.Encode.string(msg)], actual: []})
|
|
292
|
+
let missing = msg => Outcome.QueryRowsMismatch({expected: [JSON.Encode.string(msg)], actual: []})
|
|
292
293
|
|
|
293
294
|
// Read a JSON field off the primary row's encoded state.
|
|
294
295
|
let readField = (state: Primary.state, field): option<JSON.t> =>
|
|
@@ -313,7 +314,9 @@ module MakeResolver = (Primary: QueryableSpec, Target: QueryableSpec) => {
|
|
|
313
314
|
> =>
|
|
314
315
|
switch resolverFor(field) {
|
|
315
316
|
| None =>
|
|
316
|
-
Error(
|
|
317
|
+
Error(
|
|
318
|
+
missing(`@resolves on field "${field}" is missing from ${Primary.name}.config.idResolvers`),
|
|
319
|
+
)
|
|
317
320
|
| Some(_) =>
|
|
318
321
|
switch scenario.primary->Array.findMap(((id, st)) => id == primaryId ? Some(st) : None) {
|
|
319
322
|
| None => Ok(None)
|
|
@@ -332,7 +335,9 @@ module MakeResolver = (Primary: QueryableSpec, Target: QueryableSpec) => {
|
|
|
332
335
|
switch resolverManyFor(field) {
|
|
333
336
|
| None =>
|
|
334
337
|
Error(
|
|
335
|
-
missing(
|
|
338
|
+
missing(
|
|
339
|
+
`@resolvesMany on field "${field}" is missing from ${Primary.name}.config.idsResolvers`,
|
|
340
|
+
),
|
|
336
341
|
)
|
|
337
342
|
| Some(_) =>
|
|
338
343
|
switch scenario.primary->Array.findMap(((id, st)) => id == primaryId ? Some(st) : None) {
|
|
@@ -340,7 +345,9 @@ module MakeResolver = (Primary: QueryableSpec, Target: QueryableSpec) => {
|
|
|
340
345
|
| Some(pstate) =>
|
|
341
346
|
switch readField(pstate, field) {
|
|
342
347
|
| Some(JSON.Array(arr)) =>
|
|
343
|
-
Ok(
|
|
348
|
+
Ok(
|
|
349
|
+
arr->Array.map(jsonToStr)->Array.filterMap(fkId => lookupTarget(scenario.target, fkId)),
|
|
350
|
+
)
|
|
344
351
|
| _ => Ok([])
|
|
345
352
|
}
|
|
346
353
|
}
|
|
@@ -372,6 +379,8 @@ module MakeResolver = (Primary: QueryableSpec, Target: QueryableSpec) => {
|
|
|
372
379
|
| Ok(actual) =>
|
|
373
380
|
actual == expected
|
|
374
381
|
? Outcome.pass
|
|
375
|
-
: Outcome.fail(
|
|
382
|
+
: Outcome.fail(
|
|
383
|
+
QueryRowsMismatch({expected: encTargets(expected), actual: encTargets(actual)}),
|
|
384
|
+
)
|
|
376
385
|
}
|
|
377
386
|
}
|
package/src/RenderRescript.res
CHANGED
|
@@ -112,8 +112,7 @@ let renderMany = (arr: array<JSON.t>): string =>
|
|
|
112
112
|
| [] => "[]"
|
|
113
113
|
| [one] => "[" ++ render(one) ++ "]"
|
|
114
114
|
| _ =>
|
|
115
|
-
let inner =
|
|
116
|
-
arr->Array.map(v => indent(1) ++ render(~level=1, v))->Array.join(",\n")
|
|
115
|
+
let inner = arr->Array.map(v => indent(1) ++ render(~level=1, v))->Array.join(",\n")
|
|
117
116
|
"[\n" ++ inner ++ ",\n]"
|
|
118
117
|
}
|
|
119
118
|
|
package/src/SideEffect_GWT.res
CHANGED
|
@@ -79,7 +79,6 @@ module type T = {
|
|
|
79
79
|
module Make = (SE: Reventless.SideEffect.T): (T with module SE = SE) => {
|
|
80
80
|
module SE = SE
|
|
81
81
|
|
|
82
|
-
|
|
83
82
|
let describe = JestBind.describe
|
|
84
83
|
let test = (name, ~timeout=?, body) =>
|
|
85
84
|
JestBind.testPromise(~slice=SE.Source.name, name, ~timeout?, body)
|
|
@@ -14,12 +14,10 @@ type consumedEvent =
|
|
|
14
14
|
| CategoryArchived
|
|
15
15
|
|
|
16
16
|
@schema
|
|
17
|
-
type command =
|
|
18
|
-
AddCategory({categoryId: @s.matches(Reventless.DcbTag.string) string, name: string})
|
|
17
|
+
type command = AddCategory({categoryId: @s.matches(Reventless.DcbTag.string) string, name: string})
|
|
19
18
|
|
|
20
19
|
@schema
|
|
21
20
|
type error = CategoryAlreadyExists
|
|
22
21
|
|
|
23
22
|
@schema
|
|
24
|
-
type event =
|
|
25
|
-
CategoryAdded({categoryId: @s.matches(Reventless.DcbTag.string) string, name: string})
|
|
23
|
+
type event = CategoryAdded({categoryId: @s.matches(Reventless.DcbTag.string) string, name: string})
|
|
@@ -7,16 +7,13 @@
|
|
|
7
7
|
let name = "WithFixtures"
|
|
8
8
|
|
|
9
9
|
@schema
|
|
10
|
-
type consumedEvent =
|
|
11
|
-
| CategoryAdded
|
|
10
|
+
type consumedEvent = CategoryAdded
|
|
12
11
|
|
|
13
12
|
@schema
|
|
14
|
-
type command =
|
|
15
|
-
AddCategory({categoryId: @s.matches(Reventless.DcbTag.string) string, name: string})
|
|
13
|
+
type command = AddCategory({categoryId: @s.matches(Reventless.DcbTag.string) string, name: string})
|
|
16
14
|
|
|
17
15
|
@schema
|
|
18
16
|
type error = CategoryAlreadyExists
|
|
19
17
|
|
|
20
18
|
@schema
|
|
21
|
-
type event =
|
|
22
|
-
CategoryAdded({categoryId: @s.matches(Reventless.DcbTag.string) string, name: string})
|
|
19
|
+
type event = CategoryAdded({categoryId: @s.matches(Reventless.DcbTag.string) string, name: string})
|
|
@@ -8,16 +8,13 @@
|
|
|
8
8
|
let name = "WithManualOpen"
|
|
9
9
|
|
|
10
10
|
@schema
|
|
11
|
-
type consumedEvent =
|
|
12
|
-
| CategoryAdded
|
|
11
|
+
type consumedEvent = CategoryAdded
|
|
13
12
|
|
|
14
13
|
@schema
|
|
15
|
-
type command =
|
|
16
|
-
AddCategory({categoryId: @s.matches(Reventless.DcbTag.string) string, name: string})
|
|
14
|
+
type command = AddCategory({categoryId: @s.matches(Reventless.DcbTag.string) string, name: string})
|
|
17
15
|
|
|
18
16
|
@schema
|
|
19
17
|
type error = CategoryAlreadyExists
|
|
20
18
|
|
|
21
19
|
@schema
|
|
22
|
-
type event =
|
|
23
|
-
CategoryAdded({categoryId: @s.matches(Reventless.DcbTag.string) string, name: string})
|
|
20
|
+
type event = CategoryAdded({categoryId: @s.matches(Reventless.DcbTag.string) string, name: string})
|
|
@@ -31,8 +31,10 @@ module ShipOrderSlice = {
|
|
|
31
31
|
| _ => None
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
let process = (_id, item) =>
|
|
35
|
-
|
|
34
|
+
let process = (_id, item) => Some((
|
|
35
|
+
item.orderId,
|
|
36
|
+
CreateShipment({orderId: item.orderId, address: item.shippingAddress}),
|
|
37
|
+
))
|
|
36
38
|
let onExhausted = (_id, _item) => None
|
|
37
39
|
}
|
|
38
40
|
|
|
@@ -56,11 +58,11 @@ describe("ShipOrder AutomationSlice", () => {
|
|
|
56
58
|
)
|
|
57
59
|
|
|
58
60
|
test("sweep: events → commands, andThenEvents drains todos", () => {
|
|
59
|
-
let s =
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
let s = givenEvents([OrderPlaced({orderId: "o1", shippingAddress: "1 Main St"})])->whenSweep
|
|
62
|
+
let commandsOk = thenCommands(
|
|
63
|
+
s,
|
|
64
|
+
[("o1", CreateShipment({orderId: "o1", address: "1 Main St"}))],
|
|
65
|
+
)
|
|
64
66
|
let drained = andThenEvents(s, [ShipmentCreated({orderId: "o1"})])
|
|
65
67
|
let todosOk = thenScenarioTodos(drained, [])
|
|
66
68
|
switch (commandsOk, todosOk) {
|