@reventlessdev/reventless-gwt 1.0.0-alpha.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +698 -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 +603 -0
- package/src/Behavior_GWT.res.mjs +426 -0
- package/src/Bind.res +29 -0
- package/src/Bind.res.mjs +22 -0
- package/src/BuildClassifier.res +137 -0
- package/src/BuildClassifier.res.mjs +136 -0
- package/src/Cancellation.res +31 -0
- package/src/Cancellation.res.mjs +45 -0
- package/src/ChildProcess.res +84 -0
- package/src/ChildProcess.res.mjs +92 -0
- package/src/Cli.res +671 -0
- package/src/Cli.res.mjs +754 -0
- package/src/Collector.res +201 -0
- package/src/Collector.res.mjs +215 -0
- package/src/ComponentMeta.res +104 -0
- package/src/ComponentMeta.res.mjs +120 -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 +103 -0
- package/src/DomainDeadCode.res +93 -0
- package/src/DomainDeadCode.res.mjs +69 -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 +36 -0
- package/src/Filter.res.mjs +40 -0
- package/src/Flow_GWT.res +664 -0
- package/src/Flow_GWT.res.mjs +604 -0
- package/src/FormatterHuman.res +138 -0
- package/src/FormatterHuman.res.mjs +122 -0
- package/src/FormatterJson.res +257 -0
- package/src/FormatterJson.res.mjs +320 -0
- package/src/FormatterJunit.res +93 -0
- package/src/FormatterJunit.res.mjs +90 -0
- package/src/FormatterTap.res +139 -0
- package/src/FormatterTap.res.mjs +149 -0
- package/src/FormatterVsCode.res +362 -0
- package/src/FormatterVsCode.res.mjs +564 -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 +135 -0
- package/src/LocalHost.res.mjs +127 -0
- package/src/Mapping_GWT.res +474 -0
- package/src/Mapping_GWT.res.mjs +322 -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 +161 -0
- package/src/Outcome.res.mjs +140 -0
- package/src/PackageScan.res +80 -0
- package/src/PackageScan.res.mjs +81 -0
- package/src/PlatformRunner.res +160 -0
- package/src/PlatformRunner.res.mjs +155 -0
- package/src/PlatformScan.res +138 -0
- package/src/PlatformScan.res.mjs +131 -0
- package/src/ProcessManager.res +91 -0
- package/src/ProcessManager.res.mjs +80 -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/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 +53 -0
- package/src/StubRuntime.res.mjs +54 -0
- package/src/TestFixtures.res +16 -0
- package/src/TestFixtures.res.mjs +34 -0
- package/src/Watch.res +92 -0
- package/src/Watch.res.mjs +83 -0
- package/src/WatcherProbe.res +48 -0
- package/src/WatcherProbe.res.mjs +46 -0
- package/tests/AutomationGwtTest.res +71 -0
- package/tests/AutomationGwtTest.res.mjs +189 -0
- package/tests/BuildClassifierTest.res +94 -0
- package/tests/BuildClassifierTest.res.mjs +132 -0
- package/tests/ComponentMetaTest.res +52 -0
- package/tests/ComponentMetaTest.res.mjs +57 -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 +116 -0
- package/tests/DomainDeadCodeTest.res.mjs +155 -0
- package/tests/DomainGraphTest.res +365 -0
- package/tests/DomainGraphTest.res.mjs +413 -0
- package/tests/FlowAggregateGwtTest.res +169 -0
- package/tests/FlowAggregateGwtTest.res.mjs +269 -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 +432 -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 +98 -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 +348 -0
- package/tests/MappingGwtTest.res.mjs +592 -0
- package/tests/OutboundTranslationGwtTest.res +59 -0
- package/tests/OutboundTranslationGwtTest.res.mjs +138 -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 +78 -0
- package/tests/WatchDebounceTest.res.mjs +97 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
5
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
|
+
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
7
|
+
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
8
|
+
import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
|
|
9
|
+
import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
|
|
10
|
+
import * as DcbDecode$Reventless from "@reventlessdev/reventless-spec/src/components/DcbDecode.res.mjs";
|
|
11
|
+
import * as Outcome$ReventlessGwt from "./Outcome.res.mjs";
|
|
12
|
+
import * as JestBind$ReventlessGwt from "./JestBind.res.mjs";
|
|
13
|
+
import * as Message$ReventlessCore from "@reventlessdev/reventless-core/src/Message.res.mjs";
|
|
14
|
+
import * as DcbScopeInference$Reventless from "@reventlessdev/reventless-spec/src/components/DcbScopeInference.res.mjs";
|
|
15
|
+
|
|
16
|
+
function encodeTag(t) {
|
|
17
|
+
let d = {};
|
|
18
|
+
d["key"] = t.key;
|
|
19
|
+
d["value"] = t.value;
|
|
20
|
+
return d;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function encodeQueryItem(qi) {
|
|
24
|
+
let d = {};
|
|
25
|
+
let types = qi.eventTypes;
|
|
26
|
+
if (types !== undefined) {
|
|
27
|
+
d["eventTypes"] = types.map(prim => prim);
|
|
28
|
+
}
|
|
29
|
+
let tags = qi.tags;
|
|
30
|
+
if (tags !== undefined) {
|
|
31
|
+
d["tags"] = tags.map(encodeTag);
|
|
32
|
+
}
|
|
33
|
+
return d;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function encodeQuery(q) {
|
|
37
|
+
return q.map(encodeQueryItem);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function encodeAppendCondition(c) {
|
|
41
|
+
let d = {};
|
|
42
|
+
d["query"] = c.query.map(encodeQueryItem);
|
|
43
|
+
let pos = c.after;
|
|
44
|
+
if (pos !== undefined) {
|
|
45
|
+
d["after"] = pos;
|
|
46
|
+
}
|
|
47
|
+
return d;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function Make(Spec) {
|
|
51
|
+
return Behavior => {
|
|
52
|
+
S.enableJson();
|
|
53
|
+
let test = (name, body) => JestBind$ReventlessGwt.test(Spec.name, name, body);
|
|
54
|
+
let errors = {
|
|
55
|
+
contents: []
|
|
56
|
+
};
|
|
57
|
+
let derivedCondition = {
|
|
58
|
+
contents: undefined
|
|
59
|
+
};
|
|
60
|
+
let appendConditionFailure = {
|
|
61
|
+
contents: undefined
|
|
62
|
+
};
|
|
63
|
+
let consumedEventTypes = DcbDecode$Reventless.makeDecoder(Spec.consumedEventSchema).eventTypes;
|
|
64
|
+
let scopeShape = DcbTag$Reventless.sliceShapeFromSchemas("", Spec.commandSchema, Spec.consumedEventSchema, Spec.eventSchema);
|
|
65
|
+
let set = new Set();
|
|
66
|
+
DcbTag$Reventless.extractCrossPartitionTagKeys(Spec.eventSchema).forEach(k => {
|
|
67
|
+
set.add(k);
|
|
68
|
+
});
|
|
69
|
+
DcbScopeInference$Reventless.crossPartitionForSlice(scopeShape).forEach(k => {
|
|
70
|
+
set.add(k);
|
|
71
|
+
});
|
|
72
|
+
let crossPartitionTagKeys = Array.from(set.values()).toSorted(Primitive_string.compare);
|
|
73
|
+
let tagKeysByEventType = DcbScopeInference$Reventless.infer([scopeShape]).tagKeysByEventType;
|
|
74
|
+
let emittedEventTypes = DcbDecode$Reventless.makeDecoder(Spec.eventSchema).eventTypes;
|
|
75
|
+
let consumedTagKeysByType = DcbTag$Reventless.extractTagKeysByEventType(Spec.consumedEventSchema);
|
|
76
|
+
let queryTagKeys = query => query.flatMap(qi => Stdlib_Option.mapOr(qi.tags, [], tags => tags.map(t => t.key)));
|
|
77
|
+
let selectableBy = (query, declaredKeys) => {
|
|
78
|
+
if (query.length === 0) {
|
|
79
|
+
return true;
|
|
80
|
+
} else {
|
|
81
|
+
return query.some(qi => {
|
|
82
|
+
let tags = qi.tags;
|
|
83
|
+
if (tags !== undefined && tags.length !== 0) {
|
|
84
|
+
return tags.every(t => declaredKeys.includes(t.key));
|
|
85
|
+
} else {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
let unreachableForeignReads = query => {
|
|
92
|
+
let qKeys = queryTagKeys(query);
|
|
93
|
+
return Stdlib_Array.filterMap(Object.entries(consumedTagKeysByType), param => {
|
|
94
|
+
let keys = param[1];
|
|
95
|
+
let evType = param[0];
|
|
96
|
+
let key = keys.find(k => qKeys.includes(k));
|
|
97
|
+
if (key !== undefined && !emittedEventTypes.includes(evType) && !selectableBy(query, keys)) {
|
|
98
|
+
return [
|
|
99
|
+
evType,
|
|
100
|
+
key
|
|
101
|
+
];
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
let queryTagsTotal = q => Stdlib_Array.reduce(q, 0, (acc, qi) => acc + Stdlib_Option.mapOr(qi.tags, 0, t => t.length) | 0);
|
|
106
|
+
let givenEvents = consumed => consumed;
|
|
107
|
+
let whenCmd = (history, cmd) => {
|
|
108
|
+
errors.contents = [];
|
|
109
|
+
appendConditionFailure.contents = undefined;
|
|
110
|
+
let query = DcbTag$Reventless.buildQueryFromCommand(consumedEventTypes, Spec.commandSchema, cmd, tagKeysByEventType, crossPartitionTagKeys);
|
|
111
|
+
let condition = {
|
|
112
|
+
query: query
|
|
113
|
+
};
|
|
114
|
+
derivedCondition.contents = condition;
|
|
115
|
+
if (consumedEventTypes.length !== 0 && queryTagsTotal(query) === 0) {
|
|
116
|
+
appendConditionFailure.contents = {
|
|
117
|
+
TAG: "AppendConditionMismatch",
|
|
118
|
+
expected: "a non-empty tag set derived from @s.matches(DcbTag.string) fields on the command",
|
|
119
|
+
actual: encodeAppendCondition(condition)
|
|
120
|
+
};
|
|
121
|
+
} else {
|
|
122
|
+
let match = unreachableForeignReads(query)[0];
|
|
123
|
+
if (match !== undefined) {
|
|
124
|
+
let key = match[1];
|
|
125
|
+
let evType = match[0];
|
|
126
|
+
appendConditionFailure.contents = {
|
|
127
|
+
TAG: "AppendConditionMismatch",
|
|
128
|
+
expected: `a decision-query clause that selects '` + evType + `' on tag '` + key + `' alone — this is a cross-entity read, so mark '` + key + `' @crossPartition (or model it as a tagged array) and it will fan into its own clause; otherwise the decision model can never see '` + evType + `'`,
|
|
129
|
+
actual: encodeAppendCondition(condition)
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
let state = Stdlib_Array.reduce(history, Behavior.initialState, Behavior.evolve);
|
|
134
|
+
let events = Behavior.decide(state, cmd);
|
|
135
|
+
if (events.TAG === "Ok") {
|
|
136
|
+
return events._0;
|
|
137
|
+
}
|
|
138
|
+
errors.contents = [events._0];
|
|
139
|
+
return [];
|
|
140
|
+
};
|
|
141
|
+
let encEvent = e => Message$ReventlessCore.encode(e, Spec.eventSchema);
|
|
142
|
+
let encError = err => Message$ReventlessCore.encode(err, Spec.errorSchema);
|
|
143
|
+
let checkAppendCondition = () => Stdlib_Option.map(appendConditionFailure.contents, Outcome$ReventlessGwt.fail);
|
|
144
|
+
let unexpectedError = events => {
|
|
145
|
+
let actual = Stdlib_Option.map(errors.contents[0], encError);
|
|
146
|
+
return Outcome$ReventlessGwt.fail({
|
|
147
|
+
TAG: "ErrorMismatch",
|
|
148
|
+
expected: null,
|
|
149
|
+
actual: actual,
|
|
150
|
+
actualEvents: events.map(encEvent)
|
|
151
|
+
});
|
|
152
|
+
};
|
|
153
|
+
let thenEvents = (events, expectedEvents) => {
|
|
154
|
+
let o = checkAppendCondition();
|
|
155
|
+
if (o !== undefined) {
|
|
156
|
+
return o;
|
|
157
|
+
} else if (errors.contents.length !== 0) {
|
|
158
|
+
return unexpectedError(events);
|
|
159
|
+
} else if (Primitive_object.equal(events, expectedEvents)) {
|
|
160
|
+
return Outcome$ReventlessGwt.pass;
|
|
161
|
+
} else {
|
|
162
|
+
return Outcome$ReventlessGwt.fail({
|
|
163
|
+
TAG: "EventsMismatch",
|
|
164
|
+
expected: expectedEvents.map(encEvent),
|
|
165
|
+
actual: events.map(encEvent)
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
let thenEvent = (events, expectedEvent) => thenEvents(events, [expectedEvent]);
|
|
170
|
+
let thenNoEvent = events => {
|
|
171
|
+
let o = checkAppendCondition();
|
|
172
|
+
if (o !== undefined) {
|
|
173
|
+
return o;
|
|
174
|
+
} else if (errors.contents.length !== 0) {
|
|
175
|
+
return unexpectedError(events);
|
|
176
|
+
} else if (events.length === 0) {
|
|
177
|
+
return Outcome$ReventlessGwt.pass;
|
|
178
|
+
} else {
|
|
179
|
+
return Outcome$ReventlessGwt.fail({
|
|
180
|
+
TAG: "NoEventExpected",
|
|
181
|
+
actual: events.map(encEvent)
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
let matchesError = (events, expectedEvents, expectedError) => {
|
|
186
|
+
let expectedErrorJson = Message$ReventlessCore.encode(expectedError, Spec.errorSchema);
|
|
187
|
+
let actual = errors.contents[0];
|
|
188
|
+
if (actual === undefined) {
|
|
189
|
+
return Outcome$ReventlessGwt.fail({
|
|
190
|
+
TAG: "ErrorMismatch",
|
|
191
|
+
expected: expectedErrorJson,
|
|
192
|
+
actual: undefined,
|
|
193
|
+
actualEvents: events.map(encEvent)
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
let actual$1 = Primitive_option.valFromOption(actual);
|
|
197
|
+
if (Primitive_object.notequal(actual$1, expectedError)) {
|
|
198
|
+
return Outcome$ReventlessGwt.fail({
|
|
199
|
+
TAG: "ErrorMismatch",
|
|
200
|
+
expected: expectedErrorJson,
|
|
201
|
+
actual: Message$ReventlessCore.encode(actual$1, Spec.errorSchema),
|
|
202
|
+
actualEvents: events.map(encEvent)
|
|
203
|
+
});
|
|
204
|
+
} else if (Primitive_object.equal(events, expectedEvents)) {
|
|
205
|
+
return Outcome$ReventlessGwt.pass;
|
|
206
|
+
} else {
|
|
207
|
+
return Outcome$ReventlessGwt.fail({
|
|
208
|
+
TAG: "EventsMismatch",
|
|
209
|
+
expected: expectedEvents.map(encEvent),
|
|
210
|
+
actual: events.map(encEvent)
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
let thenError = (events, expectedError) => {
|
|
215
|
+
let o = checkAppendCondition();
|
|
216
|
+
if (o !== undefined) {
|
|
217
|
+
return o;
|
|
218
|
+
} else {
|
|
219
|
+
return matchesError(events, [], expectedError);
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
let thenEventWithError = (events, expectedEvent, expectedError) => {
|
|
223
|
+
let o = checkAppendCondition();
|
|
224
|
+
if (o !== undefined) {
|
|
225
|
+
return o;
|
|
226
|
+
} else {
|
|
227
|
+
return matchesError(events, [expectedEvent], expectedError);
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
let thenEventsWithError = (events, expectedEvents, expectedError) => {
|
|
231
|
+
let o = checkAppendCondition();
|
|
232
|
+
if (o !== undefined) {
|
|
233
|
+
return o;
|
|
234
|
+
} else {
|
|
235
|
+
return matchesError(events, expectedEvents, expectedError);
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
let thenAppendsConditionedOn = (_events, expectedQuery) => {
|
|
239
|
+
let cond = derivedCondition.contents;
|
|
240
|
+
if (cond !== undefined) {
|
|
241
|
+
if (Primitive_object.equal(cond.query, expectedQuery)) {
|
|
242
|
+
return Outcome$ReventlessGwt.pass;
|
|
243
|
+
} else {
|
|
244
|
+
return Outcome$ReventlessGwt.fail({
|
|
245
|
+
TAG: "AppendConditionMismatch",
|
|
246
|
+
expected: encodeAppendCondition({
|
|
247
|
+
query: expectedQuery
|
|
248
|
+
}),
|
|
249
|
+
actual: encodeAppendCondition(cond)
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
} else {
|
|
253
|
+
return Outcome$ReventlessGwt.fail({
|
|
254
|
+
TAG: "Throw",
|
|
255
|
+
error: "thenAppendsConditionedOn: whenCmd must be called before this assertion",
|
|
256
|
+
stack: ""
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
let thenAppendsConditionedOnExactly = (_events, expectedCondition) => {
|
|
261
|
+
let cond = derivedCondition.contents;
|
|
262
|
+
if (cond !== undefined) {
|
|
263
|
+
if (Primitive_object.equal(cond, expectedCondition)) {
|
|
264
|
+
return Outcome$ReventlessGwt.pass;
|
|
265
|
+
} else {
|
|
266
|
+
return Outcome$ReventlessGwt.fail({
|
|
267
|
+
TAG: "AppendConditionMismatch",
|
|
268
|
+
expected: encodeAppendCondition(expectedCondition),
|
|
269
|
+
actual: encodeAppendCondition(cond)
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
} else {
|
|
273
|
+
return Outcome$ReventlessGwt.fail({
|
|
274
|
+
TAG: "Throw",
|
|
275
|
+
error: "thenAppendsConditionedOnExactly: whenCmd must be called before this assertion",
|
|
276
|
+
stack: ""
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
return {
|
|
281
|
+
Spec: Spec,
|
|
282
|
+
describe: JestBind$ReventlessGwt.describe,
|
|
283
|
+
todo: JestBind$ReventlessGwt.todo,
|
|
284
|
+
test: test,
|
|
285
|
+
givenEvents: givenEvents,
|
|
286
|
+
whenCmd: whenCmd,
|
|
287
|
+
thenEvent: thenEvent,
|
|
288
|
+
thenEvents: thenEvents,
|
|
289
|
+
thenNoEvent: thenNoEvent,
|
|
290
|
+
thenEventWithError: thenEventWithError,
|
|
291
|
+
thenEventsWithError: thenEventsWithError,
|
|
292
|
+
thenError: thenError,
|
|
293
|
+
thenAppendsConditionedOn: thenAppendsConditionedOn,
|
|
294
|
+
thenAppendsConditionedOnExactly: thenAppendsConditionedOnExactly
|
|
295
|
+
};
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function MakeFromAggregate(Spec) {
|
|
300
|
+
return Behavior => {
|
|
301
|
+
S.enableJson();
|
|
302
|
+
let test = (name, body) => JestBind$ReventlessGwt.test(Spec.name, name, body);
|
|
303
|
+
let errors = {
|
|
304
|
+
contents: []
|
|
305
|
+
};
|
|
306
|
+
let givenEvents = events => events;
|
|
307
|
+
let whenCmd = (history, cmd) => {
|
|
308
|
+
errors.contents = [];
|
|
309
|
+
let state = Stdlib_Array.reduce(history, Behavior.initialState, Behavior.evolve);
|
|
310
|
+
let events = Behavior.decide(state, cmd);
|
|
311
|
+
if (events.TAG === "Ok") {
|
|
312
|
+
return events._0;
|
|
313
|
+
}
|
|
314
|
+
errors.contents = [events._0];
|
|
315
|
+
return [];
|
|
316
|
+
};
|
|
317
|
+
let encEvent = e => Message$ReventlessCore.encode(e, Spec.eventSchema);
|
|
318
|
+
let encError = err => Message$ReventlessCore.encode(err, Spec.errorSchema);
|
|
319
|
+
let unexpectedError = events => {
|
|
320
|
+
let actual = Stdlib_Option.map(errors.contents[0], encError);
|
|
321
|
+
return Outcome$ReventlessGwt.fail({
|
|
322
|
+
TAG: "ErrorMismatch",
|
|
323
|
+
expected: null,
|
|
324
|
+
actual: actual,
|
|
325
|
+
actualEvents: events.map(encEvent)
|
|
326
|
+
});
|
|
327
|
+
};
|
|
328
|
+
let thenEvents = (events, expectedEvents) => {
|
|
329
|
+
if (errors.contents.length !== 0) {
|
|
330
|
+
return unexpectedError(events);
|
|
331
|
+
} else if (Primitive_object.equal(events, expectedEvents)) {
|
|
332
|
+
return Outcome$ReventlessGwt.pass;
|
|
333
|
+
} else {
|
|
334
|
+
return Outcome$ReventlessGwt.fail({
|
|
335
|
+
TAG: "EventsMismatch",
|
|
336
|
+
expected: expectedEvents.map(encEvent),
|
|
337
|
+
actual: events.map(encEvent)
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
let thenCompareEvents = (events, expectedEvents, cmp) => {
|
|
342
|
+
if (errors.contents.length !== 0) {
|
|
343
|
+
return unexpectedError(events);
|
|
344
|
+
} else if (events.length === expectedEvents.length && Stdlib_Array.zip(events, expectedEvents).every(param => cmp(param[0], param[1]))) {
|
|
345
|
+
return Outcome$ReventlessGwt.pass;
|
|
346
|
+
} else {
|
|
347
|
+
return Outcome$ReventlessGwt.fail({
|
|
348
|
+
TAG: "EventsMismatch",
|
|
349
|
+
expected: expectedEvents.map(encEvent),
|
|
350
|
+
actual: events.map(encEvent)
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
let thenEvent = (events, expectedEvent) => thenEvents(events, [expectedEvent]);
|
|
355
|
+
let thenCompareEvent = (events, expectedEvent, cmp) => thenCompareEvents(events, [expectedEvent], cmp);
|
|
356
|
+
let thenNoEvent = events => {
|
|
357
|
+
if (errors.contents.length !== 0) {
|
|
358
|
+
return unexpectedError(events);
|
|
359
|
+
} else if (events.length === 0) {
|
|
360
|
+
return Outcome$ReventlessGwt.pass;
|
|
361
|
+
} else {
|
|
362
|
+
return Outcome$ReventlessGwt.fail({
|
|
363
|
+
TAG: "NoEventExpected",
|
|
364
|
+
actual: events.map(encEvent)
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
let matchesError = (events, expectedEvents, expectedError) => {
|
|
369
|
+
let expectedErrorJson = Message$ReventlessCore.encode(expectedError, Spec.errorSchema);
|
|
370
|
+
let actual = errors.contents[0];
|
|
371
|
+
if (actual === undefined) {
|
|
372
|
+
return Outcome$ReventlessGwt.fail({
|
|
373
|
+
TAG: "ErrorMismatch",
|
|
374
|
+
expected: expectedErrorJson,
|
|
375
|
+
actual: undefined,
|
|
376
|
+
actualEvents: events.map(encEvent)
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
let actual$1 = Primitive_option.valFromOption(actual);
|
|
380
|
+
if (Primitive_object.notequal(actual$1, expectedError)) {
|
|
381
|
+
return Outcome$ReventlessGwt.fail({
|
|
382
|
+
TAG: "ErrorMismatch",
|
|
383
|
+
expected: expectedErrorJson,
|
|
384
|
+
actual: Message$ReventlessCore.encode(actual$1, Spec.errorSchema),
|
|
385
|
+
actualEvents: events.map(encEvent)
|
|
386
|
+
});
|
|
387
|
+
} else if (Primitive_object.equal(events, expectedEvents)) {
|
|
388
|
+
return Outcome$ReventlessGwt.pass;
|
|
389
|
+
} else {
|
|
390
|
+
return Outcome$ReventlessGwt.fail({
|
|
391
|
+
TAG: "EventsMismatch",
|
|
392
|
+
expected: expectedEvents.map(encEvent),
|
|
393
|
+
actual: events.map(encEvent)
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
let thenError = (events, expectedError) => matchesError(events, [], expectedError);
|
|
398
|
+
let thenEventWithError = (events, expectedEvent, expectedError) => matchesError(events, [expectedEvent], expectedError);
|
|
399
|
+
let thenEventsWithError = matchesError;
|
|
400
|
+
return {
|
|
401
|
+
Spec: Spec,
|
|
402
|
+
describe: JestBind$ReventlessGwt.describe,
|
|
403
|
+
test: test,
|
|
404
|
+
givenEvents: givenEvents,
|
|
405
|
+
whenCmd: whenCmd,
|
|
406
|
+
thenEvent: thenEvent,
|
|
407
|
+
thenCompareEvent: thenCompareEvent,
|
|
408
|
+
thenNoEvent: thenNoEvent,
|
|
409
|
+
thenEventWithError: thenEventWithError,
|
|
410
|
+
thenEvents: thenEvents,
|
|
411
|
+
thenCompareEvents: thenCompareEvents,
|
|
412
|
+
thenEventsWithError: thenEventsWithError,
|
|
413
|
+
thenError: thenError
|
|
414
|
+
};
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export {
|
|
419
|
+
encodeTag,
|
|
420
|
+
encodeQueryItem,
|
|
421
|
+
encodeQuery,
|
|
422
|
+
encodeAppendCondition,
|
|
423
|
+
Make,
|
|
424
|
+
MakeFromAggregate,
|
|
425
|
+
}
|
|
426
|
+
/* S Not a pure module */
|
package/src/Bind.res
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// CLI-mode binding for test files.
|
|
2
|
+
//
|
|
3
|
+
// Mirrors the shape of `JestBind` (`describe`, `test`, `testPromise`) but
|
|
4
|
+
// pushes directly into `Collector` instead of forwarding to Jest globals.
|
|
5
|
+
// Test files can either:
|
|
6
|
+
//
|
|
7
|
+
// 1. Rely on the DSL default (every `*_GWT.res` module uses `JestBind`
|
|
8
|
+
// internally, which routes to `Collector` when the CLI has activated it),
|
|
9
|
+
// or
|
|
10
|
+
// 2. Explicitly `open ReventlessGwt.Bind` to get these functions bound at
|
|
11
|
+
// compile time — useful for ad-hoc harnesses or tests that want to
|
|
12
|
+
// bypass the JestBind fallback entirely.
|
|
13
|
+
//
|
|
14
|
+
// Either way, the runtime contract is identical: each call pushes one entry
|
|
15
|
+
// into `Collector.entries`. The CLI drains the list after dynamic-import
|
|
16
|
+
// resolves.
|
|
17
|
+
|
|
18
|
+
let describe = (label: string, body: unit => unit) =>
|
|
19
|
+
Collector.pushDescribe(label, body)
|
|
20
|
+
|
|
21
|
+
let test = (~slice=?, name: string, body: unit => Outcome.outcome) => {
|
|
22
|
+
let location = Collector.captureLocation(1)
|
|
23
|
+
Collector.push(~slice?, ~location?, name, () => Promise.resolve(body()))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let testPromise = (~slice=?, name: string, body: unit => promise<Outcome.outcome>) => {
|
|
27
|
+
let location = Collector.captureLocation(1)
|
|
28
|
+
Collector.push(~slice?, ~location?, name, body)
|
|
29
|
+
}
|
package/src/Bind.res.mjs
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Collector$ReventlessGwt from "./Collector.res.mjs";
|
|
4
|
+
|
|
5
|
+
let describe = Collector$ReventlessGwt.pushDescribe;
|
|
6
|
+
|
|
7
|
+
function test(slice, name, body) {
|
|
8
|
+
let location = Collector$ReventlessGwt.captureLocation(1);
|
|
9
|
+
Collector$ReventlessGwt.push(slice, location, name, () => Promise.resolve(body()));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function testPromise(slice, name, body) {
|
|
13
|
+
let location = Collector$ReventlessGwt.captureLocation(1);
|
|
14
|
+
Collector$ReventlessGwt.push(slice, location, name, body);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
describe,
|
|
19
|
+
test,
|
|
20
|
+
testPromise,
|
|
21
|
+
}
|
|
22
|
+
/* No side effect */
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// Turns a ReScript v12 `rescript build -w` stdout line-stream into build status
|
|
2
|
+
// callbacks. One classifier per managed package.
|
|
3
|
+
//
|
|
4
|
+
// The watch output cycle is:
|
|
5
|
+
// Parsed N source files ← a compile pass begins
|
|
6
|
+
// Compiled N modules
|
|
7
|
+
// Finished incremental compilation ← success terminator
|
|
8
|
+
// On a type/syntax error there is NO terminator — rescript prints a
|
|
9
|
+
// "We've found a bug for you!" block and waits. So success is detected on the
|
|
10
|
+
// "Finished … compilation" line; failure is detected by an error marker and
|
|
11
|
+
// flushed after the output settles (debounced), since there is no end line.
|
|
12
|
+
//
|
|
13
|
+
// Backstop: a build that prints "Parsed …" but then emits neither a terminator
|
|
14
|
+
// nor a recognised error marker — a crashed/hung watcher, or an error in a form
|
|
15
|
+
// the markers miss — would otherwise pin the consumer (the VS Code status bar)
|
|
16
|
+
// at "building" forever. A generous wall-clock watchdog fails such a build so
|
|
17
|
+
// the UI recovers.
|
|
18
|
+
|
|
19
|
+
@val external now: unit => float = "performance.now"
|
|
20
|
+
@val external setTimeout: (unit => unit, int) => float = "setTimeout"
|
|
21
|
+
@val external clearTimeout: float => unit = "clearTimeout"
|
|
22
|
+
|
|
23
|
+
let stripAnsi: string => string = %raw(`s => s.replace(/\x1b\[[0-9;]*m/g, "")`)
|
|
24
|
+
|
|
25
|
+
type callbacks = {
|
|
26
|
+
onStart: unit => unit,
|
|
27
|
+
onOk: float => unit,
|
|
28
|
+
onFail: string => unit,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let isStartLine = (l: string) =>
|
|
32
|
+
String.startsWith(l, "Parsed ") && String.endsWith(l, "source files")
|
|
33
|
+
|
|
34
|
+
let isFinishLine = (l: string) =>
|
|
35
|
+
String.includes(l, "Finished") && String.includes(l, "compilation")
|
|
36
|
+
|
|
37
|
+
let isErrorMarker = (l: string) =>
|
|
38
|
+
String.includes(l, "We've found a bug for you!") ||
|
|
39
|
+
String.includes(l, "Syntax error") ||
|
|
40
|
+
(String.startsWith(l, "Found ") && String.includes(l, "error"))
|
|
41
|
+
|
|
42
|
+
// Returns a `feed` function: hand it each raw stdout line. `watchdogMs` bounds
|
|
43
|
+
// how long a started-but-unterminated compile may stay "building" before it is
|
|
44
|
+
// reported failed (see the backstop note above).
|
|
45
|
+
let make = (~watchdogMs: int=120_000, cb: callbacks): (string => unit) => {
|
|
46
|
+
let compiling = ref(false)
|
|
47
|
+
let hasError = ref(false)
|
|
48
|
+
let startTime = ref(0.0)
|
|
49
|
+
let buffer = ref([])
|
|
50
|
+
let timer: ref<option<float>> = ref(None)
|
|
51
|
+
let watchdog: ref<option<float>> = ref(None)
|
|
52
|
+
|
|
53
|
+
let clearTimer = () =>
|
|
54
|
+
switch timer.contents {
|
|
55
|
+
| Some(t) => {
|
|
56
|
+
clearTimeout(t)
|
|
57
|
+
timer := None
|
|
58
|
+
}
|
|
59
|
+
| None => ()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let clearWatchdog = () =>
|
|
63
|
+
switch watchdog.contents {
|
|
64
|
+
| Some(t) => {
|
|
65
|
+
clearTimeout(t)
|
|
66
|
+
watchdog := None
|
|
67
|
+
}
|
|
68
|
+
| None => ()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let reset = () => {
|
|
72
|
+
compiling := false
|
|
73
|
+
hasError := false
|
|
74
|
+
buffer := []
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// After output settles, a compile that saw an error (and thus no "Finished"
|
|
78
|
+
// terminator) is reported failed, capturing a concise message.
|
|
79
|
+
let settle = () =>
|
|
80
|
+
if compiling.contents && hasError.contents {
|
|
81
|
+
let msg = buffer.contents->Array.slice(~start=0, ~end=20)->Array.join("\n")
|
|
82
|
+
clearWatchdog()
|
|
83
|
+
cb.onFail(msg)
|
|
84
|
+
reset()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let arm = () => {
|
|
88
|
+
clearTimer()
|
|
89
|
+
timer := Some(setTimeout(settle, 400))
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Last-resort timer: fail a build still compiling after `watchdogMs` with no
|
|
93
|
+
// terminator and no recognised error, so the consumer never spins forever.
|
|
94
|
+
let armWatchdog = () => {
|
|
95
|
+
clearWatchdog()
|
|
96
|
+
watchdog :=
|
|
97
|
+
Some(
|
|
98
|
+
setTimeout(() =>
|
|
99
|
+
if compiling.contents {
|
|
100
|
+
clearTimer()
|
|
101
|
+
cb.onFail("build watchdog: no completion within " ++ Int.toString(watchdogMs / 1000) ++ "s")
|
|
102
|
+
reset()
|
|
103
|
+
}
|
|
104
|
+
, watchdogMs),
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
line => {
|
|
109
|
+
let l = stripAnsi(line)->String.trim
|
|
110
|
+
if isStartLine(l) {
|
|
111
|
+
if !compiling.contents {
|
|
112
|
+
cb.onStart()
|
|
113
|
+
}
|
|
114
|
+
compiling := true
|
|
115
|
+
hasError := false
|
|
116
|
+
buffer := []
|
|
117
|
+
startTime := now()
|
|
118
|
+
arm()
|
|
119
|
+
armWatchdog()
|
|
120
|
+
} else if isFinishLine(l) {
|
|
121
|
+
clearTimer()
|
|
122
|
+
clearWatchdog()
|
|
123
|
+
if compiling.contents {
|
|
124
|
+
cb.onOk(now() -. startTime.contents)
|
|
125
|
+
reset()
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
if String.length(l) > 0 {
|
|
129
|
+
buffer := Array.concat(buffer.contents, [l])
|
|
130
|
+
}
|
|
131
|
+
if isErrorMarker(l) {
|
|
132
|
+
hasError := true
|
|
133
|
+
}
|
|
134
|
+
arm()
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|