@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,125 @@
|
|
|
1
|
+
// Golden-output tests pinning the per-mismatch rendering of the three
|
|
2
|
+
// string-emitting formatters:
|
|
3
|
+
// - `Outcome.format` (used by the JUnit `<failure>` body)
|
|
4
|
+
// - `FormatterHuman.renderMismatch` (terminal output)
|
|
5
|
+
// - `FormatterTap.renderMismatchYaml` (TAP YAML diagnostic block)
|
|
6
|
+
//
|
|
7
|
+
// These three functions are exactly what the deferred `Outcome.mismatch`
|
|
8
|
+
// normalization (plan C4) will unify. Pinning their current output byte-for-byte
|
|
9
|
+
// lets that refactor prove it preserved Human/TAP rendering — and makes the
|
|
10
|
+
// deliberate JUnit change (it currently renders via raw `Outcome.format`, unlike
|
|
11
|
+
// the RenderRescript-based Human/TAP) explicit and reviewable rather than silent.
|
|
12
|
+
//
|
|
13
|
+
// The fixtures cover all 10 mismatch kinds — the full structural variety the
|
|
14
|
+
// normalization must preserve: array expected/actual (Events/QueryRows/
|
|
15
|
+
// PublishedActions), the Error()/Ok() wrapping with a `None` actual
|
|
16
|
+
// (ErrorMismatch), the `key` extra + option rendering (StateMismatch), a
|
|
17
|
+
// single-sided actual (NoEventExpected), plain-string (non-JSON) sides
|
|
18
|
+
// (TranslateError), the `(id, value)` pair rendering (TodoMismatch), a nested
|
|
19
|
+
// JSON value (AppendConditionMismatch), and the `Throw` stack special case.
|
|
20
|
+
|
|
21
|
+
open JestGlobals
|
|
22
|
+
|
|
23
|
+
let evA = JSON.parseOrThrow(`{"TAG":"ProductAdded","_0":{"productId":"p1","name":"Widget"}}`)
|
|
24
|
+
let evB = JSON.parseOrThrow(`{"TAG":"ProductRenamed","_0":{"productId":"p1","name":"Gadget"}}`)
|
|
25
|
+
let stateActive = JSON.parseOrThrow(`{"status":"active"}`)
|
|
26
|
+
let stateArchived = JSON.parseOrThrow(`{"status":"archived"}`)
|
|
27
|
+
let appendExpected = JSON.parseOrThrow(`{"query":[]}`)
|
|
28
|
+
let appendActual = JSON.parseOrThrow(`{"query":[{"eventTypes":["ProductAdded"]}]}`)
|
|
29
|
+
|
|
30
|
+
type golden = {
|
|
31
|
+
name: string,
|
|
32
|
+
mismatch: Outcome.mismatch,
|
|
33
|
+
format: string,
|
|
34
|
+
human: string,
|
|
35
|
+
tap: string,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let goldens: array<golden> = [
|
|
39
|
+
{
|
|
40
|
+
name: "EventsMismatch",
|
|
41
|
+
mismatch: EventsMismatch({expected: [evA], actual: [evB]}),
|
|
42
|
+
format: "EventsMismatch:\n expected: [{\n \"TAG\": \"ProductAdded\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Widget\"\n }\n}]\n actual: [{\n \"TAG\": \"ProductRenamed\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Gadget\"\n }\n}]",
|
|
43
|
+
human: " expected: [ProductAdded({productId: \"p1\", name: \"Widget\"})]\n actual: [ProductRenamed({productId: \"p1\", name: \"Gadget\"})]",
|
|
44
|
+
tap: " kind: \"EventsMismatch\"\n expected: \"[ProductAdded({productId: \\\"p1\\\", name: \\\"Widget\\\"})]\"\n actual: \"[ProductRenamed({productId: \\\"p1\\\", name: \\\"Gadget\\\"})]\"",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "ErrorMismatch",
|
|
48
|
+
mismatch: ErrorMismatch({
|
|
49
|
+
expected: JSON.String("CategoryAlreadyExists"),
|
|
50
|
+
actual: None,
|
|
51
|
+
actualEvents: [evA],
|
|
52
|
+
}),
|
|
53
|
+
format: "ErrorMismatch:\n expected error: \"CategoryAlreadyExists\"\n actual error: (none)\n actual events: [{\n \"TAG\": \"ProductAdded\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Widget\"\n }\n}]",
|
|
54
|
+
human: " expected: Error(\"CategoryAlreadyExists\")\n actual: Ok([ProductAdded({productId: \"p1\", name: \"Widget\"})])",
|
|
55
|
+
tap: " kind: \"ErrorMismatch\"\n expected: \"Error(\\\"CategoryAlreadyExists\\\")\"\n actual: \"Ok([ProductAdded({productId: \\\"p1\\\", name: \\\"Widget\\\"})])\"",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: "StateMismatch",
|
|
59
|
+
mismatch: StateMismatch({key: "p1", expected: Some(stateActive), actual: Some(stateArchived)}),
|
|
60
|
+
format: "StateMismatch (key: p1):\n expected: {\n \"status\": \"active\"\n}\n actual: {\n \"status\": \"archived\"\n}",
|
|
61
|
+
human: " key: \"p1\"\n expected: {status: \"active\"}\n actual: {status: \"archived\"}",
|
|
62
|
+
tap: " kind: \"StateMismatch\"\n key: \"p1\"\n expected: \"{status: \\\"active\\\"}\"\n actual: \"{status: \\\"archived\\\"}\"",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "NoEventExpected",
|
|
66
|
+
mismatch: NoEventExpected({actual: [evA]}),
|
|
67
|
+
format: "NoEventExpected: expected no events, got:\n [{\n \"TAG\": \"ProductAdded\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Widget\"\n }\n}]",
|
|
68
|
+
human: " expected no events\n actual: [ProductAdded({productId: \"p1\", name: \"Widget\"})]",
|
|
69
|
+
tap: " kind: \"NoEventExpected\"\n actual: \"[ProductAdded({productId: \\\"p1\\\", name: \\\"Widget\\\"})]\"",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "TranslateError",
|
|
73
|
+
mismatch: TranslateError({expected: "boom", actual: Some("kaboom")}),
|
|
74
|
+
format: "TranslateError:\n expected: boom\n actual: kaboom",
|
|
75
|
+
human: " expected: boom\n actual: kaboom",
|
|
76
|
+
tap: " kind: \"TranslateError\"\n expected: \"boom\"\n actual: \"kaboom\"",
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: "Throw",
|
|
80
|
+
mismatch: Throw({error: "unexpected", stack: "at foo"}),
|
|
81
|
+
format: "Throw: unexpected\nat foo",
|
|
82
|
+
human: " error: unexpected\nat foo",
|
|
83
|
+
tap: " kind: \"Throw\"\n error: \"unexpected\"\n stack: \"at foo\"",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: "TodoMismatch",
|
|
87
|
+
mismatch: TodoMismatch({expected: [("case-1", evA)], actual: [("case-2", evB)]}),
|
|
88
|
+
format: "TodoMismatch:\n expected: [(case-1, {\n \"TAG\": \"ProductAdded\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Widget\"\n }\n})]\n actual: [(case-2, {\n \"TAG\": \"ProductRenamed\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Gadget\"\n }\n})]",
|
|
89
|
+
human: " expected: [(case-1, ProductAdded({productId: \"p1\", name: \"Widget\"}))]\n actual: [(case-2, ProductRenamed({productId: \"p1\", name: \"Gadget\"}))]",
|
|
90
|
+
tap: " kind: \"TodoMismatch\"\n expected: \"[(case-1, ProductAdded({productId: \\\"p1\\\", name: \\\"Widget\\\"}))]\"\n actual: \"[(case-2, ProductRenamed({productId: \\\"p1\\\", name: \\\"Gadget\\\"}))]\"",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "AppendConditionMismatch",
|
|
94
|
+
mismatch: AppendConditionMismatch({expected: appendExpected, actual: appendActual}),
|
|
95
|
+
format: "AppendConditionMismatch:\n expected: {\n \"query\": []\n}\n actual: {\n \"query\": [\n {\n \"eventTypes\": [\n \"ProductAdded\"\n ]\n }\n ]\n}",
|
|
96
|
+
human: " expected: {query: []}\n actual: {query: [{eventTypes: [\"ProductAdded\"]}]}",
|
|
97
|
+
tap: " kind: \"AppendConditionMismatch\"\n expected: \"{query: []}\"\n actual: \"{query: [{eventTypes: [\\\"ProductAdded\\\"]}]}\"",
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: "QueryRowsMismatch",
|
|
101
|
+
mismatch: QueryRowsMismatch({expected: [evA], actual: [evB]}),
|
|
102
|
+
format: "QueryRowsMismatch:\n expected: [{\n \"TAG\": \"ProductAdded\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Widget\"\n }\n}]\n actual: [{\n \"TAG\": \"ProductRenamed\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Gadget\"\n }\n}]",
|
|
103
|
+
human: " expected: [ProductAdded({productId: \"p1\", name: \"Widget\"})]\n actual: [ProductRenamed({productId: \"p1\", name: \"Gadget\"})]",
|
|
104
|
+
tap: " kind: \"QueryRowsMismatch\"\n expected: \"[ProductAdded({productId: \\\"p1\\\", name: \\\"Widget\\\"})]\"\n actual: \"[ProductRenamed({productId: \\\"p1\\\", name: \\\"Gadget\\\"})]\"",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: "PublishedActionsMismatch",
|
|
108
|
+
mismatch: PublishedActionsMismatch({expected: [evA], actual: [evB]}),
|
|
109
|
+
format: "PublishedActionsMismatch:\n expected: [{\n \"TAG\": \"ProductAdded\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Widget\"\n }\n}]\n actual: [{\n \"TAG\": \"ProductRenamed\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Gadget\"\n }\n}]",
|
|
110
|
+
human: " expected: [ProductAdded({productId: \"p1\", name: \"Widget\"})]\n actual: [ProductRenamed({productId: \"p1\", name: \"Gadget\"})]",
|
|
111
|
+
tap: " kind: \"PublishedActionsMismatch\"\n expected: \"[ProductAdded({productId: \\\"p1\\\", name: \\\"Widget\\\"})]\"\n actual: \"[ProductRenamed({productId: \\\"p1\\\", name: \\\"Gadget\\\"})]\"",
|
|
112
|
+
},
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
describe("Formatter golden outputs (per-mismatch rendering)", () => {
|
|
116
|
+
goldens->Array.forEach(g => {
|
|
117
|
+
testSync(`${g.name} — Outcome.format`, () => expect(Outcome.format(g.mismatch))->toEqual(g.format))
|
|
118
|
+
testSync(`${g.name} — FormatterHuman.renderMismatch`, () =>
|
|
119
|
+
expect(FormatterHuman.renderMismatch(g.mismatch))->toEqual(g.human)
|
|
120
|
+
)
|
|
121
|
+
testSync(`${g.name} — FormatterTap.renderMismatchYaml`, () =>
|
|
122
|
+
expect(FormatterTap.renderMismatchYaml(g.mismatch))->toEqual(g.tap)
|
|
123
|
+
)
|
|
124
|
+
})
|
|
125
|
+
})
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Outcome$ReventlessGwt from "../src/Outcome.res.mjs";
|
|
4
|
+
import * as FormatterTap$ReventlessGwt from "../src/FormatterTap.res.mjs";
|
|
5
|
+
import * as FormatterHuman$ReventlessGwt from "../src/FormatterHuman.res.mjs";
|
|
6
|
+
|
|
7
|
+
let evA = JSON.parse(`{"TAG":"ProductAdded","_0":{"productId":"p1","name":"Widget"}}`);
|
|
8
|
+
|
|
9
|
+
let evB = JSON.parse(`{"TAG":"ProductRenamed","_0":{"productId":"p1","name":"Gadget"}}`);
|
|
10
|
+
|
|
11
|
+
let stateActive = JSON.parse(`{"status":"active"}`);
|
|
12
|
+
|
|
13
|
+
let stateArchived = JSON.parse(`{"status":"archived"}`);
|
|
14
|
+
|
|
15
|
+
let appendExpected = JSON.parse(`{"query":[]}`);
|
|
16
|
+
|
|
17
|
+
let appendActual = JSON.parse(`{"query":[{"eventTypes":["ProductAdded"]}]}`);
|
|
18
|
+
|
|
19
|
+
let goldens = [
|
|
20
|
+
{
|
|
21
|
+
name: "EventsMismatch",
|
|
22
|
+
mismatch: {
|
|
23
|
+
TAG: "EventsMismatch",
|
|
24
|
+
expected: [evA],
|
|
25
|
+
actual: [evB]
|
|
26
|
+
},
|
|
27
|
+
format: "EventsMismatch:\n expected: [{\n \"TAG\": \"ProductAdded\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Widget\"\n }\n}]\n actual: [{\n \"TAG\": \"ProductRenamed\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Gadget\"\n }\n}]",
|
|
28
|
+
human: " expected: [ProductAdded({productId: \"p1\", name: \"Widget\"})]\n actual: [ProductRenamed({productId: \"p1\", name: \"Gadget\"})]",
|
|
29
|
+
tap: " kind: \"EventsMismatch\"\n expected: \"[ProductAdded({productId: \\\"p1\\\", name: \\\"Widget\\\"})]\"\n actual: \"[ProductRenamed({productId: \\\"p1\\\", name: \\\"Gadget\\\"})]\""
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: "ErrorMismatch",
|
|
33
|
+
mismatch: {
|
|
34
|
+
TAG: "ErrorMismatch",
|
|
35
|
+
expected: "CategoryAlreadyExists",
|
|
36
|
+
actual: undefined,
|
|
37
|
+
actualEvents: [evA]
|
|
38
|
+
},
|
|
39
|
+
format: "ErrorMismatch:\n expected error: \"CategoryAlreadyExists\"\n actual error: (none)\n actual events: [{\n \"TAG\": \"ProductAdded\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Widget\"\n }\n}]",
|
|
40
|
+
human: " expected: Error(\"CategoryAlreadyExists\")\n actual: Ok([ProductAdded({productId: \"p1\", name: \"Widget\"})])",
|
|
41
|
+
tap: " kind: \"ErrorMismatch\"\n expected: \"Error(\\\"CategoryAlreadyExists\\\")\"\n actual: \"Ok([ProductAdded({productId: \\\"p1\\\", name: \\\"Widget\\\"})])\""
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "StateMismatch",
|
|
45
|
+
mismatch: {
|
|
46
|
+
TAG: "StateMismatch",
|
|
47
|
+
key: "p1",
|
|
48
|
+
expected: stateActive,
|
|
49
|
+
actual: stateArchived
|
|
50
|
+
},
|
|
51
|
+
format: "StateMismatch (key: p1):\n expected: {\n \"status\": \"active\"\n}\n actual: {\n \"status\": \"archived\"\n}",
|
|
52
|
+
human: " key: \"p1\"\n expected: {status: \"active\"}\n actual: {status: \"archived\"}",
|
|
53
|
+
tap: " kind: \"StateMismatch\"\n key: \"p1\"\n expected: \"{status: \\\"active\\\"}\"\n actual: \"{status: \\\"archived\\\"}\""
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: "NoEventExpected",
|
|
57
|
+
mismatch: {
|
|
58
|
+
TAG: "NoEventExpected",
|
|
59
|
+
actual: [evA]
|
|
60
|
+
},
|
|
61
|
+
format: "NoEventExpected: expected no events, got:\n [{\n \"TAG\": \"ProductAdded\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Widget\"\n }\n}]",
|
|
62
|
+
human: " expected no events\n actual: [ProductAdded({productId: \"p1\", name: \"Widget\"})]",
|
|
63
|
+
tap: " kind: \"NoEventExpected\"\n actual: \"[ProductAdded({productId: \\\"p1\\\", name: \\\"Widget\\\"})]\""
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: "TranslateError",
|
|
67
|
+
mismatch: {
|
|
68
|
+
TAG: "TranslateError",
|
|
69
|
+
expected: "boom",
|
|
70
|
+
actual: "kaboom"
|
|
71
|
+
},
|
|
72
|
+
format: "TranslateError:\n expected: boom\n actual: kaboom",
|
|
73
|
+
human: " expected: boom\n actual: kaboom",
|
|
74
|
+
tap: " kind: \"TranslateError\"\n expected: \"boom\"\n actual: \"kaboom\""
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "Throw",
|
|
78
|
+
mismatch: {
|
|
79
|
+
TAG: "Throw",
|
|
80
|
+
error: "unexpected",
|
|
81
|
+
stack: "at foo"
|
|
82
|
+
},
|
|
83
|
+
format: "Throw: unexpected\nat foo",
|
|
84
|
+
human: " error: unexpected\nat foo",
|
|
85
|
+
tap: " kind: \"Throw\"\n error: \"unexpected\"\n stack: \"at foo\""
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: "TodoMismatch",
|
|
89
|
+
mismatch: {
|
|
90
|
+
TAG: "TodoMismatch",
|
|
91
|
+
expected: [[
|
|
92
|
+
"case-1",
|
|
93
|
+
evA
|
|
94
|
+
]],
|
|
95
|
+
actual: [[
|
|
96
|
+
"case-2",
|
|
97
|
+
evB
|
|
98
|
+
]]
|
|
99
|
+
},
|
|
100
|
+
format: "TodoMismatch:\n expected: [(case-1, {\n \"TAG\": \"ProductAdded\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Widget\"\n }\n})]\n actual: [(case-2, {\n \"TAG\": \"ProductRenamed\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Gadget\"\n }\n})]",
|
|
101
|
+
human: " expected: [(case-1, ProductAdded({productId: \"p1\", name: \"Widget\"}))]\n actual: [(case-2, ProductRenamed({productId: \"p1\", name: \"Gadget\"}))]",
|
|
102
|
+
tap: " kind: \"TodoMismatch\"\n expected: \"[(case-1, ProductAdded({productId: \\\"p1\\\", name: \\\"Widget\\\"}))]\"\n actual: \"[(case-2, ProductRenamed({productId: \\\"p1\\\", name: \\\"Gadget\\\"}))]\""
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: "AppendConditionMismatch",
|
|
106
|
+
mismatch: {
|
|
107
|
+
TAG: "AppendConditionMismatch",
|
|
108
|
+
expected: appendExpected,
|
|
109
|
+
actual: appendActual
|
|
110
|
+
},
|
|
111
|
+
format: "AppendConditionMismatch:\n expected: {\n \"query\": []\n}\n actual: {\n \"query\": [\n {\n \"eventTypes\": [\n \"ProductAdded\"\n ]\n }\n ]\n}",
|
|
112
|
+
human: " expected: {query: []}\n actual: {query: [{eventTypes: [\"ProductAdded\"]}]}",
|
|
113
|
+
tap: " kind: \"AppendConditionMismatch\"\n expected: \"{query: []}\"\n actual: \"{query: [{eventTypes: [\\\"ProductAdded\\\"]}]}\""
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: "QueryRowsMismatch",
|
|
117
|
+
mismatch: {
|
|
118
|
+
TAG: "QueryRowsMismatch",
|
|
119
|
+
expected: [evA],
|
|
120
|
+
actual: [evB]
|
|
121
|
+
},
|
|
122
|
+
format: "QueryRowsMismatch:\n expected: [{\n \"TAG\": \"ProductAdded\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Widget\"\n }\n}]\n actual: [{\n \"TAG\": \"ProductRenamed\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Gadget\"\n }\n}]",
|
|
123
|
+
human: " expected: [ProductAdded({productId: \"p1\", name: \"Widget\"})]\n actual: [ProductRenamed({productId: \"p1\", name: \"Gadget\"})]",
|
|
124
|
+
tap: " kind: \"QueryRowsMismatch\"\n expected: \"[ProductAdded({productId: \\\"p1\\\", name: \\\"Widget\\\"})]\"\n actual: \"[ProductRenamed({productId: \\\"p1\\\", name: \\\"Gadget\\\"})]\""
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: "PublishedActionsMismatch",
|
|
128
|
+
mismatch: {
|
|
129
|
+
TAG: "PublishedActionsMismatch",
|
|
130
|
+
expected: [evA],
|
|
131
|
+
actual: [evB]
|
|
132
|
+
},
|
|
133
|
+
format: "PublishedActionsMismatch:\n expected: [{\n \"TAG\": \"ProductAdded\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Widget\"\n }\n}]\n actual: [{\n \"TAG\": \"ProductRenamed\",\n \"_0\": {\n \"productId\": \"p1\",\n \"name\": \"Gadget\"\n }\n}]",
|
|
134
|
+
human: " expected: [ProductAdded({productId: \"p1\", name: \"Widget\"})]\n actual: [ProductRenamed({productId: \"p1\", name: \"Gadget\"})]",
|
|
135
|
+
tap: " kind: \"PublishedActionsMismatch\"\n expected: \"[ProductAdded({productId: \\\"p1\\\", name: \\\"Widget\\\"})]\"\n actual: \"[ProductRenamed({productId: \\\"p1\\\", name: \\\"Gadget\\\"})]\""
|
|
136
|
+
}
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
globalThis.describe("Formatter golden outputs (per-mismatch rendering)", () => {
|
|
140
|
+
goldens.forEach(g => {
|
|
141
|
+
globalThis.test(g.name + ` — Outcome.format`, () => {
|
|
142
|
+
globalThis.expect(Outcome$ReventlessGwt.format(g.mismatch)).toEqual(g.format);
|
|
143
|
+
});
|
|
144
|
+
globalThis.test(g.name + ` — FormatterHuman.renderMismatch`, () => {
|
|
145
|
+
globalThis.expect(FormatterHuman$ReventlessGwt.renderMismatch(g.mismatch)).toEqual(g.human);
|
|
146
|
+
});
|
|
147
|
+
globalThis.test(g.name + ` — FormatterTap.renderMismatchYaml`, () => {
|
|
148
|
+
globalThis.expect(FormatterTap$ReventlessGwt.renderMismatchYaml(g.mismatch)).toEqual(g.tap);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
export {
|
|
154
|
+
evA,
|
|
155
|
+
evB,
|
|
156
|
+
stateActive,
|
|
157
|
+
stateArchived,
|
|
158
|
+
appendExpected,
|
|
159
|
+
appendActual,
|
|
160
|
+
goldens,
|
|
161
|
+
}
|
|
162
|
+
/* evA Not a pure module */
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Unit test for FormatterVsCode.messagePayload — the failure payload the
|
|
2
|
+
// VS Code extension renders. Asserts the `kind` discriminator (the mismatch
|
|
3
|
+
// family) is present so a client can gate the apply-expected quick-fix.
|
|
4
|
+
|
|
5
|
+
open JestGlobals
|
|
6
|
+
|
|
7
|
+
let failingResult = (~mismatch): RunnerTypes.testResult => {
|
|
8
|
+
id: "t1",
|
|
9
|
+
name: "rejects when category already exists",
|
|
10
|
+
describePath: [],
|
|
11
|
+
status: RunnerTypes.Fail,
|
|
12
|
+
durationMs: 1.0,
|
|
13
|
+
location: None,
|
|
14
|
+
slice: None,
|
|
15
|
+
mismatch,
|
|
16
|
+
skipReason: None,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe("FormatterVsCode.messagePayload", () => {
|
|
20
|
+
testPromise("includes the mismatch kind for an EventsMismatch", async () => {
|
|
21
|
+
let m = FormatterVsCode.messagePayload(
|
|
22
|
+
failingResult(~mismatch=Some(Outcome.EventsMismatch({expected: [], actual: []}))),
|
|
23
|
+
)
|
|
24
|
+
expect(m.kind)->toEqual(Some("EventsMismatch"))
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
testPromise("includes the mismatch kind for an ErrorMismatch", async () => {
|
|
28
|
+
let m = FormatterVsCode.messagePayload(
|
|
29
|
+
failingResult(
|
|
30
|
+
~mismatch=Some(
|
|
31
|
+
Outcome.ErrorMismatch({
|
|
32
|
+
expected: JSON.String("CategoryAlreadyExists"),
|
|
33
|
+
actual: None,
|
|
34
|
+
actualEvents: [],
|
|
35
|
+
}),
|
|
36
|
+
),
|
|
37
|
+
),
|
|
38
|
+
)
|
|
39
|
+
expect(m.kind)->toEqual(Some("ErrorMismatch"))
|
|
40
|
+
})
|
|
41
|
+
})
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as FormatterVsCode$ReventlessGwt from "../src/FormatterVsCode.res.mjs";
|
|
4
|
+
|
|
5
|
+
function failingResult(mismatch) {
|
|
6
|
+
return {
|
|
7
|
+
id: "t1",
|
|
8
|
+
name: "rejects when category already exists",
|
|
9
|
+
describePath: [],
|
|
10
|
+
status: "Fail",
|
|
11
|
+
durationMs: 1.0,
|
|
12
|
+
location: undefined,
|
|
13
|
+
slice: undefined,
|
|
14
|
+
mismatch: mismatch,
|
|
15
|
+
skipReason: undefined
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
globalThis.describe("FormatterVsCode.messagePayload", () => {
|
|
20
|
+
globalThis.test("includes the mismatch kind for an EventsMismatch", async () => {
|
|
21
|
+
let m = FormatterVsCode$ReventlessGwt.messagePayload(failingResult({
|
|
22
|
+
TAG: "EventsMismatch",
|
|
23
|
+
expected: [],
|
|
24
|
+
actual: []
|
|
25
|
+
}));
|
|
26
|
+
globalThis.expect(m.kind).toEqual("EventsMismatch");
|
|
27
|
+
});
|
|
28
|
+
globalThis.test("includes the mismatch kind for an ErrorMismatch", async () => {
|
|
29
|
+
let m = FormatterVsCode$ReventlessGwt.messagePayload(failingResult({
|
|
30
|
+
TAG: "ErrorMismatch",
|
|
31
|
+
expected: "CategoryAlreadyExists",
|
|
32
|
+
actual: undefined,
|
|
33
|
+
actualEvents: []
|
|
34
|
+
}));
|
|
35
|
+
globalThis.expect(m.kind).toEqual("ErrorMismatch");
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
failingResult,
|
|
41
|
+
}
|
|
42
|
+
/* Not a pure module */
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Worked example for InboundTranslationSlice_GWT — a payment webhook that
|
|
2
|
+
// translates `status` into either a `ConfirmPayment` command or a
|
|
3
|
+
// translation error for unknown statuses.
|
|
4
|
+
|
|
5
|
+
@@reventless.gwt
|
|
6
|
+
|
|
7
|
+
module PaymentWebhookSlice = {
|
|
8
|
+
let name = "PaymentWebhook"
|
|
9
|
+
|
|
10
|
+
@schema
|
|
11
|
+
type externalInput = {paymentId: string, orderId: string, status: string}
|
|
12
|
+
|
|
13
|
+
@schema
|
|
14
|
+
type command = ConfirmPayment({orderId: string, paymentId: string})
|
|
15
|
+
|
|
16
|
+
let translate = input =>
|
|
17
|
+
switch input.status {
|
|
18
|
+
| "completed" =>
|
|
19
|
+
Ok([(input.orderId, ConfirmPayment({orderId: input.orderId, paymentId: input.paymentId}))])
|
|
20
|
+
| _ => Error("Unknown payment status: " ++ input.status)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe("PaymentWebhook InboundTranslationSlice", () => {
|
|
25
|
+
test("completed status emits ConfirmPayment", () =>
|
|
26
|
+
whenInput({paymentId: "p1", orderId: "o1", status: "completed"})
|
|
27
|
+
->thenCommand("o1", ConfirmPayment({orderId: "o1", paymentId: "p1"}))
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
test("unknown status surfaces translate error", () =>
|
|
31
|
+
whenInput({paymentId: "p1", orderId: "o1", status: "garbage"})
|
|
32
|
+
->thenTranslateError("Unknown payment status: garbage")
|
|
33
|
+
)
|
|
34
|
+
})
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
import * as InboundTranslation_GWT$ReventlessGwt from "../src/InboundTranslation_GWT.res.mjs";
|
|
5
|
+
|
|
6
|
+
let externalInputSchema = S.schema(s => ({
|
|
7
|
+
paymentId: s.m(S.string),
|
|
8
|
+
orderId: s.m(S.string),
|
|
9
|
+
status: s.m(S.string)
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
let commandSchema = S.schema(s => ({
|
|
13
|
+
TAG: "ConfirmPayment",
|
|
14
|
+
orderId: s.m(S.string),
|
|
15
|
+
paymentId: s.m(S.string)
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
function translate(input) {
|
|
19
|
+
let match = input.status;
|
|
20
|
+
if (match === "completed") {
|
|
21
|
+
return {
|
|
22
|
+
TAG: "Ok",
|
|
23
|
+
_0: [[
|
|
24
|
+
input.orderId,
|
|
25
|
+
{
|
|
26
|
+
TAG: "ConfirmPayment",
|
|
27
|
+
orderId: input.orderId,
|
|
28
|
+
paymentId: input.paymentId
|
|
29
|
+
}
|
|
30
|
+
]]
|
|
31
|
+
};
|
|
32
|
+
} else {
|
|
33
|
+
return {
|
|
34
|
+
TAG: "Error",
|
|
35
|
+
_0: "Unknown payment status: " + input.status
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function commandAuthorization(param) {
|
|
41
|
+
return "AllowAuthenticated";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let PaymentWebhookSlice = {
|
|
45
|
+
name: "PaymentWebhook",
|
|
46
|
+
externalInputSchema: externalInputSchema,
|
|
47
|
+
commandSchema: commandSchema,
|
|
48
|
+
translate: translate,
|
|
49
|
+
commandAuthorization: commandAuthorization,
|
|
50
|
+
externalSystem: undefined
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
let include = InboundTranslation_GWT$ReventlessGwt.Make(PaymentWebhookSlice);
|
|
54
|
+
|
|
55
|
+
let describe = include.describe;
|
|
56
|
+
|
|
57
|
+
let test = include.test;
|
|
58
|
+
|
|
59
|
+
let whenInput = include.whenInput;
|
|
60
|
+
|
|
61
|
+
let thenCommand = include.thenCommand;
|
|
62
|
+
|
|
63
|
+
let thenTranslateError = include.thenTranslateError;
|
|
64
|
+
|
|
65
|
+
describe("PaymentWebhook InboundTranslationSlice", () => {
|
|
66
|
+
test("completed status emits ConfirmPayment", () => thenCommand(whenInput({
|
|
67
|
+
paymentId: "p1",
|
|
68
|
+
orderId: "o1",
|
|
69
|
+
status: "completed"
|
|
70
|
+
}), "o1", {
|
|
71
|
+
TAG: "ConfirmPayment",
|
|
72
|
+
orderId: "o1",
|
|
73
|
+
paymentId: "p1"
|
|
74
|
+
}));
|
|
75
|
+
test("unknown status surfaces translate error", () => thenTranslateError(whenInput({
|
|
76
|
+
paymentId: "p1",
|
|
77
|
+
orderId: "o1",
|
|
78
|
+
status: "garbage"
|
|
79
|
+
}), "Unknown payment status: garbage"));
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
let Spec = include.Spec;
|
|
83
|
+
|
|
84
|
+
let thenCommands = include.thenCommands;
|
|
85
|
+
|
|
86
|
+
let thenNoCommand = include.thenNoCommand;
|
|
87
|
+
|
|
88
|
+
export {
|
|
89
|
+
PaymentWebhookSlice,
|
|
90
|
+
Spec,
|
|
91
|
+
describe,
|
|
92
|
+
test,
|
|
93
|
+
whenInput,
|
|
94
|
+
thenCommands,
|
|
95
|
+
thenCommand,
|
|
96
|
+
thenNoCommand,
|
|
97
|
+
thenTranslateError,
|
|
98
|
+
}
|
|
99
|
+
/* externalInputSchema Not a pure module */
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Integration test for LocalHost.loadGraph — the cold-load path that instantiates
|
|
2
|
+
// reventless-local's Platform and builds the real example plugins against it.
|
|
3
|
+
//
|
|
4
|
+
// Runs under `node --test` (NOT Jest) on purpose: cold-loading the local platform
|
|
5
|
+
// under Jest's experimental-vm-modules hits the graphql/yoga CJS-in-ESM interop
|
|
6
|
+
// wall documented in tests/SplitApiFixtures.res. Plain node ESM is exactly the
|
|
7
|
+
// runtime the CLI host uses, and where the Phase 4.5 spike proved cold-load works
|
|
8
|
+
// (docs/analysis/reventless-vscode-domain-graph-design.md "Spike result"). It is
|
|
9
|
+
// named without the `Test` suffix so the Jest `testMatch` glob
|
|
10
|
+
// (`tests/**/*Test.res.mjs`) skips it — `pnpm run test:host` runs it via node. The
|
|
11
|
+
// pure helpers (name derivation, discovery) are the Jest test in tests/LocalHostTest.res.
|
|
12
|
+
//
|
|
13
|
+
// Prerequisite: reventless-local + the online-shop-aggregates examples must be
|
|
14
|
+
// compiled (`pnpm run build` at the repo root). Run: `pnpm run test:host`.
|
|
15
|
+
|
|
16
|
+
@module("node:test") external test: (string, unit => promise<unit>) => unit = "test"
|
|
17
|
+
@module("node:assert/strict") external equal: ('a, 'a) => unit = "equal"
|
|
18
|
+
@module("node:assert/strict") external deepEqual: ('a, 'a) => unit = "deepEqual"
|
|
19
|
+
@module("node:assert/strict") external ok: (bool, ~message: string=?) => unit = "ok"
|
|
20
|
+
|
|
21
|
+
@module("node:url") external fileURLToPath: string => string = "fileURLToPath"
|
|
22
|
+
@module("node:path") external dirname: string => string = "dirname"
|
|
23
|
+
@module("node:path") @variadic external join: array<string> => string = "join"
|
|
24
|
+
|
|
25
|
+
let here = dirname(fileURLToPath(%raw(`import.meta.url`)))
|
|
26
|
+
let repoRoot = join([here, "..", "..", ".."]) // reventless-gwt/test → repo root
|
|
27
|
+
let catalogDir = join([repoRoot, "examples", "online-shop-aggregates", "catalog"])
|
|
28
|
+
let orderingDir = join([repoRoot, "examples", "online-shop-aggregates", "ordering"])
|
|
29
|
+
let platformPath = join([repoRoot, "reventless", "reventless-local", "src", "Platform.res.mjs"])
|
|
30
|
+
|
|
31
|
+
let structureFor = (g: LocalHost.graph, name) =>
|
|
32
|
+
g.structures->Array.find(((n, _)) => n == name)->Option.map(((_, s)) => s)
|
|
33
|
+
|
|
34
|
+
test("loadGraph cold-loads structures from the real plugins", async () => {
|
|
35
|
+
let plugins = LocalHost.discover(~packageDirs=[catalogDir, orderingDir])
|
|
36
|
+
let g = await LocalHost.loadGraph(~platformModulePath=platformPath, ~plugins)
|
|
37
|
+
|
|
38
|
+
// structures: resolved plain records, plugin-namespaced event types.
|
|
39
|
+
deepEqual(g.structures->Array.map(((n, _)) => n), ["Catalog", "Ordering"])
|
|
40
|
+
|
|
41
|
+
let catalog = structureFor(g, "Catalog")->Option.getOrThrow
|
|
42
|
+
let category = catalog.aggregates->Array.find((a: Reventless.Plugin.writableDef) => a.name == "Category")->Option.getOrThrow
|
|
43
|
+
ok(category.producedEventTypes->Array.includes("Catalog.Added"))
|
|
44
|
+
|
|
45
|
+
let ordering = structureFor(g, "Ordering")->Option.getOrThrow
|
|
46
|
+
let order = ordering.aggregates->Array.find((a: Reventless.Plugin.writableDef) => a.name == "Order")->Option.getOrThrow
|
|
47
|
+
ok(order.producedEventTypes->Array.includes("Ordering.Placed"))
|
|
48
|
+
|
|
49
|
+
// extensionPoints (producer side): Catalog owns Catalog.Products, fed by the
|
|
50
|
+
// Product aggregate's internal events — so the EP's sourceEventTypes are a
|
|
51
|
+
// subset of the producer's producedEventTypes, the link the event graph draws.
|
|
52
|
+
let catalogEps = catalog.extensionPoints->Option.getOr([])
|
|
53
|
+
let productsEp =
|
|
54
|
+
catalogEps->Array.find((e: Reventless.Plugin.extensionPointDef) => e.name == "Catalog.Products")->Option.getOrThrow
|
|
55
|
+
ok(productsEp.delegateNames->Array.includes("Product"), ~message=productsEp.delegateNames->Array.join(","))
|
|
56
|
+
ok(
|
|
57
|
+
productsEp.sourceEventTypes->Array.includes("Catalog.Added"),
|
|
58
|
+
~message=productsEp.sourceEventTypes->Array.join(","),
|
|
59
|
+
)
|
|
60
|
+
let product = catalog.aggregates->Array.find((a: Reventless.Plugin.writableDef) => a.name == "Product")->Option.getOrThrow
|
|
61
|
+
ok(productsEp.sourceEventTypes->Array.every(e => product.producedEventTypes->Array.includes(e)))
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test("loadGraph is cold — a second call in the same process succeeds (cache-busting works)", async () => {
|
|
65
|
+
let plugins = LocalHost.discover(~packageDirs=[catalogDir])
|
|
66
|
+
let g1 = await LocalHost.loadGraph(~platformModulePath=platformPath, ~plugins)
|
|
67
|
+
let g2 = await LocalHost.loadGraph(~platformModulePath=platformPath, ~plugins)
|
|
68
|
+
equal(g1.structures->Array.length, 1)
|
|
69
|
+
equal(g2.structures->Array.length, 1)
|
|
70
|
+
})
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Nodeurl from "node:url";
|
|
4
|
+
import * as Nodepath from "node:path";
|
|
5
|
+
import * as Nodetest from "node:test";
|
|
6
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
7
|
+
import * as Strict from "node:assert/strict";
|
|
8
|
+
import * as LocalHost$ReventlessGwt from "../src/LocalHost.res.mjs";
|
|
9
|
+
|
|
10
|
+
let here = Nodepath.dirname(Nodeurl.fileURLToPath(import.meta.url));
|
|
11
|
+
|
|
12
|
+
let repoRoot = Nodepath.join(here, "..", "..", "..");
|
|
13
|
+
|
|
14
|
+
let catalogDir = Nodepath.join(repoRoot, "examples", "online-shop-aggregates", "catalog");
|
|
15
|
+
|
|
16
|
+
let orderingDir = Nodepath.join(repoRoot, "examples", "online-shop-aggregates", "ordering");
|
|
17
|
+
|
|
18
|
+
let platformPath = Nodepath.join(repoRoot, "reventless", "reventless-local", "src", "Platform.res.mjs");
|
|
19
|
+
|
|
20
|
+
function structureFor(g, name) {
|
|
21
|
+
return Stdlib_Option.map(g.structures.find(param => param[0] === name), param => param[1]);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
Nodetest.test("loadGraph cold-loads structures from the real plugins", async () => {
|
|
25
|
+
let plugins = LocalHost$ReventlessGwt.discover([
|
|
26
|
+
catalogDir,
|
|
27
|
+
orderingDir
|
|
28
|
+
]);
|
|
29
|
+
let g = await LocalHost$ReventlessGwt.loadGraph(platformPath, plugins);
|
|
30
|
+
Strict.deepEqual(g.structures.map(param => param[0]), [
|
|
31
|
+
"Catalog",
|
|
32
|
+
"Ordering"
|
|
33
|
+
]);
|
|
34
|
+
let catalog = Stdlib_Option.getOrThrow(structureFor(g, "Catalog"), undefined);
|
|
35
|
+
let category = Stdlib_Option.getOrThrow(catalog.aggregates.find(a => a.name === "Category"), undefined);
|
|
36
|
+
Strict.ok(category.producedEventTypes.includes("Catalog.Added"));
|
|
37
|
+
let ordering = Stdlib_Option.getOrThrow(structureFor(g, "Ordering"), undefined);
|
|
38
|
+
let order = Stdlib_Option.getOrThrow(ordering.aggregates.find(a => a.name === "Order"), undefined);
|
|
39
|
+
Strict.ok(order.producedEventTypes.includes("Ordering.Placed"));
|
|
40
|
+
let catalogEps = Stdlib_Option.getOr(catalog.extensionPoints, []);
|
|
41
|
+
let productsEp = Stdlib_Option.getOrThrow(catalogEps.find(e => e.name === "Catalog.Products"), undefined);
|
|
42
|
+
Strict.ok(productsEp.delegateNames.includes("Product"), productsEp.delegateNames.join(","));
|
|
43
|
+
Strict.ok(productsEp.sourceEventTypes.includes("Catalog.Added"), productsEp.sourceEventTypes.join(","));
|
|
44
|
+
let product = Stdlib_Option.getOrThrow(catalog.aggregates.find(a => a.name === "Product"), undefined);
|
|
45
|
+
Strict.ok(productsEp.sourceEventTypes.every(e => product.producedEventTypes.includes(e)));
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
Nodetest.test("loadGraph is cold — a second call in the same process succeeds (cache-busting works)", async () => {
|
|
49
|
+
let plugins = LocalHost$ReventlessGwt.discover([catalogDir]);
|
|
50
|
+
let g1 = await LocalHost$ReventlessGwt.loadGraph(platformPath, plugins);
|
|
51
|
+
let g2 = await LocalHost$ReventlessGwt.loadGraph(platformPath, plugins);
|
|
52
|
+
Strict.equal(g1.structures.length, 1);
|
|
53
|
+
Strict.equal(g2.structures.length, 1);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
export {
|
|
57
|
+
here,
|
|
58
|
+
repoRoot,
|
|
59
|
+
catalogDir,
|
|
60
|
+
orderingDir,
|
|
61
|
+
platformPath,
|
|
62
|
+
structureFor,
|
|
63
|
+
}
|
|
64
|
+
/* here Not a pure module */
|