@reventlessdev/reventless-gwt 1.0.0-alpha.247 → 1.0.0-alpha.249
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 +21 -0
- package/README.md +1 -1
- package/package.json +7 -7
- package/src/.gwtignore +1 -1
- package/src/{Behavior_GWT.res → Decision_GWT.res} +110 -40
- package/src/{Behavior_GWT.res.mjs → Decision_GWT.res.mjs} +92 -42
- package/src/Delegate_GWT.res +1 -1
- package/src/EventMapping_GWT.res +7 -7
- package/src/EventMapping_GWT.res.mjs +3 -3
- package/src/Flow_GWT.res +14 -14
- package/src/Flow_GWT.res.mjs +6 -6
- package/src/Hint.res +1 -1
- package/src/Mapping_GWT.res +8 -8
- package/src/Mapping_GWT.res.mjs +6 -6
- package/src/Outcome.res +1 -1
- package/src/Query_GWT.res +1 -1
- package/src/RunnerHook.res +1 -1
- package/src/ScenarioRecorder.res +182 -0
- package/src/ScenarioRecorder.res.mjs +287 -0
- package/src/StateChange/ExternalAddCategorySlice.res +1 -1
- package/src/StateChange/WithFixtures.res +1 -1
- package/src/StateChange/WithManualOpen.res +1 -1
- package/tests/CallerTest.res +7 -7
- package/tests/CallerTest.res.mjs +8 -8
- package/tests/FlowAggregateGwtTest.res +6 -6
- package/tests/FlowAggregateGwtTest.res.mjs +6 -6
- package/tests/FlowCrossPluginGwtTest.res +8 -8
- package/tests/FlowCrossPluginGwtTest.res.mjs +8 -8
- package/tests/FlowGwtTest.res +4 -4
- package/tests/FlowGwtTest.res.mjs +4 -4
- package/tests/HintTest.res +2 -2
- package/tests/HintTest.res.mjs +1 -1
- package/tests/MappingGwtTest.res +5 -5
- package/tests/MappingGwtTest.res.mjs +6 -6
- package/tests/ScenarioRecorderTest.res +101 -0
- package/tests/ScenarioRecorderTest.res.mjs +163 -0
- package/tests/StateChange/ExternalAddCategorySlice_GWT.res +3 -3
- package/tests/StateChange/ExternalAddCategorySlice_GWT.res.mjs +5 -5
- package/tests/StateChange/WithFixtures_GWT.res.mjs +5 -5
- package/tests/StateChange/WithManualOpen_GWT.res.mjs +5 -5
- package/tests/StateChangeSliceAuthorizeGwtTest.res +10 -10
- package/tests/StateChangeSliceAuthorizeGwtTest.res.mjs +14 -14
- package/tests/StateChangeSliceGwtTest.res +9 -9
- package/tests/StateChangeSliceGwtTest.res.mjs +12 -12
- /package/src/StateChange/{ExternalAddCategorySlice_Behavior.res → ExternalAddCategorySlice_Decision.res} +0 -0
- /package/src/StateChange/{ExternalAddCategorySlice_Behavior.res.mjs → ExternalAddCategorySlice_Decision.res.mjs} +0 -0
- /package/src/StateChange/{WithFixtures_Behavior.res → WithFixtures_Decision.res} +0 -0
- /package/src/StateChange/{WithFixtures_Behavior.res.mjs → WithFixtures_Decision.res.mjs} +0 -0
- /package/src/StateChange/{WithManualOpen_Behavior.res → WithManualOpen_Decision.res} +0 -0
- /package/src/StateChange/{WithManualOpen_Behavior.res.mjs → WithManualOpen_Decision.res.mjs} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,27 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 1.0.0-alpha.249 (2026-10-21)
|
|
7
|
+
|
|
8
|
+
* feat!: the write-side logic module is called the decision, and a codemod moves an app over ([cb573ff](https://github.com/ReventlessDev/reventless-core/commit/cb573ff5b928c7d7f3e75988dfaa65066f8c95eb))
|
|
9
|
+
|
|
10
|
+
### BREAKING CHANGES
|
|
11
|
+
|
|
12
|
+
* <Spec>_Behavior.res, @@reventless.behavior,
|
|
13
|
+
Reventless.Behavior.T, StateChangeSlice.Behavior, Behavior_GWT and
|
|
14
|
+
thenRefused no longer exist. Run `npx reventless-rename-to-decision` at an
|
|
15
|
+
app's root, then rebuild; comments are not rewritten. The PPX must be the
|
|
16
|
+
matching release.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# 1.0.0-alpha.248 (2026-10-19)
|
|
21
|
+
|
|
22
|
+
### Features
|
|
23
|
+
|
|
24
|
+
* **gwt:** record behaviour scenarios as they run, for suites whose source names none ([256b253](https://github.com/ReventlessDev/reventless-core/commit/256b253153ec043105c38ac742a1a3efbde48c87))
|
|
25
|
+
|
|
26
|
+
|
|
6
27
|
# 1.0.0-alpha.247 (2026-10-17)
|
|
7
28
|
|
|
8
29
|
**Note:** Version bump only for package @reventlessdev/reventless-gwt
|
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ the `reventless-dev` CLI discovers and runs those tests across a workspace.
|
|
|
21
21
|
Every component kind has a functor that produces the same
|
|
22
22
|
`describe / test / given* / when* / then*` combinator surface:
|
|
23
23
|
|
|
24
|
-
- **`
|
|
24
|
+
- **`Decision_GWT`** — event-sourced decision tests for aggregates and
|
|
25
25
|
StateChangeSlices (`givenEvents → whenCmd → thenEmits`), including DCB
|
|
26
26
|
append-condition assertions.
|
|
27
27
|
- **`Projection_GWT`** / **`MultiSourceProjection_GWT`** — single-source
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-gwt",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.249",
|
|
4
4
|
"description": "Given-When-Then DSLs and test harness for Reventless slice testing",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"jest": {
|
|
@@ -14,12 +14,12 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"sury": "11.0.0",
|
|
17
|
-
"@reventlessdev/rescript-effect": "1.0.0-alpha.
|
|
18
|
-
"@reventlessdev/rescript-
|
|
19
|
-
"@reventlessdev/reventless-
|
|
20
|
-
"@reventlessdev/
|
|
21
|
-
"@reventlessdev/
|
|
22
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.
|
|
17
|
+
"@reventlessdev/rescript-effect": "1.0.0-alpha.1",
|
|
18
|
+
"@reventlessdev/rescript-jest": "1.0.0-alpha.13",
|
|
19
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.198",
|
|
20
|
+
"@reventlessdev/rescript-node": "2.0.0-alpha.23",
|
|
21
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.301",
|
|
22
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.170"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"rescript": "12.3.1",
|
package/src/.gwtignore
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Prunes this subtree from reventless-gwt discovery (test tree + component tree).
|
|
2
2
|
#
|
|
3
|
-
# These are the GWT DSL framework modules — `Automation_GWT`, `
|
|
3
|
+
# These are the GWT DSL framework modules — `Automation_GWT`, `Decision_GWT`,
|
|
4
4
|
# `Mapping_GWT`, etc. — i.e. the `*_GWT.Make` functors the test runner is built
|
|
5
5
|
# from. They are named with the same `_GWT` suffix that marks user test files, so
|
|
6
6
|
# discovery would otherwise list each one as a test, even though loading the
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
open ReventlessCore
|
|
2
2
|
|
|
3
|
-
// Plan 02 Phase 6b — unified
|
|
3
|
+
// Plan 02 Phase 6b — unified Decision GWT DSL covering both Aggregate and
|
|
4
4
|
// StateChangeSlice. Two entry points:
|
|
5
5
|
//
|
|
6
|
-
// * [Make(Spec,
|
|
7
|
-
// [consumedEvent]; [
|
|
6
|
+
// * [Make(Spec, Decision)] — StateChangeSlice form. [Spec] carries
|
|
7
|
+
// [consumedEvent]; [Decision.evolve]
|
|
8
8
|
// consumes it. DCB append-condition
|
|
9
9
|
// checks are wired in.
|
|
10
|
-
// * [MakeFromAggregate(Spec,
|
|
10
|
+
// * [MakeFromAggregate(Spec, Decision)] — Aggregate adapter. [Spec] is
|
|
11
11
|
// [Reventless.Aggregate.Spec],
|
|
12
|
-
// [
|
|
12
|
+
// [Decision] is [Reventless.Decision.T].
|
|
13
13
|
// No DCB semantics; the simpler
|
|
14
14
|
// [AggregateT] surface is returned.
|
|
15
15
|
//
|
|
@@ -19,7 +19,7 @@ open ReventlessCore
|
|
|
19
19
|
// optimistic-concurrency assertions.
|
|
20
20
|
//
|
|
21
21
|
// Aggregate users *cannot* use [Make] (their Spec doesn't carry [consumedEvent])
|
|
22
|
-
// and slice users *cannot* use [MakeFromAggregate] (their Spec/
|
|
22
|
+
// and slice users *cannot* use [MakeFromAggregate] (their Spec/Decision shape
|
|
23
23
|
// differs). Pick the matching entry point.
|
|
24
24
|
|
|
25
25
|
// ---------------------------------------------------------------------------
|
|
@@ -204,7 +204,7 @@ module Acting = (Spec: ActingSpec) => {
|
|
|
204
204
|
)
|
|
205
205
|
)
|
|
206
206
|
|
|
207
|
-
let
|
|
207
|
+
let thenForbidden = (~encEvents, ~decided: option<JSON.t>, events) =>
|
|
208
208
|
switch refusal.contents {
|
|
209
209
|
| Some(_) => Outcome.pass
|
|
210
210
|
| None =>
|
|
@@ -224,7 +224,7 @@ module Acting = (Spec: ActingSpec) => {
|
|
|
224
224
|
// Slice form — replaces the legacy `StateChangeSlice_GWT`.
|
|
225
225
|
// ---------------------------------------------------------------------------
|
|
226
226
|
|
|
227
|
-
module type
|
|
227
|
+
module type DecisionSpec = {
|
|
228
228
|
let name: string
|
|
229
229
|
|
|
230
230
|
@schema
|
|
@@ -243,8 +243,8 @@ module type BehaviorSpec = {
|
|
|
243
243
|
let authorizationOf: string => Reventless.Authorization.rule<role>
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
-
module type
|
|
247
|
-
module Spec:
|
|
246
|
+
module type Decision = {
|
|
247
|
+
module Spec: DecisionSpec
|
|
248
248
|
|
|
249
249
|
type state
|
|
250
250
|
let initialState: state
|
|
@@ -253,7 +253,7 @@ module type Behavior = {
|
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
module type T = {
|
|
256
|
-
module Spec:
|
|
256
|
+
module Spec: DecisionSpec
|
|
257
257
|
|
|
258
258
|
let describe: (string, unit => unit) => unit
|
|
259
259
|
let todo: string => unit
|
|
@@ -275,7 +275,7 @@ module type T = {
|
|
|
275
275
|
let thenError: (array<Spec.event>, Spec.error) => Outcome.outcome
|
|
276
276
|
// Refused before `decide`: by the command's rule, or because the caller does not
|
|
277
277
|
// own what it acts on.
|
|
278
|
-
let
|
|
278
|
+
let thenForbidden: array<Spec.event> => Outcome.outcome
|
|
279
279
|
|
|
280
280
|
// DCB optimistic-concurrency assertions.
|
|
281
281
|
let thenAppendsConditionedOn: (array<Spec.event>, Reventless.DcbTag.query) => Outcome.outcome
|
|
@@ -323,7 +323,7 @@ let encodeAppendCondition = (c: Reventless.DcbTag.appendCondition): JSON.t => {
|
|
|
323
323
|
}
|
|
324
324
|
|
|
325
325
|
// ---------------------------------------------------------------------------
|
|
326
|
-
// Shared assertion core for both
|
|
326
|
+
// Shared assertion core for both Decision GWT flavours.
|
|
327
327
|
//
|
|
328
328
|
// `Make` (slice/DCB) and `MakeFromAggregate` encode produced events / errors
|
|
329
329
|
// identically and compare them against the expectation the same way — only the
|
|
@@ -436,16 +436,19 @@ module AssertionCore = (Spec: CoreSpec) => {
|
|
|
436
436
|
}
|
|
437
437
|
}
|
|
438
438
|
|
|
439
|
-
module Make = (Spec:
|
|
439
|
+
module Make = (Spec: DecisionSpec, Decision: Decision with module Spec := Spec): (
|
|
440
440
|
T with module Spec = Spec
|
|
441
441
|
) => {
|
|
442
442
|
module Spec = Spec
|
|
443
443
|
|
|
444
444
|
let describe = JestBind.describe
|
|
445
445
|
let todo = JestBind.todo
|
|
446
|
-
let test = (name, body) =>
|
|
446
|
+
let test = (name, body) =>
|
|
447
|
+
JestBind.test(~slice=Spec.name, name, () =>
|
|
448
|
+
ScenarioRecorder.around(~spec=Spec.name, name, body)
|
|
449
|
+
)
|
|
447
450
|
|
|
448
|
-
let currentState = consumed => consumed->Array.reduce(
|
|
451
|
+
let currentState = consumed => consumed->Array.reduce(Decision.initialState, Decision.evolve)
|
|
449
452
|
|
|
450
453
|
module Core = AssertionCore(Spec)
|
|
451
454
|
let errors = Core.errors
|
|
@@ -604,7 +607,7 @@ module Make = (Spec: BehaviorSpec, Behavior: Behavior with module Spec := Spec):
|
|
|
604
607
|
if Guard.refuses(history, command) {
|
|
605
608
|
[]
|
|
606
609
|
} else {
|
|
607
|
-
switch
|
|
610
|
+
switch Decision.decide(currentState(history), command) {
|
|
608
611
|
| Ok(events) => events
|
|
609
612
|
| Error(error) =>
|
|
610
613
|
errors := [error]
|
|
@@ -614,7 +617,27 @@ module Make = (Spec: BehaviorSpec, Behavior: Behavior with module Spec := Spec):
|
|
|
614
617
|
}
|
|
615
618
|
|
|
616
619
|
let givenEvents = consumed => consumed
|
|
617
|
-
let whenCmd = (history, cmd) =>
|
|
620
|
+
let whenCmd = (history, cmd) => {
|
|
621
|
+
if ScenarioRecorder.recording() {
|
|
622
|
+
ScenarioRecorder.given(
|
|
623
|
+
history->Array.map(e =>
|
|
624
|
+
e->Message.encode(Spec.consumedEventSchema)->Message.variantNameOfJson
|
|
625
|
+
),
|
|
626
|
+
)
|
|
627
|
+
ScenarioRecorder.whenCommand(Guard.commandName(cmd))
|
|
628
|
+
}
|
|
629
|
+
history->exec(cmd)
|
|
630
|
+
}
|
|
631
|
+
// What a test expects, named for the recorded sidecar.
|
|
632
|
+
let expects = (~events=[], ~error=?) =>
|
|
633
|
+
if ScenarioRecorder.recording() {
|
|
634
|
+
ScenarioRecorder.thenEvents(
|
|
635
|
+
events->Array.map(e => e->Core.encEvent->Message.variantNameOfJson),
|
|
636
|
+
)
|
|
637
|
+
error->Option.forEach(err =>
|
|
638
|
+
ScenarioRecorder.thenError(err->Core.encError->Message.variantNameOfJson)
|
|
639
|
+
)
|
|
640
|
+
}
|
|
618
641
|
|
|
619
642
|
let checkAppendCondition = (): option<Outcome.outcome> =>
|
|
620
643
|
switch appendConditionFailure.contents {
|
|
@@ -622,8 +645,8 @@ module Make = (Spec: BehaviorSpec, Behavior: Behavior with module Spec := Spec):
|
|
|
622
645
|
| None => Guard.unexpectedRefusal()
|
|
623
646
|
}
|
|
624
647
|
|
|
625
|
-
let
|
|
626
|
-
Guard.
|
|
648
|
+
let thenForbidden = events =>
|
|
649
|
+
Guard.thenForbidden(
|
|
627
650
|
~encEvents=Core.encEvents,
|
|
628
651
|
~decided=errors.contents->Array.get(0)->Option.map(Core.encError),
|
|
629
652
|
events,
|
|
@@ -631,37 +654,49 @@ module Make = (Spec: BehaviorSpec, Behavior: Behavior with module Spec := Spec):
|
|
|
631
654
|
|
|
632
655
|
// The append-condition footgun is surfaced before the shared comparison core
|
|
633
656
|
// runs; `thenAppends*` below bypass it deliberately.
|
|
634
|
-
let thenEvents = (events, expectedEvents) =>
|
|
657
|
+
let thenEvents = (events, expectedEvents) => {
|
|
658
|
+
expects(~events=expectedEvents)
|
|
635
659
|
switch checkAppendCondition() {
|
|
636
660
|
| Some(o) => o
|
|
637
661
|
| None => Core.compareEvents(events, expectedEvents)
|
|
638
662
|
}
|
|
663
|
+
}
|
|
639
664
|
|
|
640
665
|
let thenEvent = (events, expectedEvent) => thenEvents(events, [expectedEvent])
|
|
641
666
|
|
|
642
|
-
let thenNoEvent = events =>
|
|
667
|
+
let thenNoEvent = events => {
|
|
668
|
+
if ScenarioRecorder.recording() {
|
|
669
|
+
ScenarioRecorder.thenNoEvent()
|
|
670
|
+
}
|
|
643
671
|
switch checkAppendCondition() {
|
|
644
672
|
| Some(o) => o
|
|
645
673
|
| None => Core.compareNoEvent(events)
|
|
646
674
|
}
|
|
675
|
+
}
|
|
647
676
|
|
|
648
|
-
let thenError = (events, expectedError) =>
|
|
677
|
+
let thenError = (events, expectedError) => {
|
|
678
|
+
expects(~error=expectedError)
|
|
649
679
|
switch checkAppendCondition() {
|
|
650
680
|
| Some(o) => o
|
|
651
681
|
| None => Core.matchesError(events, [], expectedError)
|
|
652
682
|
}
|
|
683
|
+
}
|
|
653
684
|
|
|
654
|
-
let thenEventWithError = (events, expectedEvent, expectedError) =>
|
|
685
|
+
let thenEventWithError = (events, expectedEvent, expectedError) => {
|
|
686
|
+
expects(~events=[expectedEvent], ~error=expectedError)
|
|
655
687
|
switch checkAppendCondition() {
|
|
656
688
|
| Some(o) => o
|
|
657
689
|
| None => Core.matchesError(events, [expectedEvent], expectedError)
|
|
658
690
|
}
|
|
691
|
+
}
|
|
659
692
|
|
|
660
|
-
let thenEventsWithError = (events, expectedEvents, expectedError) =>
|
|
693
|
+
let thenEventsWithError = (events, expectedEvents, expectedError) => {
|
|
694
|
+
expects(~events=expectedEvents, ~error=expectedError)
|
|
661
695
|
switch checkAppendCondition() {
|
|
662
696
|
| Some(o) => o
|
|
663
697
|
| None => Core.matchesError(events, expectedEvents, expectedError)
|
|
664
698
|
}
|
|
699
|
+
}
|
|
665
700
|
|
|
666
701
|
let thenAppendsConditionedOn = (_events, expectedQuery: Reventless.DcbTag.query) =>
|
|
667
702
|
switch derivedCondition.contents {
|
|
@@ -716,7 +751,7 @@ module Make = (Spec: BehaviorSpec, Behavior: Behavior with module Spec := Spec):
|
|
|
716
751
|
// ---------------------------------------------------------------------------
|
|
717
752
|
|
|
718
753
|
// Minimal aggregate spec the DSL requires. Declared inline (rather than
|
|
719
|
-
// aliasing `Reventless.
|
|
754
|
+
// aliasing `Reventless.Decision.T`'s inner Spec) because that inner Spec
|
|
720
755
|
// only carries the three @schema types — no `name` — and ReScript's
|
|
721
756
|
// `with module Spec = X` can only equate the inner Spec to a concrete
|
|
722
757
|
// module, not constrain it to a module type that adds `name`.
|
|
@@ -764,18 +799,21 @@ module type AggregateT = {
|
|
|
764
799
|
let thenError: (array<Spec.event>, Spec.error) => Outcome.outcome
|
|
765
800
|
// Refused before `decide`: by the command's rule, or because the caller does not
|
|
766
801
|
// own what it acts on.
|
|
767
|
-
let
|
|
802
|
+
let thenForbidden: array<Spec.event> => Outcome.outcome
|
|
768
803
|
}
|
|
769
804
|
|
|
770
|
-
module MakeFromAggregate = (Spec: AggregateSpec,
|
|
805
|
+
module MakeFromAggregate = (Spec: AggregateSpec, Decision: Decision.T with module Spec = Spec): (
|
|
771
806
|
AggregateT with module Spec = Spec
|
|
772
807
|
) => {
|
|
773
808
|
module Spec = Spec
|
|
774
809
|
|
|
775
810
|
let describe = JestBind.describe
|
|
776
|
-
let test = (name, body) =>
|
|
811
|
+
let test = (name, body) =>
|
|
812
|
+
JestBind.test(~slice=Spec.name, name, () =>
|
|
813
|
+
ScenarioRecorder.around(~spec=Spec.name, name, body)
|
|
814
|
+
)
|
|
777
815
|
|
|
778
|
-
let currentState = events => events->Array.reduce(
|
|
816
|
+
let currentState = events => events->Array.reduce(Decision.initialState, Decision.evolve)
|
|
779
817
|
|
|
780
818
|
module Core = AssertionCore(Spec)
|
|
781
819
|
let errors = Core.errors
|
|
@@ -799,7 +837,7 @@ module MakeFromAggregate = (Spec: AggregateSpec, Behavior: Behavior.T with modul
|
|
|
799
837
|
if Guard.refuses(history, command) {
|
|
800
838
|
[]
|
|
801
839
|
} else {
|
|
802
|
-
switch
|
|
840
|
+
switch Decision.decide(currentState(history), command) {
|
|
803
841
|
| Ok(events) => events
|
|
804
842
|
| Error(error) =>
|
|
805
843
|
errors := [error]
|
|
@@ -809,7 +847,23 @@ module MakeFromAggregate = (Spec: AggregateSpec, Behavior: Behavior.T with modul
|
|
|
809
847
|
}
|
|
810
848
|
|
|
811
849
|
let givenEvents = events => events
|
|
812
|
-
let whenCmd = (history, cmd) =>
|
|
850
|
+
let whenCmd = (history, cmd) => {
|
|
851
|
+
if ScenarioRecorder.recording() {
|
|
852
|
+
ScenarioRecorder.given(history->Array.map(e => e->Core.encEvent->Message.variantNameOfJson))
|
|
853
|
+
ScenarioRecorder.whenCommand(Guard.commandName(cmd))
|
|
854
|
+
}
|
|
855
|
+
history->exec(cmd)
|
|
856
|
+
}
|
|
857
|
+
// What a test expects, named for the recorded sidecar.
|
|
858
|
+
let expects = (~events=[], ~error=?) =>
|
|
859
|
+
if ScenarioRecorder.recording() {
|
|
860
|
+
ScenarioRecorder.thenEvents(
|
|
861
|
+
events->Array.map(e => e->Core.encEvent->Message.variantNameOfJson),
|
|
862
|
+
)
|
|
863
|
+
error->Option.forEach(err =>
|
|
864
|
+
ScenarioRecorder.thenError(err->Core.encError->Message.variantNameOfJson)
|
|
865
|
+
)
|
|
866
|
+
}
|
|
813
867
|
|
|
814
868
|
// The aggregate flavour has no append-condition footgun, so the shared core's
|
|
815
869
|
// comparisons are exposed directly once a refusal has been ruled out; only
|
|
@@ -819,25 +873,41 @@ module MakeFromAggregate = (Spec: AggregateSpec, Behavior: Behavior.T with modul
|
|
|
819
873
|
| Some(o) => o
|
|
820
874
|
| None => compare(events)
|
|
821
875
|
}
|
|
822
|
-
let thenEvents = (events, expected) =>
|
|
823
|
-
|
|
876
|
+
let thenEvents = (events, expected) => {
|
|
877
|
+
expects(~events=expected)
|
|
878
|
+
events->unlessRefused(Core.compareEvents(_, expected))
|
|
879
|
+
}
|
|
880
|
+
let thenCompareEvents = (events, expected, cmp) => {
|
|
881
|
+
expects(~events=expected)
|
|
824
882
|
events->unlessRefused(Core.compareEventsWith(_, expected, cmp))
|
|
883
|
+
}
|
|
825
884
|
let thenEvent = (events, expectedEvent) => thenEvents(events, [expectedEvent])
|
|
826
885
|
let thenCompareEvent = (events, expectedEvent, cmp) =>
|
|
827
886
|
thenCompareEvents(events, [expectedEvent], cmp)
|
|
828
|
-
let thenNoEvent = events =>
|
|
887
|
+
let thenNoEvent = events => {
|
|
888
|
+
if ScenarioRecorder.recording() {
|
|
889
|
+
ScenarioRecorder.thenNoEvent()
|
|
890
|
+
}
|
|
891
|
+
events->unlessRefused(Core.compareNoEvent)
|
|
892
|
+
}
|
|
829
893
|
|
|
830
|
-
let thenError = (events, expectedError) =>
|
|
894
|
+
let thenError = (events, expectedError) => {
|
|
895
|
+
expects(~error=expectedError)
|
|
831
896
|
events->unlessRefused(Core.matchesError(_, [], expectedError))
|
|
897
|
+
}
|
|
832
898
|
|
|
833
|
-
let thenEventWithError = (events, expectedEvent, expectedError) =>
|
|
899
|
+
let thenEventWithError = (events, expectedEvent, expectedError) => {
|
|
900
|
+
expects(~events=[expectedEvent], ~error=expectedError)
|
|
834
901
|
events->unlessRefused(Core.matchesError(_, [expectedEvent], expectedError))
|
|
902
|
+
}
|
|
835
903
|
|
|
836
|
-
let thenEventsWithError = (events, expectedEvents, expectedError) =>
|
|
904
|
+
let thenEventsWithError = (events, expectedEvents, expectedError) => {
|
|
905
|
+
expects(~events=expectedEvents, ~error=expectedError)
|
|
837
906
|
events->unlessRefused(Core.matchesError(_, expectedEvents, expectedError))
|
|
907
|
+
}
|
|
838
908
|
|
|
839
|
-
let
|
|
840
|
-
Guard.
|
|
909
|
+
let thenForbidden = events =>
|
|
910
|
+
Guard.thenForbidden(
|
|
841
911
|
~encEvents=Core.encEvents,
|
|
842
912
|
~decided=errors.contents->Array.get(0)->Option.map(Core.encError),
|
|
843
913
|
events,
|
|
@@ -15,6 +15,7 @@ import * as JestBind$ReventlessGwt from "./JestBind.res.mjs";
|
|
|
15
15
|
import * as Message$ReventlessCore from "@reventlessdev/reventless-core/src/Message.res.mjs";
|
|
16
16
|
import * as Authorization$Reventless from "@reventlessdev/reventless-spec/src/types/Authorization.res.mjs";
|
|
17
17
|
import * as DcbScopeInference$Reventless from "@reventlessdev/reventless-spec/src/components/DcbScopeInference.res.mjs";
|
|
18
|
+
import * as ScenarioRecorder$ReventlessGwt from "./ScenarioRecorder.res.mjs";
|
|
18
19
|
import * as CommandTopic_Helpers$ReventlessCore from "@reventlessdev/reventless-core/src/components/CommandTopic/CommandTopic_Helpers.res.mjs";
|
|
19
20
|
|
|
20
21
|
function roleNames(roles) {
|
|
@@ -155,7 +156,7 @@ function Acting(Spec) {
|
|
|
155
156
|
actual: encRefusal(r),
|
|
156
157
|
actualEvents: []
|
|
157
158
|
}));
|
|
158
|
-
let
|
|
159
|
+
let thenForbidden = (encEvents, decided, events) => {
|
|
159
160
|
let match = refusal.contents;
|
|
160
161
|
if (match !== undefined) {
|
|
161
162
|
return Outcome$ReventlessGwt.pass;
|
|
@@ -181,7 +182,7 @@ function Acting(Spec) {
|
|
|
181
182
|
refuses: refuses,
|
|
182
183
|
encRefusal: encRefusal,
|
|
183
184
|
unexpectedRefusal: unexpectedRefusal,
|
|
184
|
-
|
|
185
|
+
thenForbidden: thenForbidden
|
|
185
186
|
};
|
|
186
187
|
}
|
|
187
188
|
|
|
@@ -316,8 +317,8 @@ function AssertionCore(Spec) {
|
|
|
316
317
|
}
|
|
317
318
|
|
|
318
319
|
function Make(Spec) {
|
|
319
|
-
return
|
|
320
|
-
let test = (name, body) => JestBind$ReventlessGwt.test(Spec.name, name, body);
|
|
320
|
+
return Decision => {
|
|
321
|
+
let test = (name, body) => JestBind$ReventlessGwt.test(Spec.name, name, () => ScenarioRecorder$ReventlessGwt.around(Spec.name, name, body));
|
|
321
322
|
let errors = {
|
|
322
323
|
contents: []
|
|
323
324
|
};
|
|
@@ -494,6 +495,10 @@ function Make(Spec) {
|
|
|
494
495
|
let queryTagsTotal = q => Stdlib_Array.reduce(q, 0, (acc, qi) => acc + Stdlib_Option.mapOr(qi.tags, 0, t => t.length) | 0);
|
|
495
496
|
let givenEvents = consumed => consumed;
|
|
496
497
|
let whenCmd = (history, cmd) => {
|
|
498
|
+
if (ScenarioRecorder$ReventlessGwt.recording()) {
|
|
499
|
+
ScenarioRecorder$ReventlessGwt.given(history.map(e => Message$ReventlessCore.variantNameOfJson(Message$ReventlessCore.encode(e, Spec.consumedEventSchema))));
|
|
500
|
+
ScenarioRecorder$ReventlessGwt.whenCommand(Message$ReventlessCore.variantNameOfJson(Message$ReventlessCore.encode(cmd, commandSchema)));
|
|
501
|
+
}
|
|
497
502
|
errors.contents = [];
|
|
498
503
|
appendConditionFailure.contents = undefined;
|
|
499
504
|
let query = DcbTag$Reventless.buildQueryFromCommand(consumedEventTypes, Spec.commandSchema, cmd, tagKeysByEventType, crossPartitionTagKeys, payloadTagKeys);
|
|
@@ -522,13 +527,20 @@ function Make(Spec) {
|
|
|
522
527
|
if (refuses(history, cmd)) {
|
|
523
528
|
return [];
|
|
524
529
|
}
|
|
525
|
-
let events =
|
|
530
|
+
let events = Decision.decide(Stdlib_Array.reduce(history, Decision.initialState, Decision.evolve), cmd);
|
|
526
531
|
if (events.TAG === "Ok") {
|
|
527
532
|
return events._0;
|
|
528
533
|
}
|
|
529
534
|
errors.contents = [events._0];
|
|
530
535
|
return [];
|
|
531
536
|
};
|
|
537
|
+
let expects = (eventsOpt, error) => {
|
|
538
|
+
let events = eventsOpt !== undefined ? eventsOpt : [];
|
|
539
|
+
if (ScenarioRecorder$ReventlessGwt.recording()) {
|
|
540
|
+
ScenarioRecorder$ReventlessGwt.thenEvents(events.map(e => Message$ReventlessCore.variantNameOfJson(Message$ReventlessCore.encode(e, Spec.eventSchema))));
|
|
541
|
+
return Stdlib_Option.forEach(error, err => ScenarioRecorder$ReventlessGwt.thenError(Message$ReventlessCore.variantNameOfJson(Message$ReventlessCore.encode(err, Spec.errorSchema))));
|
|
542
|
+
}
|
|
543
|
+
};
|
|
532
544
|
let checkAppendCondition = () => {
|
|
533
545
|
let m = appendConditionFailure.contents;
|
|
534
546
|
if (m !== undefined) {
|
|
@@ -537,7 +549,7 @@ function Make(Spec) {
|
|
|
537
549
|
return unexpectedRefusal();
|
|
538
550
|
}
|
|
539
551
|
};
|
|
540
|
-
let
|
|
552
|
+
let thenForbidden = events => {
|
|
541
553
|
let decided = Stdlib_Option.map(errors.contents[0], encError);
|
|
542
554
|
let match = refusal.contents;
|
|
543
555
|
if (match !== undefined) {
|
|
@@ -555,6 +567,7 @@ function Make(Spec) {
|
|
|
555
567
|
}
|
|
556
568
|
};
|
|
557
569
|
let thenEvents = (events, expectedEvents) => {
|
|
570
|
+
expects(expectedEvents, undefined);
|
|
558
571
|
let o = checkAppendCondition();
|
|
559
572
|
if (o !== undefined) {
|
|
560
573
|
return o;
|
|
@@ -572,6 +585,9 @@ function Make(Spec) {
|
|
|
572
585
|
};
|
|
573
586
|
let thenEvent = (events, expectedEvent) => thenEvents(events, [expectedEvent]);
|
|
574
587
|
let thenNoEvent = events => {
|
|
588
|
+
if (ScenarioRecorder$ReventlessGwt.recording()) {
|
|
589
|
+
ScenarioRecorder$ReventlessGwt.thenNoEvent();
|
|
590
|
+
}
|
|
575
591
|
let o = checkAppendCondition();
|
|
576
592
|
if (o !== undefined) {
|
|
577
593
|
return o;
|
|
@@ -587,6 +603,7 @@ function Make(Spec) {
|
|
|
587
603
|
}
|
|
588
604
|
};
|
|
589
605
|
let thenError = (events, expectedError) => {
|
|
606
|
+
expects(undefined, Primitive_option.some(expectedError));
|
|
590
607
|
let o = checkAppendCondition();
|
|
591
608
|
if (o !== undefined) {
|
|
592
609
|
return o;
|
|
@@ -595,6 +612,7 @@ function Make(Spec) {
|
|
|
595
612
|
}
|
|
596
613
|
};
|
|
597
614
|
let thenEventWithError = (events, expectedEvent, expectedError) => {
|
|
615
|
+
expects([expectedEvent], Primitive_option.some(expectedError));
|
|
598
616
|
let o = checkAppendCondition();
|
|
599
617
|
if (o !== undefined) {
|
|
600
618
|
return o;
|
|
@@ -603,6 +621,7 @@ function Make(Spec) {
|
|
|
603
621
|
}
|
|
604
622
|
};
|
|
605
623
|
let thenEventsWithError = (events, expectedEvents, expectedError) => {
|
|
624
|
+
expects(expectedEvents, Primitive_option.some(expectedError));
|
|
606
625
|
let o = checkAppendCondition();
|
|
607
626
|
if (o !== undefined) {
|
|
608
627
|
return o;
|
|
@@ -667,7 +686,7 @@ function Make(Spec) {
|
|
|
667
686
|
thenEventWithError: thenEventWithError,
|
|
668
687
|
thenEventsWithError: thenEventsWithError,
|
|
669
688
|
thenError: thenError,
|
|
670
|
-
|
|
689
|
+
thenForbidden: thenForbidden,
|
|
671
690
|
thenAppendsConditionedOn: thenAppendsConditionedOn,
|
|
672
691
|
thenAppendsConditionedOnExactly: thenAppendsConditionedOnExactly
|
|
673
692
|
};
|
|
@@ -675,8 +694,8 @@ function Make(Spec) {
|
|
|
675
694
|
}
|
|
676
695
|
|
|
677
696
|
function MakeFromAggregate(Spec) {
|
|
678
|
-
return
|
|
679
|
-
let test = (name, body) => JestBind$ReventlessGwt.test(Spec.name, name, body);
|
|
697
|
+
return Decision => {
|
|
698
|
+
let test = (name, body) => JestBind$ReventlessGwt.test(Spec.name, name, () => ScenarioRecorder$ReventlessGwt.around(Spec.name, name, body));
|
|
680
699
|
let errors = {
|
|
681
700
|
contents: []
|
|
682
701
|
};
|
|
@@ -810,17 +829,28 @@ function MakeFromAggregate(Spec) {
|
|
|
810
829
|
}));
|
|
811
830
|
let givenEvents = events => events;
|
|
812
831
|
let whenCmd = (history, cmd) => {
|
|
832
|
+
if (ScenarioRecorder$ReventlessGwt.recording()) {
|
|
833
|
+
ScenarioRecorder$ReventlessGwt.given(history.map(e => Message$ReventlessCore.variantNameOfJson(Message$ReventlessCore.encode(e, Spec.eventSchema))));
|
|
834
|
+
ScenarioRecorder$ReventlessGwt.whenCommand(Message$ReventlessCore.variantNameOfJson(Message$ReventlessCore.encode(cmd, commandSchema)));
|
|
835
|
+
}
|
|
813
836
|
errors.contents = [];
|
|
814
837
|
if (refuses(history, cmd)) {
|
|
815
838
|
return [];
|
|
816
839
|
}
|
|
817
|
-
let events =
|
|
840
|
+
let events = Decision.decide(Stdlib_Array.reduce(history, Decision.initialState, Decision.evolve), cmd);
|
|
818
841
|
if (events.TAG === "Ok") {
|
|
819
842
|
return events._0;
|
|
820
843
|
}
|
|
821
844
|
errors.contents = [events._0];
|
|
822
845
|
return [];
|
|
823
846
|
};
|
|
847
|
+
let expects = (eventsOpt, error) => {
|
|
848
|
+
let events = eventsOpt !== undefined ? eventsOpt : [];
|
|
849
|
+
if (ScenarioRecorder$ReventlessGwt.recording()) {
|
|
850
|
+
ScenarioRecorder$ReventlessGwt.thenEvents(events.map(e => Message$ReventlessCore.variantNameOfJson(Message$ReventlessCore.encode(e, Spec.eventSchema))));
|
|
851
|
+
return Stdlib_Option.forEach(error, err => ScenarioRecorder$ReventlessGwt.thenError(Message$ReventlessCore.variantNameOfJson(Message$ReventlessCore.encode(err, Spec.errorSchema))));
|
|
852
|
+
}
|
|
853
|
+
};
|
|
824
854
|
let unlessRefused = (events, compare) => {
|
|
825
855
|
let o = unexpectedRefusal();
|
|
826
856
|
if (o !== undefined) {
|
|
@@ -829,39 +859,59 @@ function MakeFromAggregate(Spec) {
|
|
|
829
859
|
return compare(events);
|
|
830
860
|
}
|
|
831
861
|
};
|
|
832
|
-
let thenEvents = (events, expected) =>
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
|
|
862
|
+
let thenEvents = (events, expected) => {
|
|
863
|
+
expects(expected, undefined);
|
|
864
|
+
return unlessRefused(events, __x => {
|
|
865
|
+
if (errors.contents.length !== 0) {
|
|
866
|
+
return unexpectedError(__x);
|
|
867
|
+
} else if (Primitive_object.equal(__x.map(encEvent), expected.map(encEvent))) {
|
|
868
|
+
return Outcome$ReventlessGwt.pass;
|
|
869
|
+
} else {
|
|
870
|
+
return Outcome$ReventlessGwt.fail({
|
|
871
|
+
TAG: "EventsMismatch",
|
|
872
|
+
expected: expected.map(encEvent),
|
|
873
|
+
actual: __x.map(encEvent)
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
};
|
|
878
|
+
let thenCompareEvents = (events, expected, cmp) => {
|
|
879
|
+
expects(expected, undefined);
|
|
880
|
+
return unlessRefused(events, __x => {
|
|
881
|
+
if (errors.contents.length !== 0) {
|
|
882
|
+
return unexpectedError(__x);
|
|
883
|
+
} else if (__x.length === expected.length && Stdlib_Array.zip(__x, expected).every(param => cmp(param[0], param[1]))) {
|
|
884
|
+
return Outcome$ReventlessGwt.pass;
|
|
885
|
+
} else {
|
|
886
|
+
return Outcome$ReventlessGwt.fail({
|
|
887
|
+
TAG: "EventsMismatch",
|
|
888
|
+
expected: expected.map(encEvent),
|
|
889
|
+
actual: __x.map(encEvent)
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
});
|
|
893
|
+
};
|
|
858
894
|
let thenEvent = (events, expectedEvent) => thenEvents(events, [expectedEvent]);
|
|
859
895
|
let thenCompareEvent = (events, expectedEvent, cmp) => thenCompareEvents(events, [expectedEvent], cmp);
|
|
860
|
-
let thenNoEvent = events =>
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
896
|
+
let thenNoEvent = events => {
|
|
897
|
+
if (ScenarioRecorder$ReventlessGwt.recording()) {
|
|
898
|
+
ScenarioRecorder$ReventlessGwt.thenNoEvent();
|
|
899
|
+
}
|
|
900
|
+
return unlessRefused(events, compareNoEvent);
|
|
901
|
+
};
|
|
902
|
+
let thenError = (events, expectedError) => {
|
|
903
|
+
expects(undefined, Primitive_option.some(expectedError));
|
|
904
|
+
return unlessRefused(events, __x => matchesError(__x, [], expectedError));
|
|
905
|
+
};
|
|
906
|
+
let thenEventWithError = (events, expectedEvent, expectedError) => {
|
|
907
|
+
expects([expectedEvent], Primitive_option.some(expectedError));
|
|
908
|
+
return unlessRefused(events, __x => matchesError(__x, [expectedEvent], expectedError));
|
|
909
|
+
};
|
|
910
|
+
let thenEventsWithError = (events, expectedEvents, expectedError) => {
|
|
911
|
+
expects(expectedEvents, Primitive_option.some(expectedError));
|
|
912
|
+
return unlessRefused(events, __x => matchesError(__x, expectedEvents, expectedError));
|
|
913
|
+
};
|
|
914
|
+
let thenForbidden = events => {
|
|
865
915
|
let decided = Stdlib_Option.map(errors.contents[0], encError);
|
|
866
916
|
let match = refusal.contents;
|
|
867
917
|
if (match !== undefined) {
|
|
@@ -894,7 +944,7 @@ function MakeFromAggregate(Spec) {
|
|
|
894
944
|
thenCompareEvents: thenCompareEvents,
|
|
895
945
|
thenEventsWithError: thenEventsWithError,
|
|
896
946
|
thenError: thenError,
|
|
897
|
-
|
|
947
|
+
thenForbidden: thenForbidden
|
|
898
948
|
};
|
|
899
949
|
};
|
|
900
950
|
}
|