@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/tests/HintTest.res
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// A state mismatch's hint names the fold that produced the state: `project` for
|
|
2
|
-
// a view's scenario, `evolve` for a
|
|
2
|
+
// a view's scenario, `evolve` for a decision's.
|
|
3
3
|
|
|
4
4
|
open JestGlobals
|
|
5
5
|
|
|
@@ -46,7 +46,7 @@ describe("Hint for a StateMismatch", () => {
|
|
|
46
46
|
}
|
|
47
47
|
})
|
|
48
48
|
|
|
49
|
-
testSync("a
|
|
49
|
+
testSync("a decision's differing state still points at evolve", () => {
|
|
50
50
|
let hint = Hint.forMismatch(
|
|
51
51
|
~slice="AddCategory",
|
|
52
52
|
Outcome.StateMismatch({key: "c1", expected: None, actual: None}),
|
package/tests/HintTest.res.mjs
CHANGED
|
@@ -80,7 +80,7 @@ globalThis.describe("Hint for a StateMismatch", () => {
|
|
|
80
80
|
globalThis.expect(hint.locus).toBe("CategoriesView.project");
|
|
81
81
|
globalThis.expect(hint.message).toBe("project() filed a different row than expected. Check the arm for the event and the key it sets.");
|
|
82
82
|
});
|
|
83
|
-
globalThis.test("a
|
|
83
|
+
globalThis.test("a decision's differing state still points at evolve", () => {
|
|
84
84
|
let hint = Hint$ReventlessGwt.forMismatch("AddCategory", {
|
|
85
85
|
TAG: "StateMismatch",
|
|
86
86
|
key: "c1",
|
package/tests/MappingGwtTest.res
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// module types.
|
|
8
8
|
|
|
9
9
|
// ---------------------------------------------------------------------------
|
|
10
|
-
// Shared Aggregate specs +
|
|
10
|
+
// Shared Aggregate specs + decisions used by the Aggr→* cases below.
|
|
11
11
|
// ---------------------------------------------------------------------------
|
|
12
12
|
|
|
13
13
|
module CategorySpec = {
|
|
@@ -26,7 +26,7 @@ module CategorySpec = {
|
|
|
26
26
|
let moduleUrl = ""
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
module
|
|
29
|
+
module CategoryDecision = {
|
|
30
30
|
module Spec = CategorySpec
|
|
31
31
|
type state = NotCreated | Created
|
|
32
32
|
let initialState = NotCreated
|
|
@@ -64,7 +64,7 @@ module ProductSpec = {
|
|
|
64
64
|
let moduleUrl = ""
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
module
|
|
67
|
+
module ProductDecision = {
|
|
68
68
|
module Spec = ProductSpec
|
|
69
69
|
type state = Pristine | Mirrored
|
|
70
70
|
let initialState = Pristine
|
|
@@ -164,8 +164,8 @@ module InventorySlice = {
|
|
|
164
164
|
// Adapter aliases — built once, shared across mappings.
|
|
165
165
|
// ---------------------------------------------------------------------------
|
|
166
166
|
|
|
167
|
-
module CategorySource = Mapping_GWT.
|
|
168
|
-
module ProductTarget = Mapping_GWT.
|
|
167
|
+
module CategorySource = Mapping_GWT.FromDecision(CategorySpec, CategoryDecision)
|
|
168
|
+
module ProductTarget = Mapping_GWT.FromDecision(ProductSpec, ProductDecision)
|
|
169
169
|
module NotificationSource = Mapping_GWT.FromStateChangeSlice(NotificationSlice)
|
|
170
170
|
module NotificationTarget = Mapping_GWT.FromStateChangeSlice(NotificationSlice)
|
|
171
171
|
module InventoryTarget = Mapping_GWT.FromStateChangeSlice(InventorySlice)
|
|
@@ -69,7 +69,7 @@ function decide(state, cmd) {
|
|
|
69
69
|
|
|
70
70
|
let moduleUrl$1 = "";
|
|
71
71
|
|
|
72
|
-
let
|
|
72
|
+
let CategoryDecision = {
|
|
73
73
|
Spec: undefined,
|
|
74
74
|
initialState: "NotCreated",
|
|
75
75
|
snapshot: undefined,
|
|
@@ -145,7 +145,7 @@ function decide$1(state, cmd) {
|
|
|
145
145
|
|
|
146
146
|
let moduleUrl$3 = "";
|
|
147
147
|
|
|
148
|
-
let
|
|
148
|
+
let ProductDecision = {
|
|
149
149
|
Spec: undefined,
|
|
150
150
|
initialState: "Pristine",
|
|
151
151
|
snapshot: undefined,
|
|
@@ -298,7 +298,7 @@ let InventorySlice = {
|
|
|
298
298
|
readConsistency: "EscalateOnRetry"
|
|
299
299
|
};
|
|
300
300
|
|
|
301
|
-
let CategorySource = Mapping_GWT$ReventlessGwt.
|
|
301
|
+
let CategorySource = Mapping_GWT$ReventlessGwt.FromDecision({
|
|
302
302
|
Id: {
|
|
303
303
|
schema: Id$Reventless.StringPure.schema,
|
|
304
304
|
make: prim => prim,
|
|
@@ -339,7 +339,7 @@ let CategorySource = Mapping_GWT$ReventlessGwt.FromBehavior({
|
|
|
339
339
|
moduleUrl: moduleUrl$1
|
|
340
340
|
});
|
|
341
341
|
|
|
342
|
-
let ProductTarget = Mapping_GWT$ReventlessGwt.
|
|
342
|
+
let ProductTarget = Mapping_GWT$ReventlessGwt.FromDecision({
|
|
343
343
|
Id: {
|
|
344
344
|
schema: Id$Reventless.StringPure.schema,
|
|
345
345
|
make: prim => prim,
|
|
@@ -611,9 +611,9 @@ DcbToDcbGwt.describe("Inventory → Notification (DCB → DCB)", () => {
|
|
|
611
611
|
|
|
612
612
|
export {
|
|
613
613
|
CategorySpec,
|
|
614
|
-
|
|
614
|
+
CategoryDecision,
|
|
615
615
|
ProductSpec,
|
|
616
|
-
|
|
616
|
+
ProductDecision,
|
|
617
617
|
NotificationSlice,
|
|
618
618
|
InventorySlice,
|
|
619
619
|
CategorySource,
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
open JestGlobals
|
|
2
|
+
|
|
3
|
+
// `ScenarioRecorder` writes what a decision test runs as a sidecar `check-lifecycle` reads, for
|
|
4
|
+
// a suite whose source names nothing (one written against a binding). Each case records into a
|
|
5
|
+
// file of its own under the OS temp folder, named by `setFile` as an external runner would.
|
|
6
|
+
|
|
7
|
+
let dir = NodeFs.mkdtempSync(NodePath.join([NodeOs.tmpdir(), "scenario-recorder-"]))
|
|
8
|
+
let testFile = name => NodePath.join([dir, `${name}_GWT.res.mjs`])
|
|
9
|
+
let sidecarOf = name => NodePath.join([dir, `${name}_GWT.gwt.json`])
|
|
10
|
+
|
|
11
|
+
let read = path => NodeFs.readFileSync(path)->JSON.parseOrThrow
|
|
12
|
+
|
|
13
|
+
let field = (json: JSON.t, key) =>
|
|
14
|
+
switch json {
|
|
15
|
+
| Object(d) => d->Dict.get(key)
|
|
16
|
+
| _ => None
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// One recorded test: the history, the command, and the event it expects.
|
|
20
|
+
let run = (~spec, ~title, ~expects) =>
|
|
21
|
+
ScenarioRecorder.around(~spec, title, () => {
|
|
22
|
+
ScenarioRecorder.given(["CustomerRegistered"])
|
|
23
|
+
ScenarioRecorder.whenCommand("RequestNotification")
|
|
24
|
+
ScenarioRecorder.thenEvents([expects])
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
let switched = (on, f) => {
|
|
28
|
+
NodeProcess.env->Dict.set("REVENTLESS_EMIT_SIDECAR", on ? "1" : "0")
|
|
29
|
+
let out = f()
|
|
30
|
+
NodeProcess.env->Dict.delete("REVENTLESS_EMIT_SIDECAR")
|
|
31
|
+
out
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe("ScenarioRecorder", () => {
|
|
35
|
+
testSync("with the switch off it writes nothing", () => {
|
|
36
|
+
ScenarioRecorder.setFile(testFile("Off"))
|
|
37
|
+
switched(false, () => run(~spec="Prefs", ~title="t", ~expects="NotificationRequested"))
|
|
38
|
+
expect(NodeFs.existsSync(sidecarOf("Off")))->toBe(false)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
testSync("a test run with the switch on is a sidecar naming its spec as the component", () => {
|
|
42
|
+
ScenarioRecorder.setFile(testFile("Conformance"))
|
|
43
|
+
switched(
|
|
44
|
+
true,
|
|
45
|
+
() => {
|
|
46
|
+
run(~spec="Prefs", ~title="undeliverable", ~expects="NotificationUndeliverable")
|
|
47
|
+
run(~spec="Prefs", ~title="deferred", ~expects="NotificationDeferred")
|
|
48
|
+
},
|
|
49
|
+
)
|
|
50
|
+
let json = read(sidecarOf("Conformance"))
|
|
51
|
+
let scenarios = switch json->field("scenarios") {
|
|
52
|
+
| Some(Array(xs)) => xs
|
|
53
|
+
| _ => []
|
|
54
|
+
}
|
|
55
|
+
expect((
|
|
56
|
+
json->field("stem"),
|
|
57
|
+
json->field("recorded"),
|
|
58
|
+
scenarios->Array.map(
|
|
59
|
+
s =>
|
|
60
|
+
switch (s->field("when"), s->field("then")) {
|
|
61
|
+
| (Some(Array([w])), Some(Array([t]))) => (w->field("element"), t->field("element"))
|
|
62
|
+
| _ => (None, None)
|
|
63
|
+
},
|
|
64
|
+
),
|
|
65
|
+
))->toEqual((
|
|
66
|
+
Some(JSON.String("Prefs")),
|
|
67
|
+
Some(JSON.Boolean(true)),
|
|
68
|
+
[
|
|
69
|
+
(Some(JSON.String("RequestNotification")), Some(JSON.String("NotificationUndeliverable"))),
|
|
70
|
+
(Some(JSON.String("RequestNotification")), Some(JSON.String("NotificationDeferred"))),
|
|
71
|
+
],
|
|
72
|
+
))
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
testSync("a sidecar the PPX wrote is left as it is", () => {
|
|
76
|
+
let ppx = `{"stem":"Mine_GWT","scenarios":[]}`
|
|
77
|
+
NodeFs.writeFileSync(sidecarOf("Mine"), ppx)
|
|
78
|
+
ScenarioRecorder.setFile(testFile("Mine"))
|
|
79
|
+
switched(true, () => run(~spec="Prefs", ~title="t", ~expects="NotificationRequested"))
|
|
80
|
+
expect(NodeFs.readFileSync(sidecarOf("Mine")))->toBe(ppx)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
testSync("a second spec in one file gets a sidecar of its own", () => {
|
|
84
|
+
ScenarioRecorder.setFile(testFile("Two"))
|
|
85
|
+
switched(
|
|
86
|
+
true,
|
|
87
|
+
() => {
|
|
88
|
+
run(~spec="Prefs", ~title="a", ~expects="NotificationRequested")
|
|
89
|
+
run(~spec="Claims", ~title="b", ~expects="NotificationSourceClaimed")
|
|
90
|
+
},
|
|
91
|
+
)
|
|
92
|
+
expect((
|
|
93
|
+
read(sidecarOf("Two"))->field("stem"),
|
|
94
|
+
read(NodePath.join([dir, "Two_GWT.Claims.gwt.json"]))->field("stem"),
|
|
95
|
+
))->toEqual((Some(JSON.String("Prefs")), Some(JSON.String("Claims"))))
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
testSync("steps outside a recorded test record nothing", () =>
|
|
99
|
+
expect(ScenarioRecorder.recording())->toBe(false)
|
|
100
|
+
)
|
|
101
|
+
})
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Nodefs from "node:fs";
|
|
4
|
+
import * as Nodeos from "node:os";
|
|
5
|
+
import * as Nodepath from "node:path";
|
|
6
|
+
import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
|
|
7
|
+
import * as ScenarioRecorder$ReventlessGwt from "../src/ScenarioRecorder.res.mjs";
|
|
8
|
+
|
|
9
|
+
let dir = Nodefs.mkdtempSync(Nodepath.join(Nodeos.tmpdir(), "scenario-recorder-"));
|
|
10
|
+
|
|
11
|
+
function testFile(name) {
|
|
12
|
+
return Nodepath.join(dir, name + `_GWT.res.mjs`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function sidecarOf(name) {
|
|
16
|
+
return Nodepath.join(dir, name + `_GWT.gwt.json`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function read(path) {
|
|
20
|
+
return JSON.parse(Nodefs.readFileSync(path, "utf8"));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function field(json, key) {
|
|
24
|
+
if (typeof json === "object" && json !== null && !Array.isArray(json)) {
|
|
25
|
+
return json[key];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function run(spec, title, expects) {
|
|
30
|
+
ScenarioRecorder$ReventlessGwt.around(spec, title, () => {
|
|
31
|
+
ScenarioRecorder$ReventlessGwt.given(["CustomerRegistered"]);
|
|
32
|
+
ScenarioRecorder$ReventlessGwt.whenCommand("RequestNotification");
|
|
33
|
+
ScenarioRecorder$ReventlessGwt.thenEvents([expects]);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function switched(on, f) {
|
|
38
|
+
process.env["REVENTLESS_EMIT_SIDECAR"] = on ? "1" : "0";
|
|
39
|
+
let out = f();
|
|
40
|
+
Stdlib_Dict.$$delete(process.env, "REVENTLESS_EMIT_SIDECAR");
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
globalThis.describe("ScenarioRecorder", () => {
|
|
45
|
+
globalThis.test("with the switch off it writes nothing", () => {
|
|
46
|
+
ScenarioRecorder$ReventlessGwt.setFile(testFile("Off"));
|
|
47
|
+
switched(false, () => run("Prefs", "t", "NotificationRequested"));
|
|
48
|
+
globalThis.expect(Nodefs.existsSync(sidecarOf("Off"))).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
globalThis.test("a test run with the switch on is a sidecar naming its spec as the component", () => {
|
|
51
|
+
ScenarioRecorder$ReventlessGwt.setFile(testFile("Conformance"));
|
|
52
|
+
switched(true, () => {
|
|
53
|
+
run("Prefs", "undeliverable", "NotificationUndeliverable");
|
|
54
|
+
run("Prefs", "deferred", "NotificationDeferred");
|
|
55
|
+
});
|
|
56
|
+
let path = sidecarOf("Conformance");
|
|
57
|
+
let json = JSON.parse(Nodefs.readFileSync(path, "utf8"));
|
|
58
|
+
let match = field(json, "scenarios");
|
|
59
|
+
let scenarios = match !== undefined ? (
|
|
60
|
+
Array.isArray(match) ? match : []
|
|
61
|
+
) : [];
|
|
62
|
+
globalThis.expect([
|
|
63
|
+
field(json, "stem"),
|
|
64
|
+
field(json, "recorded"),
|
|
65
|
+
scenarios.map(s => {
|
|
66
|
+
let match = field(s, "when");
|
|
67
|
+
let match$1 = field(s, "then");
|
|
68
|
+
if (match === undefined) {
|
|
69
|
+
return [
|
|
70
|
+
undefined,
|
|
71
|
+
undefined
|
|
72
|
+
];
|
|
73
|
+
}
|
|
74
|
+
if (!Array.isArray(match)) {
|
|
75
|
+
return [
|
|
76
|
+
undefined,
|
|
77
|
+
undefined
|
|
78
|
+
];
|
|
79
|
+
}
|
|
80
|
+
if (match.length !== 1) {
|
|
81
|
+
return [
|
|
82
|
+
undefined,
|
|
83
|
+
undefined
|
|
84
|
+
];
|
|
85
|
+
}
|
|
86
|
+
let w = match[0];
|
|
87
|
+
if (match$1 === undefined) {
|
|
88
|
+
return [
|
|
89
|
+
undefined,
|
|
90
|
+
undefined
|
|
91
|
+
];
|
|
92
|
+
}
|
|
93
|
+
if (!Array.isArray(match$1)) {
|
|
94
|
+
return [
|
|
95
|
+
undefined,
|
|
96
|
+
undefined
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
if (match$1.length !== 1) {
|
|
100
|
+
return [
|
|
101
|
+
undefined,
|
|
102
|
+
undefined
|
|
103
|
+
];
|
|
104
|
+
}
|
|
105
|
+
let t = match$1[0];
|
|
106
|
+
return [
|
|
107
|
+
field(w, "element"),
|
|
108
|
+
field(t, "element")
|
|
109
|
+
];
|
|
110
|
+
})
|
|
111
|
+
]).toEqual([
|
|
112
|
+
"Prefs",
|
|
113
|
+
true,
|
|
114
|
+
[
|
|
115
|
+
[
|
|
116
|
+
"RequestNotification",
|
|
117
|
+
"NotificationUndeliverable"
|
|
118
|
+
],
|
|
119
|
+
[
|
|
120
|
+
"RequestNotification",
|
|
121
|
+
"NotificationDeferred"
|
|
122
|
+
]
|
|
123
|
+
]
|
|
124
|
+
]);
|
|
125
|
+
});
|
|
126
|
+
globalThis.test("a sidecar the PPX wrote is left as it is", () => {
|
|
127
|
+
let ppx = `{"stem":"Mine_GWT","scenarios":[]}`;
|
|
128
|
+
Nodefs.writeFileSync(sidecarOf("Mine"), ppx, "utf8");
|
|
129
|
+
ScenarioRecorder$ReventlessGwt.setFile(testFile("Mine"));
|
|
130
|
+
switched(true, () => run("Prefs", "t", "NotificationRequested"));
|
|
131
|
+
globalThis.expect(Nodefs.readFileSync(sidecarOf("Mine"), "utf8")).toBe(ppx);
|
|
132
|
+
});
|
|
133
|
+
globalThis.test("a second spec in one file gets a sidecar of its own", () => {
|
|
134
|
+
ScenarioRecorder$ReventlessGwt.setFile(testFile("Two"));
|
|
135
|
+
switched(true, () => {
|
|
136
|
+
run("Prefs", "a", "NotificationRequested");
|
|
137
|
+
run("Claims", "b", "NotificationSourceClaimed");
|
|
138
|
+
});
|
|
139
|
+
let path = sidecarOf("Two");
|
|
140
|
+
let path$1 = Nodepath.join(dir, "Two_GWT.Claims.gwt.json");
|
|
141
|
+
globalThis.expect([
|
|
142
|
+
field(JSON.parse(Nodefs.readFileSync(path, "utf8")), "stem"),
|
|
143
|
+
field(JSON.parse(Nodefs.readFileSync(path$1, "utf8")), "stem")
|
|
144
|
+
]).toEqual([
|
|
145
|
+
"Prefs",
|
|
146
|
+
"Claims"
|
|
147
|
+
]);
|
|
148
|
+
});
|
|
149
|
+
globalThis.test("steps outside a recorded test record nothing", () => {
|
|
150
|
+
globalThis.expect(ScenarioRecorder$ReventlessGwt.recording()).toBe(false);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
export {
|
|
155
|
+
dir,
|
|
156
|
+
testFile,
|
|
157
|
+
sidecarOf,
|
|
158
|
+
read,
|
|
159
|
+
field,
|
|
160
|
+
run,
|
|
161
|
+
switched,
|
|
162
|
+
}
|
|
163
|
+
/* dir Not a pure module */
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// External-Spec verification test for @@reventless.gwt.
|
|
2
2
|
//
|
|
3
3
|
// Zero payload, no local module, no alias. The PPX resolves:
|
|
4
|
-
// - Kind from the folder segment "StateChange" ->
|
|
4
|
+
// - Kind from the folder segment "StateChange" -> Decision
|
|
5
5
|
// - Spec from the filename stem (strip "_GWT") -> ExternalAddCategorySlice
|
|
6
|
-
// - Impl derived as
|
|
6
|
+
// - Impl derived as ExternalAddCategorySlice_Decision
|
|
7
7
|
// - open ExternalAddCategorySlice +
|
|
8
|
-
// include
|
|
8
|
+
// include Decision_GWT.Make(ExternalAddCategorySlice, ExternalAddCategorySlice_Decision)
|
|
9
9
|
//
|
|
10
10
|
// Mirrors the canonical consumer pattern documented in
|
|
11
11
|
// docs/guides/given-when-then.md § 4.10.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
-
import * as
|
|
3
|
+
import * as Decision_GWT$ReventlessGwt from "../../src/Decision_GWT.res.mjs";
|
|
4
4
|
import * as ExternalAddCategorySlice$ReventlessGwt from "../../src/StateChange/ExternalAddCategorySlice.res.mjs";
|
|
5
|
-
import * as
|
|
5
|
+
import * as ExternalAddCategorySlice_Decision$ReventlessGwt from "../../src/StateChange/ExternalAddCategorySlice_Decision.res.mjs";
|
|
6
6
|
|
|
7
|
-
let include =
|
|
7
|
+
let include = Decision_GWT$ReventlessGwt.Make(ExternalAddCategorySlice$ReventlessGwt)(ExternalAddCategorySlice_Decision$ReventlessGwt);
|
|
8
8
|
|
|
9
9
|
let describe = include.describe;
|
|
10
10
|
|
|
@@ -51,7 +51,7 @@ let thenEventWithError = include.thenEventWithError;
|
|
|
51
51
|
|
|
52
52
|
let thenEventsWithError = include.thenEventsWithError;
|
|
53
53
|
|
|
54
|
-
let
|
|
54
|
+
let thenForbidden = include.thenForbidden;
|
|
55
55
|
|
|
56
56
|
let thenAppendsConditionedOn = include.thenAppendsConditionedOn;
|
|
57
57
|
|
|
@@ -72,7 +72,7 @@ export {
|
|
|
72
72
|
thenEventWithError,
|
|
73
73
|
thenEventsWithError,
|
|
74
74
|
thenError,
|
|
75
|
-
|
|
75
|
+
thenForbidden,
|
|
76
76
|
thenAppendsConditionedOn,
|
|
77
77
|
thenAppendsConditionedOnExactly,
|
|
78
78
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
-
import * as
|
|
3
|
+
import * as Decision_GWT$ReventlessGwt from "../../src/Decision_GWT.res.mjs";
|
|
4
4
|
import * as WithFixtures$ReventlessGwt from "../../src/StateChange/WithFixtures.res.mjs";
|
|
5
|
-
import * as
|
|
5
|
+
import * as WithFixtures_Decision$ReventlessGwt from "../../src/StateChange/WithFixtures_Decision.res.mjs";
|
|
6
6
|
import * as WithFixtures_Fixtures$ReventlessGwt from "./WithFixtures_Fixtures.res.mjs";
|
|
7
7
|
|
|
8
|
-
let include =
|
|
8
|
+
let include = Decision_GWT$ReventlessGwt.Make(WithFixtures$ReventlessGwt)(WithFixtures_Decision$ReventlessGwt);
|
|
9
9
|
|
|
10
10
|
let describe = include.describe;
|
|
11
11
|
|
|
@@ -37,7 +37,7 @@ let thenEventsWithError = include.thenEventsWithError;
|
|
|
37
37
|
|
|
38
38
|
let thenError = include.thenError;
|
|
39
39
|
|
|
40
|
-
let
|
|
40
|
+
let thenForbidden = include.thenForbidden;
|
|
41
41
|
|
|
42
42
|
let thenAppendsConditionedOn = include.thenAppendsConditionedOn;
|
|
43
43
|
|
|
@@ -58,7 +58,7 @@ export {
|
|
|
58
58
|
thenEventWithError,
|
|
59
59
|
thenEventsWithError,
|
|
60
60
|
thenError,
|
|
61
|
-
|
|
61
|
+
thenForbidden,
|
|
62
62
|
thenAppendsConditionedOn,
|
|
63
63
|
thenAppendsConditionedOnExactly,
|
|
64
64
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
-
import * as
|
|
3
|
+
import * as Decision_GWT$ReventlessGwt from "../../src/Decision_GWT.res.mjs";
|
|
4
4
|
import * as WithManualOpen$ReventlessGwt from "../../src/StateChange/WithManualOpen.res.mjs";
|
|
5
|
-
import * as
|
|
5
|
+
import * as WithManualOpen_Decision$ReventlessGwt from "../../src/StateChange/WithManualOpen_Decision.res.mjs";
|
|
6
6
|
import * as WithManualOpen_Fixtures$ReventlessGwt from "./WithManualOpen_Fixtures.res.mjs";
|
|
7
7
|
|
|
8
|
-
let include =
|
|
8
|
+
let include = Decision_GWT$ReventlessGwt.Make(WithManualOpen$ReventlessGwt)(WithManualOpen_Decision$ReventlessGwt);
|
|
9
9
|
|
|
10
10
|
let describe = include.describe;
|
|
11
11
|
|
|
@@ -37,7 +37,7 @@ let thenEventsWithError = include.thenEventsWithError;
|
|
|
37
37
|
|
|
38
38
|
let thenError = include.thenError;
|
|
39
39
|
|
|
40
|
-
let
|
|
40
|
+
let thenForbidden = include.thenForbidden;
|
|
41
41
|
|
|
42
42
|
let thenAppendsConditionedOn = include.thenAppendsConditionedOn;
|
|
43
43
|
|
|
@@ -58,7 +58,7 @@ export {
|
|
|
58
58
|
thenEventWithError,
|
|
59
59
|
thenEventsWithError,
|
|
60
60
|
thenError,
|
|
61
|
-
|
|
61
|
+
thenForbidden,
|
|
62
62
|
thenAppendsConditionedOn,
|
|
63
63
|
thenAppendsConditionedOnExactly,
|
|
64
64
|
}
|
|
@@ -8,7 +8,7 @@ module ShelveSlice = {
|
|
|
8
8
|
let name = "Shelve"
|
|
9
9
|
|
|
10
10
|
// Inside the spec, so the zero-argument form still finds the spec and its
|
|
11
|
-
//
|
|
11
|
+
// decision first; the rule's `Roles` resolves by ordinary scoping either way.
|
|
12
12
|
module Roles = {
|
|
13
13
|
type t =
|
|
14
14
|
| Admin
|
|
@@ -35,7 +35,7 @@ module ShelveSlice = {
|
|
|
35
35
|
type event = Done({shelfId: @s.matches(Reventless.DcbTag.string) string})
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
module
|
|
38
|
+
module ShelveSliceDecision = {
|
|
39
39
|
module Spec = ShelveSlice
|
|
40
40
|
open ShelveSlice
|
|
41
41
|
|
|
@@ -67,23 +67,23 @@ describe("a command's rule, checked against the caller's roles", () => {
|
|
|
67
67
|
)
|
|
68
68
|
|
|
69
69
|
test("refused for a signed-in caller holding none of them", () =>
|
|
70
|
-
givenEvents([])->asCaller(Caller.owner("c1"))->whenCmd(Shelve({shelfId: s1}))->
|
|
70
|
+
givenEvents([])->asCaller(Caller.owner("c1"))->whenCmd(Shelve({shelfId: s1}))->thenForbidden
|
|
71
71
|
)
|
|
72
72
|
|
|
73
73
|
test("refused for an anonymous caller", () =>
|
|
74
|
-
givenEvents([])->asCaller(Caller.anonymous)->whenCmd(Shelve({shelfId: s1}))->
|
|
74
|
+
givenEvents([])->asCaller(Caller.anonymous)->whenCmd(Shelve({shelfId: s1}))->thenForbidden
|
|
75
75
|
)
|
|
76
76
|
|
|
77
77
|
// Elevation lifts ownership rules, not rules about who may issue a command.
|
|
78
78
|
test("refused for an operator, who holds no role", () =>
|
|
79
|
-
givenEvents([])->asCaller(Caller.operator)->whenCmd(Shelve({shelfId: s1}))->
|
|
79
|
+
givenEvents([])->asCaller(Caller.operator)->whenCmd(Shelve({shelfId: s1}))->thenForbidden
|
|
80
80
|
)
|
|
81
81
|
|
|
82
82
|
test("DenyAll refuses every caller, Admin included", () =>
|
|
83
83
|
givenEvents([])
|
|
84
84
|
->asCaller(Caller.inRoles([Admin]))
|
|
85
85
|
->whenCmd(Purge({shelfId: s1}))
|
|
86
|
-
->
|
|
86
|
+
->thenForbidden
|
|
87
87
|
)
|
|
88
88
|
|
|
89
89
|
test("AllowAnonymous accepts an anonymous caller", () =>
|
|
@@ -94,7 +94,7 @@ describe("a command's rule, checked against the caller's roles", () => {
|
|
|
94
94
|
)
|
|
95
95
|
|
|
96
96
|
test("the default rule refuses an anonymous caller", () =>
|
|
97
|
-
givenEvents([])->asCaller(Caller.anonymous)->whenCmd(Browse({shelfId: s1}))->
|
|
97
|
+
givenEvents([])->asCaller(Caller.anonymous)->whenCmd(Browse({shelfId: s1}))->thenForbidden
|
|
98
98
|
)
|
|
99
99
|
|
|
100
100
|
test("a scenario without asCaller is checked against no rule", () =>
|
|
@@ -132,7 +132,7 @@ module ClaimSlice = {
|
|
|
132
132
|
type event = Done({shelfId: @s.matches(Reventless.DcbTag.string) string})
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
module Claim =
|
|
135
|
+
module Claim = Decision_GWT.Make(
|
|
136
136
|
ClaimSlice,
|
|
137
137
|
{
|
|
138
138
|
type state = unit
|
|
@@ -162,13 +162,13 @@ describe("the rule, then ownership", () => {
|
|
|
162
162
|
Claim.givenEvents(claimedByC1)
|
|
163
163
|
->Claim.asCaller(Claim.Caller.owner("c2", ~roles=[Merchandiser]))
|
|
164
164
|
->Claim.whenCmd(Shelve({shelfId: s1}))
|
|
165
|
-
->Claim.
|
|
165
|
+
->Claim.thenForbidden
|
|
166
166
|
)
|
|
167
167
|
|
|
168
168
|
Claim.test("owning a thing does not pass a rule the owner fails", () =>
|
|
169
169
|
Claim.givenEvents(claimedByC1)
|
|
170
170
|
->Claim.asCaller(Claim.Caller.owner("c1"))
|
|
171
171
|
->Claim.whenCmd(Shelve({shelfId: s1}))
|
|
172
|
-
->Claim.
|
|
172
|
+
->Claim.thenForbidden
|
|
173
173
|
)
|
|
174
174
|
})
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import * as Sury from "sury";
|
|
4
4
|
import * as Owner$Reventless from "@reventlessdev/reventless-spec/src/components/Owner.res.mjs";
|
|
5
5
|
import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
|
|
6
|
-
import * as
|
|
6
|
+
import * as Decision_GWT$ReventlessGwt from "../src/Decision_GWT.res.mjs";
|
|
7
7
|
|
|
8
8
|
let name = "Shelve";
|
|
9
9
|
|
|
@@ -92,14 +92,14 @@ function decide(_state, command) {
|
|
|
92
92
|
};
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
let
|
|
95
|
+
let ShelveSliceDecision = {
|
|
96
96
|
Spec: undefined,
|
|
97
97
|
initialState: undefined,
|
|
98
98
|
evolve: evolve,
|
|
99
99
|
decide: decide
|
|
100
100
|
};
|
|
101
101
|
|
|
102
|
-
let include =
|
|
102
|
+
let include = Decision_GWT$ReventlessGwt.Make({
|
|
103
103
|
name: name,
|
|
104
104
|
consumedEventSchema: consumedEventSchema,
|
|
105
105
|
commandSchema: commandSchema,
|
|
@@ -126,7 +126,7 @@ let whenCmd = include.whenCmd;
|
|
|
126
126
|
|
|
127
127
|
let thenEvent = include.thenEvent;
|
|
128
128
|
|
|
129
|
-
let
|
|
129
|
+
let thenForbidden = include.thenForbidden;
|
|
130
130
|
|
|
131
131
|
let s1 = "s1";
|
|
132
132
|
|
|
@@ -145,19 +145,19 @@ describe("a command's rule, checked against the caller's roles", () => {
|
|
|
145
145
|
TAG: "Done",
|
|
146
146
|
shelfId: s1
|
|
147
147
|
}));
|
|
148
|
-
test("refused for a signed-in caller holding none of them", () =>
|
|
148
|
+
test("refused for a signed-in caller holding none of them", () => thenForbidden(whenCmd(asCaller(givenEvents([]), Caller.owner("c1", undefined)), {
|
|
149
149
|
TAG: "Shelve",
|
|
150
150
|
shelfId: s1
|
|
151
151
|
})));
|
|
152
|
-
test("refused for an anonymous caller", () =>
|
|
152
|
+
test("refused for an anonymous caller", () => thenForbidden(whenCmd(asCaller(givenEvents([]), Caller.anonymous), {
|
|
153
153
|
TAG: "Shelve",
|
|
154
154
|
shelfId: s1
|
|
155
155
|
})));
|
|
156
|
-
test("refused for an operator, who holds no role", () =>
|
|
156
|
+
test("refused for an operator, who holds no role", () => thenForbidden(whenCmd(asCaller(givenEvents([]), Caller.operator), {
|
|
157
157
|
TAG: "Shelve",
|
|
158
158
|
shelfId: s1
|
|
159
159
|
})));
|
|
160
|
-
test("DenyAll refuses every caller, Admin included", () =>
|
|
160
|
+
test("DenyAll refuses every caller, Admin included", () => thenForbidden(whenCmd(asCaller(givenEvents([]), Caller.inRoles(["Admin"])), {
|
|
161
161
|
TAG: "Purge",
|
|
162
162
|
shelfId: s1
|
|
163
163
|
})));
|
|
@@ -168,7 +168,7 @@ describe("a command's rule, checked against the caller's roles", () => {
|
|
|
168
168
|
TAG: "Done",
|
|
169
169
|
shelfId: s1
|
|
170
170
|
}));
|
|
171
|
-
test("the default rule refuses an anonymous caller", () =>
|
|
171
|
+
test("the default rule refuses an anonymous caller", () => thenForbidden(whenCmd(asCaller(givenEvents([]), Caller.anonymous), {
|
|
172
172
|
TAG: "Browse",
|
|
173
173
|
shelfId: s1
|
|
174
174
|
})));
|
|
@@ -247,7 +247,7 @@ function decide$1(_state, command) {
|
|
|
247
247
|
};
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
-
let Claim =
|
|
250
|
+
let Claim = Decision_GWT$ReventlessGwt.Make({
|
|
251
251
|
name: name$1,
|
|
252
252
|
consumedEventSchema: consumedEventSchema$1,
|
|
253
253
|
commandSchema: commandSchema$1,
|
|
@@ -274,11 +274,11 @@ describe("the rule, then ownership", () => {
|
|
|
274
274
|
TAG: "Done",
|
|
275
275
|
shelfId: s1
|
|
276
276
|
}));
|
|
277
|
-
Claim.test("passing the rule does not let a caller act on someone else's thing", () => Claim.
|
|
277
|
+
Claim.test("passing the rule does not let a caller act on someone else's thing", () => Claim.thenForbidden(Claim.whenCmd(Claim.asCaller(Claim.givenEvents(claimedByC1), Claim.Caller.owner("c2", ["Merchandiser"])), {
|
|
278
278
|
TAG: "Shelve",
|
|
279
279
|
shelfId: s1
|
|
280
280
|
})));
|
|
281
|
-
Claim.test("owning a thing does not pass a rule the owner fails", () => Claim.
|
|
281
|
+
Claim.test("owning a thing does not pass a rule the owner fails", () => Claim.thenForbidden(Claim.whenCmd(Claim.asCaller(Claim.givenEvents(claimedByC1), Claim.Caller.owner("c1", undefined)), {
|
|
282
282
|
TAG: "Shelve",
|
|
283
283
|
shelfId: s1
|
|
284
284
|
})));
|
|
@@ -304,7 +304,7 @@ let thenAppendsConditionedOnExactly = include.thenAppendsConditionedOnExactly;
|
|
|
304
304
|
|
|
305
305
|
export {
|
|
306
306
|
ShelveSlice,
|
|
307
|
-
|
|
307
|
+
ShelveSliceDecision,
|
|
308
308
|
Spec,
|
|
309
309
|
describe,
|
|
310
310
|
todo,
|
|
@@ -319,7 +319,7 @@ export {
|
|
|
319
319
|
thenEventWithError,
|
|
320
320
|
thenEventsWithError,
|
|
321
321
|
thenError,
|
|
322
|
-
|
|
322
|
+
thenForbidden,
|
|
323
323
|
thenAppendsConditionedOn,
|
|
324
324
|
thenAppendsConditionedOnExactly,
|
|
325
325
|
s1,
|