@reventlessdev/reventless-gwt 1.0.0-alpha.100
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 +916 -0
- package/LICENSE +202 -0
- package/bin/reventless-dev.mjs +21 -0
- package/package.json +54 -0
- package/rescript.json +36 -0
- package/src/.gwtignore +10 -0
- package/src/Automation_GWT.res +207 -0
- package/src/Automation_GWT.res.mjs +160 -0
- package/src/Behavior_GWT.res +569 -0
- package/src/Behavior_GWT.res.mjs +523 -0
- package/src/Bind.res +29 -0
- package/src/Bind.res.mjs +22 -0
- package/src/BuildClassifier.res +147 -0
- package/src/BuildClassifier.res.mjs +136 -0
- package/src/Cancellation.res +37 -0
- package/src/Cancellation.res.mjs +46 -0
- package/src/ChildProcess.res +102 -0
- package/src/ChildProcess.res.mjs +107 -0
- package/src/Cli.res +905 -0
- package/src/Cli.res.mjs +952 -0
- package/src/Collector.res +212 -0
- package/src/Collector.res.mjs +218 -0
- package/src/ComponentMeta.res +77 -0
- package/src/ComponentMeta.res.mjs +91 -0
- package/src/ComponentScan.res +103 -0
- package/src/ComponentScan.res.mjs +105 -0
- package/src/Delegate_GWT.res +303 -0
- package/src/Delegate_GWT.res.mjs +372 -0
- package/src/Diff.res +91 -0
- package/src/Diff.res.mjs +98 -0
- package/src/Discovery.res +105 -0
- package/src/Discovery.res.mjs +99 -0
- package/src/DomainDeadCode.res +98 -0
- package/src/DomainDeadCode.res.mjs +72 -0
- package/src/DomainGraph.res +0 -0
- package/src/DomainGraph.res.mjs +0 -0
- package/src/EventMapping_GWT.res +29 -0
- package/src/EventMapping_GWT.res.mjs +44 -0
- package/src/Filter.res +45 -0
- package/src/Filter.res.mjs +47 -0
- package/src/Flow_GWT.res +664 -0
- package/src/Flow_GWT.res.mjs +604 -0
- package/src/FormatterHuman.res +109 -0
- package/src/FormatterHuman.res.mjs +115 -0
- package/src/FormatterJson.res +264 -0
- package/src/FormatterJson.res.mjs +322 -0
- package/src/FormatterJunit.res +93 -0
- package/src/FormatterJunit.res.mjs +90 -0
- package/src/FormatterTap.res +100 -0
- package/src/FormatterTap.res.mjs +131 -0
- package/src/FormatterVsCode.res +352 -0
- package/src/FormatterVsCode.res.mjs +546 -0
- package/src/Hint.res +119 -0
- package/src/Hint.res.mjs +124 -0
- package/src/InboundTranslation_GWT.res +83 -0
- package/src/InboundTranslation_GWT.res.mjs +94 -0
- package/src/JestBind.res +70 -0
- package/src/JestBind.res.mjs +63 -0
- package/src/Loader.res +24 -0
- package/src/Loader.res.mjs +23 -0
- package/src/LocalHost.res +119 -0
- package/src/LocalHost.res.mjs +99 -0
- package/src/Mapping_GWT.res +474 -0
- package/src/Mapping_GWT.res.mjs +322 -0
- package/src/MismatchRender.res +72 -0
- package/src/MismatchRender.res.mjs +127 -0
- package/src/MultiSourceProjection_GWT.res +316 -0
- package/src/MultiSourceProjection_GWT.res.mjs +354 -0
- package/src/OutboundTranslation_GWT.res +238 -0
- package/src/OutboundTranslation_GWT.res.mjs +186 -0
- package/src/Outcome.res +97 -0
- package/src/Outcome.res.mjs +93 -0
- package/src/PackageScan.res +80 -0
- package/src/PackageScan.res.mjs +81 -0
- package/src/PlatformRunner.res +181 -0
- package/src/PlatformRunner.res.mjs +176 -0
- package/src/PlatformScan.res +142 -0
- package/src/PlatformScan.res.mjs +132 -0
- package/src/ProcessManager.res +135 -0
- package/src/ProcessManager.res.mjs +119 -0
- package/src/Projection_GWT.res +312 -0
- package/src/Projection_GWT.res.mjs +332 -0
- package/src/Query_GWT.res +379 -0
- package/src/Query_GWT.res.mjs +367 -0
- package/src/RenderRescript.res +125 -0
- package/src/RenderRescript.res.mjs +141 -0
- package/src/RunWorker.res +24 -0
- package/src/RunWorker.res.mjs +16 -0
- package/src/RunnerTypes.res +68 -0
- package/src/RunnerTypes.res.mjs +60 -0
- package/src/SideEffect_GWT.res +157 -0
- package/src/SideEffect_GWT.res.mjs +113 -0
- package/src/StateChange/ExternalAddCategorySlice.res +25 -0
- package/src/StateChange/ExternalAddCategorySlice.res.mjs +34 -0
- package/src/StateChange/ExternalAddCategorySlice_Behavior.res +19 -0
- package/src/StateChange/ExternalAddCategorySlice_Behavior.res.mjs +46 -0
- package/src/StateChange/WithFixtures.res +22 -0
- package/src/StateChange/WithFixtures.res.mjs +31 -0
- package/src/StateChange/WithFixtures_Behavior.res +17 -0
- package/src/StateChange/WithFixtures_Behavior.res.mjs +37 -0
- package/src/StateChange/WithManualOpen.res +23 -0
- package/src/StateChange/WithManualOpen.res.mjs +31 -0
- package/src/StateChange/WithManualOpen_Behavior.res +17 -0
- package/src/StateChange/WithManualOpen_Behavior.res.mjs +37 -0
- package/src/StubRuntime.res +54 -0
- package/src/StubRuntime.res.mjs +55 -0
- package/src/TestFixtures.res +16 -0
- package/src/TestFixtures.res.mjs +34 -0
- package/src/Watch.res +108 -0
- package/src/Watch.res.mjs +85 -0
- package/src/WatcherProbe.res +56 -0
- package/src/WatcherProbe.res.mjs +59 -0
- package/src/Worker.res +24 -0
- package/src/Worker.res.mjs +9 -0
- package/tests/AutomationGwtTest.res +71 -0
- package/tests/AutomationGwtTest.res.mjs +189 -0
- package/tests/BuildClassifierTest.res +133 -0
- package/tests/BuildClassifierTest.res.mjs +176 -0
- package/tests/CliRunEntryTest.res +57 -0
- package/tests/CliRunEntryTest.res.mjs +65 -0
- package/tests/CliWatchScopeTest.res +62 -0
- package/tests/CliWatchScopeTest.res.mjs +95 -0
- package/tests/ComponentMetaTest.res +67 -0
- package/tests/ComponentMetaTest.res.mjs +71 -0
- package/tests/DelegateGwtTest.res +487 -0
- package/tests/DelegateGwtTest.res.mjs +891 -0
- package/tests/DiscoveryTest.res +42 -0
- package/tests/DiscoveryTest.res.mjs +43 -0
- package/tests/DomainDeadCodeTest.res +119 -0
- package/tests/DomainDeadCodeTest.res.mjs +158 -0
- package/tests/DomainGraphTest.res +448 -0
- package/tests/DomainGraphTest.res.mjs +505 -0
- package/tests/FlowAggregateGwtTest.res +171 -0
- package/tests/FlowAggregateGwtTest.res.mjs +273 -0
- package/tests/FlowCrossPluginGwtTest.res +340 -0
- package/tests/FlowCrossPluginGwtTest.res.mjs +798 -0
- package/tests/FlowGwtTest.res +222 -0
- package/tests/FlowGwtTest.res.mjs +433 -0
- package/tests/FormatterGoldenTest.res +125 -0
- package/tests/FormatterGoldenTest.res.mjs +162 -0
- package/tests/FormatterVsCodeMessageTest.res +41 -0
- package/tests/FormatterVsCodeMessageTest.res.mjs +42 -0
- package/tests/InboundTranslationGwtTest.res +34 -0
- package/tests/InboundTranslationGwtTest.res.mjs +99 -0
- package/tests/LocalHostIntegration.res +70 -0
- package/tests/LocalHostIntegration.res.mjs +64 -0
- package/tests/LocalHostTest.res +71 -0
- package/tests/LocalHostTest.res.mjs +63 -0
- package/tests/MappingGwtTest.res +350 -0
- package/tests/MappingGwtTest.res.mjs +596 -0
- package/tests/OutboundTranslationGwtTest.res +59 -0
- package/tests/OutboundTranslationGwtTest.res.mjs +139 -0
- package/tests/PlatformRunnerTest.res +66 -0
- package/tests/PlatformRunnerTest.res.mjs +85 -0
- package/tests/PlatformScanTest.res +61 -0
- package/tests/PlatformScanTest.res.mjs +56 -0
- package/tests/QueryGwtTest.res +161 -0
- package/tests/QueryGwtTest.res.mjs +314 -0
- package/tests/QueryResolverGwtTest.res +71 -0
- package/tests/QueryResolverGwtTest.res.mjs +136 -0
- package/tests/RunnerUnitTest.res +219 -0
- package/tests/RunnerUnitTest.res.mjs +244 -0
- package/tests/SideEffect/FakeOrderNotification.res +23 -0
- package/tests/SideEffect/FakeOrderNotification.res.mjs +36 -0
- package/tests/SideEffect/FakeOrderNotificationGwtTest.res +35 -0
- package/tests/SideEffect/FakeOrderNotificationGwtTest.res.mjs +73 -0
- package/tests/SideEffect/MockEmail.res +18 -0
- package/tests/SideEffect/MockEmail.res.mjs +47 -0
- package/tests/StateChange/ExternalAddCategorySlice_GWT.res +27 -0
- package/tests/StateChange/ExternalAddCategorySlice_GWT.res.mjs +70 -0
- package/tests/StateChange/WithFixtures_Fixtures.res +9 -0
- package/tests/StateChange/WithFixtures_Fixtures.res.mjs +20 -0
- package/tests/StateChange/WithFixtures_GWT.res +23 -0
- package/tests/StateChange/WithFixtures_GWT.res.mjs +56 -0
- package/tests/StateChange/WithManualOpen_Fixtures.res +7 -0
- package/tests/StateChange/WithManualOpen_Fixtures.res.mjs +20 -0
- package/tests/StateChange/WithManualOpen_GWT.res +17 -0
- package/tests/StateChange/WithManualOpen_GWT.res.mjs +56 -0
- package/tests/StateChangeSliceGwtTest.res +160 -0
- package/tests/StateChangeSliceGwtTest.res.mjs +280 -0
- package/tests/StateViewSliceGwtTest.res +64 -0
- package/tests/StateViewSliceGwtTest.res.mjs +161 -0
- package/tests/WatchDebounceTest.res +106 -0
- package/tests/WatchDebounceTest.res.mjs +128 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
|
|
5
|
+
import * as Outcome$ReventlessGwt from "../src/Outcome.res.mjs";
|
|
6
|
+
import * as Behavior_GWT$ReventlessGwt from "../src/Behavior_GWT.res.mjs";
|
|
7
|
+
|
|
8
|
+
let consumedEventSchema = S.union([
|
|
9
|
+
S.literal("CategoryAdded"),
|
|
10
|
+
S.literal("CategoryArchived")
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
let commandSchema = S.schema(s => ({
|
|
14
|
+
TAG: "AddCategory",
|
|
15
|
+
categoryId: s.m(DcbTag$Reventless.string),
|
|
16
|
+
name: s.m(S.string)
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
let errorSchema = S.literal("CategoryAlreadyExists");
|
|
20
|
+
|
|
21
|
+
let eventSchema = S.schema(s => ({
|
|
22
|
+
TAG: "CategoryAdded",
|
|
23
|
+
categoryId: s.m(DcbTag$Reventless.string),
|
|
24
|
+
name: s.m(S.string)
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
function commandAuthorization(param) {
|
|
28
|
+
return "AllowAuthenticated";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let AddCategorySlice = {
|
|
32
|
+
name: "AddCategory",
|
|
33
|
+
consumedEventSchema: consumedEventSchema,
|
|
34
|
+
commandSchema: commandSchema,
|
|
35
|
+
errorSchema: errorSchema,
|
|
36
|
+
eventSchema: eventSchema,
|
|
37
|
+
commandAuthorization: commandAuthorization,
|
|
38
|
+
readConsistency: "EscalateOnRetry"
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
let initialState = {
|
|
42
|
+
exists: false,
|
|
43
|
+
archived: false
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
function evolve(state, event) {
|
|
47
|
+
if (event === "CategoryAdded") {
|
|
48
|
+
return {
|
|
49
|
+
exists: true,
|
|
50
|
+
archived: false
|
|
51
|
+
};
|
|
52
|
+
} else {
|
|
53
|
+
return {
|
|
54
|
+
exists: state.exists,
|
|
55
|
+
archived: true
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function decide(state, command) {
|
|
61
|
+
if (state.exists) {
|
|
62
|
+
return {
|
|
63
|
+
TAG: "Error",
|
|
64
|
+
_0: "CategoryAlreadyExists"
|
|
65
|
+
};
|
|
66
|
+
} else {
|
|
67
|
+
return {
|
|
68
|
+
TAG: "Ok",
|
|
69
|
+
_0: [{
|
|
70
|
+
TAG: "CategoryAdded",
|
|
71
|
+
categoryId: command.categoryId,
|
|
72
|
+
name: command.name
|
|
73
|
+
}]
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let AddCategorySliceBehavior = {
|
|
79
|
+
Spec: undefined,
|
|
80
|
+
initialState: initialState,
|
|
81
|
+
evolve: evolve,
|
|
82
|
+
decide: decide
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
let include = Behavior_GWT$ReventlessGwt.Make(AddCategorySlice)({
|
|
86
|
+
initialState: initialState,
|
|
87
|
+
evolve: evolve,
|
|
88
|
+
decide: decide
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
let describe = include.describe;
|
|
92
|
+
|
|
93
|
+
let test = include.test;
|
|
94
|
+
|
|
95
|
+
let givenEvents = include.givenEvents;
|
|
96
|
+
|
|
97
|
+
let whenCmd = include.whenCmd;
|
|
98
|
+
|
|
99
|
+
let thenEvent = include.thenEvent;
|
|
100
|
+
|
|
101
|
+
let thenError = include.thenError;
|
|
102
|
+
|
|
103
|
+
let thenAppendsConditionedOn = include.thenAppendsConditionedOn;
|
|
104
|
+
|
|
105
|
+
let thenAppendsConditionedOnExactly = include.thenAppendsConditionedOnExactly;
|
|
106
|
+
|
|
107
|
+
describe("AddCategory StateChangeSlice", () => {
|
|
108
|
+
test("on empty event log produces CategoryAdded", () => thenEvent(whenCmd(givenEvents([]), {
|
|
109
|
+
TAG: "AddCategory",
|
|
110
|
+
categoryId: "c1",
|
|
111
|
+
name: "Electronics"
|
|
112
|
+
}), {
|
|
113
|
+
TAG: "CategoryAdded",
|
|
114
|
+
categoryId: "c1",
|
|
115
|
+
name: "Electronics"
|
|
116
|
+
}));
|
|
117
|
+
test("on existing category returns CategoryAlreadyExists", () => thenError(whenCmd(givenEvents(["CategoryAdded"]), {
|
|
118
|
+
TAG: "AddCategory",
|
|
119
|
+
categoryId: "c1",
|
|
120
|
+
name: "X"
|
|
121
|
+
}), "CategoryAlreadyExists"));
|
|
122
|
+
test("append condition is single-entity query over consumed event types", () => thenAppendsConditionedOn(whenCmd(givenEvents([]), {
|
|
123
|
+
TAG: "AddCategory",
|
|
124
|
+
categoryId: "c1",
|
|
125
|
+
name: "Electronics"
|
|
126
|
+
}), [{
|
|
127
|
+
eventTypes: [
|
|
128
|
+
"CategoryAdded",
|
|
129
|
+
"CategoryArchived"
|
|
130
|
+
],
|
|
131
|
+
tags: [{
|
|
132
|
+
key: "categoryId",
|
|
133
|
+
value: "c1"
|
|
134
|
+
}]
|
|
135
|
+
}]));
|
|
136
|
+
test("exact condition matches no prior position (new entity)", () => thenAppendsConditionedOnExactly(whenCmd(givenEvents([]), {
|
|
137
|
+
TAG: "AddCategory",
|
|
138
|
+
categoryId: "c2",
|
|
139
|
+
name: "Books"
|
|
140
|
+
}), {
|
|
141
|
+
query: [{
|
|
142
|
+
eventTypes: [
|
|
143
|
+
"CategoryAdded",
|
|
144
|
+
"CategoryArchived"
|
|
145
|
+
],
|
|
146
|
+
tags: [{
|
|
147
|
+
key: "categoryId",
|
|
148
|
+
value: "c2"
|
|
149
|
+
}]
|
|
150
|
+
}]
|
|
151
|
+
}));
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
let consumedEventSchema$1 = S.literal("Noop");
|
|
155
|
+
|
|
156
|
+
let commandSchema$1 = S.schema(s => ({
|
|
157
|
+
TAG: "Do",
|
|
158
|
+
id: s.m(S.string)
|
|
159
|
+
}));
|
|
160
|
+
|
|
161
|
+
let errorSchema$1 = S.literal("Rejected");
|
|
162
|
+
|
|
163
|
+
let eventSchema$1 = S.schema(s => ({
|
|
164
|
+
TAG: "Done",
|
|
165
|
+
id: s.m(S.string)
|
|
166
|
+
}));
|
|
167
|
+
|
|
168
|
+
function commandAuthorization$1(param) {
|
|
169
|
+
return "AllowAuthenticated";
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let MissingTagSlice = {
|
|
173
|
+
name: "MissingTagSlice",
|
|
174
|
+
consumedEventSchema: consumedEventSchema$1,
|
|
175
|
+
commandSchema: commandSchema$1,
|
|
176
|
+
errorSchema: errorSchema$1,
|
|
177
|
+
eventSchema: eventSchema$1,
|
|
178
|
+
commandAuthorization: commandAuthorization$1,
|
|
179
|
+
readConsistency: "EscalateOnRetry"
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
function evolve$1(state, _event) {
|
|
183
|
+
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function decide$1(_state, cmd) {
|
|
187
|
+
return {
|
|
188
|
+
TAG: "Ok",
|
|
189
|
+
_0: [{
|
|
190
|
+
TAG: "Done",
|
|
191
|
+
id: cmd.id
|
|
192
|
+
}]
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let MissingTagBehavior = {
|
|
197
|
+
Spec: undefined,
|
|
198
|
+
initialState: undefined,
|
|
199
|
+
evolve: evolve$1,
|
|
200
|
+
decide: decide$1
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
let MissingTagGwt = Behavior_GWT$ReventlessGwt.Make(MissingTagSlice)({
|
|
204
|
+
initialState: undefined,
|
|
205
|
+
evolve: evolve$1,
|
|
206
|
+
decide: decide$1
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
MissingTagGwt.describe("MissingTag slice implicit check", () => {
|
|
210
|
+
MissingTagGwt.test("thenEvent surfaces AppendConditionMismatch when command lacks DCB tag", () => {
|
|
211
|
+
let outcome = MissingTagGwt.thenEvent(MissingTagGwt.whenCmd(MissingTagGwt.givenEvents([]), {
|
|
212
|
+
TAG: "Do",
|
|
213
|
+
id: "x1"
|
|
214
|
+
}), {
|
|
215
|
+
TAG: "Done",
|
|
216
|
+
id: "x1"
|
|
217
|
+
});
|
|
218
|
+
if (outcome.TAG === "Ok") {
|
|
219
|
+
return Outcome$ReventlessGwt.fail({
|
|
220
|
+
TAG: "Throw",
|
|
221
|
+
error: "expected AppendConditionMismatch, got pass",
|
|
222
|
+
stack: ""
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
let other = outcome._0;
|
|
226
|
+
if (other.TAG === "AppendConditionMismatch") {
|
|
227
|
+
return Outcome$ReventlessGwt.pass;
|
|
228
|
+
} else {
|
|
229
|
+
return Outcome$ReventlessGwt.fail({
|
|
230
|
+
TAG: "Throw",
|
|
231
|
+
error: "expected AppendConditionMismatch, got: " + Outcome$ReventlessGwt.kindName(other),
|
|
232
|
+
stack: ""
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
MissingTagGwt.test("thenAppendsConditionedOnExactly bypasses implicit check (still passes with derived)", () => MissingTagGwt.thenAppendsConditionedOnExactly(MissingTagGwt.whenCmd(MissingTagGwt.givenEvents([]), {
|
|
237
|
+
TAG: "Do",
|
|
238
|
+
id: "x1"
|
|
239
|
+
}), {
|
|
240
|
+
query: [{
|
|
241
|
+
eventTypes: ["Noop"],
|
|
242
|
+
tags: []
|
|
243
|
+
}]
|
|
244
|
+
}));
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
let Spec = include.Spec;
|
|
248
|
+
|
|
249
|
+
let todo = include.todo;
|
|
250
|
+
|
|
251
|
+
let thenEvents = include.thenEvents;
|
|
252
|
+
|
|
253
|
+
let thenNoEvent = include.thenNoEvent;
|
|
254
|
+
|
|
255
|
+
let thenEventWithError = include.thenEventWithError;
|
|
256
|
+
|
|
257
|
+
let thenEventsWithError = include.thenEventsWithError;
|
|
258
|
+
|
|
259
|
+
export {
|
|
260
|
+
AddCategorySlice,
|
|
261
|
+
AddCategorySliceBehavior,
|
|
262
|
+
Spec,
|
|
263
|
+
describe,
|
|
264
|
+
todo,
|
|
265
|
+
test,
|
|
266
|
+
givenEvents,
|
|
267
|
+
whenCmd,
|
|
268
|
+
thenEvent,
|
|
269
|
+
thenEvents,
|
|
270
|
+
thenNoEvent,
|
|
271
|
+
thenEventWithError,
|
|
272
|
+
thenEventsWithError,
|
|
273
|
+
thenError,
|
|
274
|
+
thenAppendsConditionedOn,
|
|
275
|
+
thenAppendsConditionedOnExactly,
|
|
276
|
+
MissingTagSlice,
|
|
277
|
+
MissingTagBehavior,
|
|
278
|
+
MissingTagGwt,
|
|
279
|
+
}
|
|
280
|
+
/* consumedEventSchema Not a pure module */
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Worked example for Projection_GWT (single-source StateViewSlice flavor).
|
|
2
|
+
// Projects a small category event stream into a `{categoryId, name, archived}`
|
|
3
|
+
// read model, exercising Set / Update actions.
|
|
4
|
+
//
|
|
5
|
+
// Plan 02 Phase 6: split-form Spec/Projection; PPX inference resolves the
|
|
6
|
+
// "StateViewSlice" filename substring → Projection DSL → emits
|
|
7
|
+
// [include Projection_GWT.Make(Spec, Projection)].
|
|
8
|
+
|
|
9
|
+
@@reventless.gwt
|
|
10
|
+
|
|
11
|
+
open Reventless.Projection
|
|
12
|
+
|
|
13
|
+
module CategoriesView = {
|
|
14
|
+
let name = "CategoriesView"
|
|
15
|
+
|
|
16
|
+
@schema
|
|
17
|
+
type state = {categoryId: string, name: string, archived: bool}
|
|
18
|
+
|
|
19
|
+
@schema
|
|
20
|
+
type consumedEvent =
|
|
21
|
+
| CategoryAdded({categoryId: string, name: string})
|
|
22
|
+
| CategoryRenamed({categoryId: string, name: string})
|
|
23
|
+
| CategoryArchived({categoryId: string})
|
|
24
|
+
|
|
25
|
+
let subIdConfig = None
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module CategoriesViewProjection = {
|
|
29
|
+
module Spec = CategoriesView
|
|
30
|
+
open CategoriesView
|
|
31
|
+
|
|
32
|
+
let project = (event: consumedEvent) =>
|
|
33
|
+
switch event {
|
|
34
|
+
| CategoryAdded({categoryId, name}) => [
|
|
35
|
+
Set(categoryId, {categoryId, name, archived: false}),
|
|
36
|
+
]
|
|
37
|
+
| CategoryRenamed({categoryId, name}) => [
|
|
38
|
+
Update(categoryId, state => {...state, name}),
|
|
39
|
+
]
|
|
40
|
+
| CategoryArchived({categoryId}) => [
|
|
41
|
+
Update(categoryId, state => {...state, archived: true}),
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe("CategoriesView StateViewSlice", () => {
|
|
47
|
+
test("projects CategoryAdded into a new row", () =>
|
|
48
|
+
givenEvents([])
|
|
49
|
+
->whenEvent(CategoryAdded({categoryId: "c1", name: "Electronics"}))
|
|
50
|
+
->thenStateWithId(
|
|
51
|
+
"c1",
|
|
52
|
+
{categoryId: "c1", name: "Electronics", archived: false},
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
test("CategoryRenamed updates the name", () =>
|
|
57
|
+
givenEvents([CategoryAdded({categoryId: "c1", name: "Electronics"})])
|
|
58
|
+
->whenEvent(CategoryRenamed({categoryId: "c1", name: "Consumer Electronics"}))
|
|
59
|
+
->thenStateWithId(
|
|
60
|
+
"c1",
|
|
61
|
+
{categoryId: "c1", name: "Consumer Electronics", archived: false},
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
})
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
import * as Projection_GWT$ReventlessGwt from "../src/Projection_GWT.res.mjs";
|
|
5
|
+
|
|
6
|
+
let stateSchema = S.schema(s => ({
|
|
7
|
+
categoryId: s.m(S.string),
|
|
8
|
+
name: s.m(S.string),
|
|
9
|
+
archived: s.m(S.bool)
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
let consumedEventSchema = S.union([
|
|
13
|
+
S.schema(s => ({
|
|
14
|
+
TAG: "CategoryAdded",
|
|
15
|
+
categoryId: s.m(S.string),
|
|
16
|
+
name: s.m(S.string)
|
|
17
|
+
})),
|
|
18
|
+
S.schema(s => ({
|
|
19
|
+
TAG: "CategoryRenamed",
|
|
20
|
+
categoryId: s.m(S.string),
|
|
21
|
+
name: s.m(S.string)
|
|
22
|
+
})),
|
|
23
|
+
S.schema(s => ({
|
|
24
|
+
TAG: "CategoryArchived",
|
|
25
|
+
categoryId: s.m(S.string)
|
|
26
|
+
}))
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
let CategoriesView = {
|
|
30
|
+
name: "CategoriesView",
|
|
31
|
+
stateSchema: stateSchema,
|
|
32
|
+
consumedEventSchema: consumedEventSchema,
|
|
33
|
+
subIdConfig: undefined,
|
|
34
|
+
authorization: "AllowAuthenticated",
|
|
35
|
+
visibility: "Public"
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
function project(event) {
|
|
39
|
+
switch (event.TAG) {
|
|
40
|
+
case "CategoryAdded" :
|
|
41
|
+
let categoryId = event.categoryId;
|
|
42
|
+
return [{
|
|
43
|
+
TAG: "Set",
|
|
44
|
+
_0: categoryId,
|
|
45
|
+
_1: {
|
|
46
|
+
categoryId: categoryId,
|
|
47
|
+
name: event.name,
|
|
48
|
+
archived: false
|
|
49
|
+
}
|
|
50
|
+
}];
|
|
51
|
+
case "CategoryRenamed" :
|
|
52
|
+
let name = event.name;
|
|
53
|
+
return [{
|
|
54
|
+
TAG: "Update",
|
|
55
|
+
_0: event.categoryId,
|
|
56
|
+
_1: state => ({
|
|
57
|
+
categoryId: state.categoryId,
|
|
58
|
+
name: name,
|
|
59
|
+
archived: state.archived
|
|
60
|
+
})
|
|
61
|
+
}];
|
|
62
|
+
case "CategoryArchived" :
|
|
63
|
+
return [{
|
|
64
|
+
TAG: "Update",
|
|
65
|
+
_0: event.categoryId,
|
|
66
|
+
_1: state => ({
|
|
67
|
+
categoryId: state.categoryId,
|
|
68
|
+
name: state.name,
|
|
69
|
+
archived: true
|
|
70
|
+
})
|
|
71
|
+
}];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let CategoriesViewProjection = {
|
|
76
|
+
Spec: undefined,
|
|
77
|
+
project: project
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
let include = Projection_GWT$ReventlessGwt.Make(CategoriesView)({
|
|
81
|
+
project: project
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
let describe = include.describe;
|
|
85
|
+
|
|
86
|
+
let test = include.test;
|
|
87
|
+
|
|
88
|
+
let givenEvents = include.givenEvents;
|
|
89
|
+
|
|
90
|
+
let whenEvent = include.whenEvent;
|
|
91
|
+
|
|
92
|
+
let thenStateWithId = include.thenStateWithId;
|
|
93
|
+
|
|
94
|
+
describe("CategoriesView StateViewSlice", () => {
|
|
95
|
+
test("projects CategoryAdded into a new row", undefined, () => thenStateWithId(whenEvent(givenEvents([]), {
|
|
96
|
+
TAG: "CategoryAdded",
|
|
97
|
+
categoryId: "c1",
|
|
98
|
+
name: "Electronics"
|
|
99
|
+
}), "c1", {
|
|
100
|
+
categoryId: "c1",
|
|
101
|
+
name: "Electronics",
|
|
102
|
+
archived: false
|
|
103
|
+
}));
|
|
104
|
+
test("CategoryRenamed updates the name", undefined, () => thenStateWithId(whenEvent(givenEvents([{
|
|
105
|
+
TAG: "CategoryAdded",
|
|
106
|
+
categoryId: "c1",
|
|
107
|
+
name: "Electronics"
|
|
108
|
+
}]), {
|
|
109
|
+
TAG: "CategoryRenamed",
|
|
110
|
+
categoryId: "c1",
|
|
111
|
+
name: "Consumer Electronics"
|
|
112
|
+
}), "c1", {
|
|
113
|
+
categoryId: "c1",
|
|
114
|
+
name: "Consumer Electronics",
|
|
115
|
+
archived: false
|
|
116
|
+
}));
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
let Spec = include.Spec;
|
|
120
|
+
|
|
121
|
+
let describeWithId = include.describeWithId;
|
|
122
|
+
|
|
123
|
+
let todo = include.todo;
|
|
124
|
+
|
|
125
|
+
let whenEvents = include.whenEvents;
|
|
126
|
+
|
|
127
|
+
let thenStates = include.thenStates;
|
|
128
|
+
|
|
129
|
+
let thenStatesWithId = include.thenStatesWithId;
|
|
130
|
+
|
|
131
|
+
let thenAllStates = include.thenAllStates;
|
|
132
|
+
|
|
133
|
+
let thenState = include.thenState;
|
|
134
|
+
|
|
135
|
+
let thenNoState = include.thenNoState;
|
|
136
|
+
|
|
137
|
+
let thenThrow = include.thenThrow;
|
|
138
|
+
|
|
139
|
+
let thenFail = include.thenFail;
|
|
140
|
+
|
|
141
|
+
export {
|
|
142
|
+
CategoriesView,
|
|
143
|
+
CategoriesViewProjection,
|
|
144
|
+
Spec,
|
|
145
|
+
describe,
|
|
146
|
+
describeWithId,
|
|
147
|
+
todo,
|
|
148
|
+
test,
|
|
149
|
+
givenEvents,
|
|
150
|
+
whenEvent,
|
|
151
|
+
whenEvents,
|
|
152
|
+
thenStates,
|
|
153
|
+
thenStatesWithId,
|
|
154
|
+
thenAllStates,
|
|
155
|
+
thenState,
|
|
156
|
+
thenStateWithId,
|
|
157
|
+
thenNoState,
|
|
158
|
+
thenThrow,
|
|
159
|
+
thenFail,
|
|
160
|
+
}
|
|
161
|
+
/* stateSchema Not a pure module */
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Unit tests for Watch's event coalescing — the part Phase 12 added so a
|
|
2
|
+
// relocation's structural `unlink` survives a debounce window that also carries
|
|
3
|
+
// the trailing `add`/`change` of the same `mv`. The debounce uses real
|
|
4
|
+
// setTimeout(120ms), so these await past the window.
|
|
5
|
+
|
|
6
|
+
open JestGlobals
|
|
7
|
+
|
|
8
|
+
@val external setTimeout: (unit => unit, int) => unit = "setTimeout"
|
|
9
|
+
let delay = (ms: int): promise<unit> =>
|
|
10
|
+
Promise.make((resolve, _reject) => setTimeout(() => resolve(), ms))
|
|
11
|
+
|
|
12
|
+
describe("Watch.isStructuralSource", () => {
|
|
13
|
+
testPromise("an unlink of a .res source is structural", async () =>
|
|
14
|
+
expect(Watch.isStructuralSource(Unlink, "/p/src/Foo/Bar.res"))->toEqual(true)
|
|
15
|
+
)
|
|
16
|
+
testPromise("a generated .res.mjs unlink is NOT structural (ends in .mjs)", async () =>
|
|
17
|
+
expect(Watch.isStructuralSource(Unlink, "/p/src/Foo/Bar.res.mjs"))->toEqual(false)
|
|
18
|
+
)
|
|
19
|
+
testPromise("an add of a .res source is not structural (incremental handles it)", async () =>
|
|
20
|
+
expect(Watch.isStructuralSource(Add, "/p/src/Foo/Bar.res"))->toEqual(false)
|
|
21
|
+
)
|
|
22
|
+
testPromise("a change of a .res source is not structural", async () =>
|
|
23
|
+
expect(Watch.isStructuralSource(Change, "/p/src/Foo/Bar.res"))->toEqual(false)
|
|
24
|
+
)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe("Watch.debounce coalescing", () => {
|
|
28
|
+
testPromise("a burst collapses to one call carrying the strongest event", async () => {
|
|
29
|
+
let calls = ref([])
|
|
30
|
+
let fire = Watch.debounce(40, (e, p) => calls := Array.concat(calls.contents, [(e, p)]))
|
|
31
|
+
// A move/ungroup: the new file is created, an edit lands, then the old file
|
|
32
|
+
// is unlinked — all inside one window. The unlink must win.
|
|
33
|
+
fire(Watch.Change, "/p/src/A.res")
|
|
34
|
+
fire(Watch.Add, "/p/src/new/A.res")
|
|
35
|
+
fire(Watch.Unlink, "/p/src/old/A.res")
|
|
36
|
+
await delay(120)
|
|
37
|
+
expect(calls.contents->Array.length)->toEqual(1)
|
|
38
|
+
let (e, p) = calls.contents->Array.getUnsafe(0)
|
|
39
|
+
expect(e)->toEqual(Watch.Unlink)
|
|
40
|
+
expect(p)->toEqual("/p/src/old/A.res")
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
testPromise("a later weaker event does not mask an earlier unlink", async () => {
|
|
44
|
+
let calls = ref([])
|
|
45
|
+
let fire = Watch.debounce(40, (e, _p) => calls := Array.concat(calls.contents, [e]))
|
|
46
|
+
fire(Watch.Unlink, "/p/src/old/A.res")
|
|
47
|
+
fire(Watch.Change, "/p/src/A.res")
|
|
48
|
+
await delay(120)
|
|
49
|
+
expect(calls.contents)->toEqual([Watch.Unlink])
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
testPromise("interleaved .res.mjs unlinks never surface; each distinct .res source does", async () => {
|
|
53
|
+
// The real directory-mv burst chokidar emits: source AND generated files
|
|
54
|
+
// unlink interleaved. Only `.res` sources are emitted (never a `.res.mjs`),
|
|
55
|
+
// and every *distinct* source is emitted — two moved modules must both drive
|
|
56
|
+
// a rebuild, not collapse to one representative.
|
|
57
|
+
let calls = ref([])
|
|
58
|
+
let fire = Watch.debounce(40, (e, p) => calls := Array.concat(calls.contents, [(e, p)]))
|
|
59
|
+
fire(Watch.Unlink, "/p/src/old/A.res")
|
|
60
|
+
fire(Watch.Unlink, "/p/src/old/A.res.mjs")
|
|
61
|
+
fire(Watch.Unlink, "/p/src/old/A_Behavior.res")
|
|
62
|
+
fire(Watch.Unlink, "/p/src/old/A_Behavior.res.mjs")
|
|
63
|
+
fire(Watch.Add, "/p/src/new/A.res")
|
|
64
|
+
fire(Watch.Add, "/p/src/new/A.res.mjs")
|
|
65
|
+
await delay(120)
|
|
66
|
+
let paths = calls.contents->Array.map(((_, p)) => p)
|
|
67
|
+
expect(calls.contents->Array.every(((e, p)) => Watch.isStructuralSource(e, p)))->toEqual(true)
|
|
68
|
+
expect(paths->Array.includes("/p/src/old/A.res"))->toEqual(true)
|
|
69
|
+
expect(paths->Array.includes("/p/src/old/A_Behavior.res"))->toEqual(true)
|
|
70
|
+
expect(paths->Array.some(p => p->String.endsWith(".mjs")))->toEqual(false)
|
|
71
|
+
expect(calls.contents->Array.length)->toEqual(2)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
testPromise("a burst spanning two packages emits an unlink per package (A4.1)", async () => {
|
|
75
|
+
// Regression: the old single-`bestPath` debounce clean-rebuilt only one
|
|
76
|
+
// package on a multi-package burst, stranding the other with stale .res.mjs.
|
|
77
|
+
let calls = ref([])
|
|
78
|
+
let fire = Watch.debounce(40, (e, p) => calls := Array.concat(calls.contents, [(e, p)]))
|
|
79
|
+
fire(Watch.Unlink, "/pkgA/src/Foo.res")
|
|
80
|
+
fire(Watch.Unlink, "/pkgB/src/Bar.res")
|
|
81
|
+
await delay(120)
|
|
82
|
+
let paths = calls.contents->Array.map(((_, p)) => p)
|
|
83
|
+
expect(paths->Array.includes("/pkgA/src/Foo.res"))->toEqual(true)
|
|
84
|
+
expect(paths->Array.includes("/pkgB/src/Bar.res"))->toEqual(true)
|
|
85
|
+
expect(calls.contents->Array.length)->toEqual(2)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
testPromise("a structural signal suppresses a co-occurring plain change", async () => {
|
|
89
|
+
// A structural rebuild does its own re-run, so a plain edit in the same
|
|
90
|
+
// window must not also emit a separate Change (which would double-run).
|
|
91
|
+
let calls = ref([])
|
|
92
|
+
let fire = Watch.debounce(40, (e, _p) => calls := Array.concat(calls.contents, [e]))
|
|
93
|
+
fire(Watch.Change, "/p/src/Edited.res")
|
|
94
|
+
fire(Watch.Unlink, "/p/src/old/Moved.res")
|
|
95
|
+
await delay(120)
|
|
96
|
+
expect(calls.contents)->toEqual([Watch.Unlink])
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
testPromise("a lone change stays a change (incremental re-run)", async () => {
|
|
100
|
+
let calls = ref([])
|
|
101
|
+
let fire = Watch.debounce(40, (e, _p) => calls := Array.concat(calls.contents, [e]))
|
|
102
|
+
fire(Watch.Change, "/p/src/A.res")
|
|
103
|
+
await delay(120)
|
|
104
|
+
expect(calls.contents)->toEqual([Watch.Change])
|
|
105
|
+
})
|
|
106
|
+
})
|