@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,352 @@
|
|
|
1
|
+
// NDJSON event stream tailored for the VS Code Testing API. Each line maps directly
|
|
2
|
+
// to a `TestRun` method call; field names mirror `TestItem` / `TestMessage` so the
|
|
3
|
+
// extension needs no translation layer.
|
|
4
|
+
//
|
|
5
|
+
// The contract is the shared `@reventlessdev/reventless-vscode-protocol` `Protocol`
|
|
6
|
+
// module — the SAME sury `@schema` the extension decodes with. Every event is built as
|
|
7
|
+
// a `Protocol.streamEvent` variant and serialized via `Protocol.toJsonLine`, so a
|
|
8
|
+
// `protocolVersion` bump or field change touches that one definition instead of
|
|
9
|
+
// drifting between this emitter and the extension's decoder.
|
|
10
|
+
|
|
11
|
+
module P = ReventlessVscodeProtocol.Protocol
|
|
12
|
+
|
|
13
|
+
@val external processStdout: {"write": string => unit} = "process.stdout"
|
|
14
|
+
let write = (s: string) => processStdout["write"](s)
|
|
15
|
+
let writeLine = (s: string) => write(s ++ "\n")
|
|
16
|
+
|
|
17
|
+
// Serialize one contract event to its NDJSON line (single source: Protocol).
|
|
18
|
+
let emit = (e: P.streamEvent) => writeLine(P.toJsonLine(e))
|
|
19
|
+
|
|
20
|
+
let uriOf = (path: string) => "file://" ++ path
|
|
21
|
+
|
|
22
|
+
@val external processCwd: unit => string = "process.cwd"
|
|
23
|
+
@module("node:path") external pathBasename: string => string = "basename"
|
|
24
|
+
@module("node:path") external pathRelative: (string, string) => string = "relative"
|
|
25
|
+
|
|
26
|
+
let fileLabelOf = (abs: string) => {
|
|
27
|
+
let base = pathBasename(abs)
|
|
28
|
+
if String.endsWith(base, ".res.mjs") {
|
|
29
|
+
String.slice(base, ~start=0, ~end=String.length(base) - 8)
|
|
30
|
+
} else {
|
|
31
|
+
base
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let relativeToCwd = (abs: string) => pathRelative(processCwd(), abs)
|
|
36
|
+
|
|
37
|
+
// ─── .res-source locator ───────────────────────────────────────────────────
|
|
38
|
+
// ReScript v12 does not emit JS source maps, so V8 stack frames (the basis for
|
|
39
|
+
// Collector.captureLocation) point at `.res.mjs`. Every GWT combinator takes a string
|
|
40
|
+
// literal as its first argument — scanning the sibling `.res` file for that literal
|
|
41
|
+
// yields an exact line, turning tree-node clicks into `.res` jumps.
|
|
42
|
+
|
|
43
|
+
@module("node:fs") external _readFileSync: (string, string) => string = "readFileSync"
|
|
44
|
+
@module("node:fs") external _existsSync: string => bool = "existsSync"
|
|
45
|
+
|
|
46
|
+
let sourceLineCache: Dict.t<option<array<string>>> = Dict.make()
|
|
47
|
+
|
|
48
|
+
let mjsToRes = (mjsPath: string): option<string> =>
|
|
49
|
+
if String.endsWith(mjsPath, ".res.mjs") {
|
|
50
|
+
Some(String.slice(mjsPath, ~start=0, ~end=String.length(mjsPath) - 4))
|
|
51
|
+
} else {
|
|
52
|
+
None
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let readLinesCached = (resPath: string): option<array<string>> =>
|
|
56
|
+
switch sourceLineCache->Dict.get(resPath) {
|
|
57
|
+
| Some(v) => v
|
|
58
|
+
| None => {
|
|
59
|
+
let v = try {
|
|
60
|
+
_existsSync(resPath)
|
|
61
|
+
? Some(_readFileSync(resPath, "utf8")->String.split("\n"))
|
|
62
|
+
: None
|
|
63
|
+
} catch {
|
|
64
|
+
| _ => None
|
|
65
|
+
}
|
|
66
|
+
sourceLineCache->Dict.set(resPath, v)
|
|
67
|
+
v
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let escapeForDouble = (s: string) =>
|
|
72
|
+
s->String.replaceAll("\\", "\\\\")->String.replaceAll("\"", "\\\"")
|
|
73
|
+
|
|
74
|
+
let escapeForSingle = (s: string) =>
|
|
75
|
+
s->String.replaceAll("\\", "\\\\")->String.replaceAll("'", "\\'")
|
|
76
|
+
|
|
77
|
+
let locateInSource = (mjsPath: string, label: string): option<Collector.location> =>
|
|
78
|
+
switch mjsToRes(mjsPath) {
|
|
79
|
+
| None => None
|
|
80
|
+
| Some(resPath) =>
|
|
81
|
+
switch readLinesCached(resPath) {
|
|
82
|
+
| None => None
|
|
83
|
+
| Some(lines) => {
|
|
84
|
+
let needleD = "\"" ++ escapeForDouble(label) ++ "\""
|
|
85
|
+
let needleS = "'" ++ escapeForSingle(label) ++ "'"
|
|
86
|
+
let total = lines->Array.length
|
|
87
|
+
let rec find = i =>
|
|
88
|
+
if i >= total {
|
|
89
|
+
None
|
|
90
|
+
} else {
|
|
91
|
+
let line = lines->Array.getUnsafe(i)
|
|
92
|
+
let idxD = String.indexOf(line, needleD)
|
|
93
|
+
let idxS = String.indexOf(line, needleS)
|
|
94
|
+
let col = switch (idxD, idxS) {
|
|
95
|
+
| (-1, -1) => -1
|
|
96
|
+
| (-1, c) => c + 1
|
|
97
|
+
| (c, -1) => c + 1
|
|
98
|
+
| (a, b) => (a < b ? a : b) + 1
|
|
99
|
+
}
|
|
100
|
+
if col < 0 {
|
|
101
|
+
find(i + 1)
|
|
102
|
+
} else {
|
|
103
|
+
Some({Collector.file: resPath, line: i + 1, column: col})
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
find(0)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 1-based Collector.location → 0-based vscode range (both ends at the token start, as
|
|
112
|
+
// the previous emitter did).
|
|
113
|
+
let rangeOf = (loc: Collector.location): P.vsRange => {
|
|
114
|
+
let pos: P.position = {line: loc.line - 1, character: loc.column - 1}
|
|
115
|
+
{start: pos, end: pos}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let locationOf = (loc: Collector.location): P.failLocation => {
|
|
119
|
+
uri: uriOf(loc.file),
|
|
120
|
+
range: rangeOf(loc),
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Bumped whenever the event contract changes — single source in `Protocol`.
|
|
124
|
+
let protocolVersion = P.protocolVersion
|
|
125
|
+
|
|
126
|
+
// First line of every `--format=vscode` invocation, so a client can detect a
|
|
127
|
+
// version-skewed CLI before interpreting the stream.
|
|
128
|
+
let hello = () => emit(Hello({protocol: P.protocolVersion}))
|
|
129
|
+
|
|
130
|
+
let discoverStart = () => emit(DiscoverStart({}))
|
|
131
|
+
|
|
132
|
+
// Used in both `discover` and `run` flows — emit one `item` per file, one per describe,
|
|
133
|
+
// one per test so the tree populates before any test runs. File / suite / test URIs
|
|
134
|
+
// point at `.res` sources whenever the sibling `.res` is readable.
|
|
135
|
+
let emitDiscoveryItems = (files: array<(string, array<RunnerTypes.testResult>)>) => {
|
|
136
|
+
files->Array.forEach(((path, tests)) => {
|
|
137
|
+
let fileId = path
|
|
138
|
+
let fileUri = switch mjsToRes(path) {
|
|
139
|
+
| Some(resPath) if _existsSync(resPath) => uriOf(resPath)
|
|
140
|
+
| _ => uriOf(path)
|
|
141
|
+
}
|
|
142
|
+
let component =
|
|
143
|
+
ComponentMeta.componentOfTestFile(path)->Option.map((c): P.componentMeta => {
|
|
144
|
+
// `ComponentKind.folderName` spellings ARE the typed vocabulary's constructor
|
|
145
|
+
// names, so the identity conversion lands every known kind on its constructor.
|
|
146
|
+
kind: P.componentKindOfString(c.kind),
|
|
147
|
+
name: c.name,
|
|
148
|
+
})
|
|
149
|
+
emit(
|
|
150
|
+
Item({
|
|
151
|
+
id: fileId,
|
|
152
|
+
kind: File,
|
|
153
|
+
label: fileLabelOf(path),
|
|
154
|
+
description: relativeToCwd(path),
|
|
155
|
+
uri: fileUri,
|
|
156
|
+
component: ?component,
|
|
157
|
+
}),
|
|
158
|
+
)
|
|
159
|
+
// Emit describe suites and test leaves.
|
|
160
|
+
let seenDescribes = Dict.make()
|
|
161
|
+
tests->Array.forEach(t => {
|
|
162
|
+
let stack = ref([])
|
|
163
|
+
t.describePath->Array.forEach(label => {
|
|
164
|
+
let parent =
|
|
165
|
+
stack.contents->Array.length == 0
|
|
166
|
+
? fileId
|
|
167
|
+
: fileId ++ "::" ++ stack.contents->Array.join("::")
|
|
168
|
+
stack := Array.concat(stack.contents, [label])
|
|
169
|
+
let id = fileId ++ "::" ++ stack.contents->Array.join("::")
|
|
170
|
+
switch seenDescribes->Dict.get(id) {
|
|
171
|
+
| Some(_) => ()
|
|
172
|
+
| None => {
|
|
173
|
+
seenDescribes->Dict.set(id, true)
|
|
174
|
+
let loc = locateInSource(path, label)
|
|
175
|
+
emit(
|
|
176
|
+
Item({
|
|
177
|
+
id,
|
|
178
|
+
parent,
|
|
179
|
+
kind: Suite,
|
|
180
|
+
label,
|
|
181
|
+
uri: ?loc->Option.map(l => uriOf(l.file)),
|
|
182
|
+
range: ?loc->Option.map(rangeOf),
|
|
183
|
+
}),
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
let parent =
|
|
189
|
+
t.describePath->Array.length == 0
|
|
190
|
+
? fileId
|
|
191
|
+
: fileId ++ "::" ++ t.describePath->Array.join("::")
|
|
192
|
+
let testId = fileId ++ "::" ++ t.id
|
|
193
|
+
let resLoc = locateInSource(path, t.name)
|
|
194
|
+
let loc = switch (resLoc, t.location) {
|
|
195
|
+
| (Some(loc), _) | (None, Some(loc)) => Some(loc)
|
|
196
|
+
| (None, None) => None
|
|
197
|
+
}
|
|
198
|
+
emit(
|
|
199
|
+
Item({
|
|
200
|
+
id: testId,
|
|
201
|
+
parent,
|
|
202
|
+
kind: Test,
|
|
203
|
+
label: t.name,
|
|
204
|
+
uri: ?loc->Option.map(l => uriOf(l.file)),
|
|
205
|
+
range: ?loc->Option.map(rangeOf),
|
|
206
|
+
}),
|
|
207
|
+
)
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
let discoverEnd = (total: int) => emit(DiscoverEnd({total: total}))
|
|
213
|
+
|
|
214
|
+
// The set of workspace packages owning discovered tests — the build set a watch
|
|
215
|
+
// session keeps compiling.
|
|
216
|
+
let packages = (pkgs: array<PackageScan.pkg>) =>
|
|
217
|
+
emit(
|
|
218
|
+
Packages({
|
|
219
|
+
packages: pkgs->Array.map((p): P.packageInfo => {name: p.name, dir: p.dir, build: p.build}),
|
|
220
|
+
}),
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
// The full component inventory across the owning packages — every Aggregate / slice /
|
|
224
|
+
// ReadModel / … in src/, whether or not it has a GWT test.
|
|
225
|
+
let components = (comps: array<ComponentScan.component>) =>
|
|
226
|
+
emit(
|
|
227
|
+
Components({
|
|
228
|
+
components: comps->Array.map((c): P.componentRef => {
|
|
229
|
+
dir: c.dir,
|
|
230
|
+
kind: P.componentKindOfString(c.kind),
|
|
231
|
+
name: c.name,
|
|
232
|
+
files: c.files,
|
|
233
|
+
}),
|
|
234
|
+
}),
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
// Domain-level dead code (Phase 5) — produced events no component consumes.
|
|
238
|
+
let deadCode = (findings: array<DomainDeadCode.finding>) =>
|
|
239
|
+
emit(
|
|
240
|
+
DeadCode({
|
|
241
|
+
findings: findings->Array.map((f): P.deadCodeFinding => {
|
|
242
|
+
kind: f.kind,
|
|
243
|
+
plugin: f.pluginName,
|
|
244
|
+
component: f.componentName,
|
|
245
|
+
detail: f.detail,
|
|
246
|
+
}),
|
|
247
|
+
}),
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
// Event-Modeling graph (Phase 6) — the node/edge model assembled from each plugin's
|
|
251
|
+
// `pluginStructure` + cross-plugin edges.
|
|
252
|
+
// `DomainGraph` already builds `P.graphNode`/`P.graphEdge`, so the graph event
|
|
253
|
+
// forwards them directly — no field-by-field re-map to drift from the contract.
|
|
254
|
+
let graph = (g: DomainGraph.graph) => emit(Graph({nodes: g.nodes, edges: g.edges}))
|
|
255
|
+
|
|
256
|
+
// Component definitions (Phase 6.3) — one `encodePluginStructureEntry` JSON object
|
|
257
|
+
// per plugin (commands/events with field schemas, read-side state schemas), used by
|
|
258
|
+
// the extension to render field rows.
|
|
259
|
+
let definitions = (entries: array<JSON.t>) => emit(Definitions({entries: entries}))
|
|
260
|
+
|
|
261
|
+
// ─── Build status (watch mode) ──────────────────────────────────────────────
|
|
262
|
+
|
|
263
|
+
let buildStart = (pkg: string) => emit(BuildStart({package: pkg}))
|
|
264
|
+
let buildOk = (pkg: string, durationMs: float) => emit(BuildOk({package: pkg, durationMs: durationMs}))
|
|
265
|
+
let buildFail = (pkg: string, message: string) => emit(BuildFail({package: pkg, message: message}))
|
|
266
|
+
let buildExternal = (pkg: string) => emit(BuildExternal({package: pkg}))
|
|
267
|
+
|
|
268
|
+
// ── Local platform runner events (protocol 7) ───────────────────────────────
|
|
269
|
+
|
|
270
|
+
let platformStart = (~package: string, ~dir: string, ~domainPort: int, ~platformPort: int) =>
|
|
271
|
+
emit(PlatformStart({package, dir, domainPort, platformPort}))
|
|
272
|
+
|
|
273
|
+
let platformReady = (~domainEndpoint: string) => emit(PlatformReady({domainEndpoint: domainEndpoint}))
|
|
274
|
+
|
|
275
|
+
// `payload` is the parsed JSON object the LocalBus tap emitted (already shaped
|
|
276
|
+
// `{event:"domainEvent", seq, topic, service, payload, ts}`) — re-emitted verbatim as
|
|
277
|
+
// one NDJSON line so the extension parses it like any other engine event.
|
|
278
|
+
let domainEvent = (payload: JSON.t) => writeLine(JSON.stringify(payload))
|
|
279
|
+
|
|
280
|
+
let platformLog = (~line: string) => emit(PlatformLog({line: line}))
|
|
281
|
+
|
|
282
|
+
let platformStop = (~code: option<int>) => emit(PlatformStop({code: ?code}))
|
|
283
|
+
|
|
284
|
+
let runStart = (~total: int, ~filter: array<string>) => emit(RunStart({total, filter}))
|
|
285
|
+
|
|
286
|
+
let testStart = (id: string) => emit(TestStart({id: id}))
|
|
287
|
+
|
|
288
|
+
let testPass = (id: string, durationMs: float) => emit(TestPass({id, durationMs}))
|
|
289
|
+
|
|
290
|
+
let testSkip = (id: string, reason: string) => emit(TestSkip({id, reason}))
|
|
291
|
+
|
|
292
|
+
// The failure payload the extension renders — built as a typed `Protocol.failMessage`.
|
|
293
|
+
let messagePayload = (t: RunnerTypes.testResult): P.failMessage =>
|
|
294
|
+
switch t.mismatch {
|
|
295
|
+
| Some(m) =>
|
|
296
|
+
let hint = Hint.forMismatch(~slice=?t.slice, m)
|
|
297
|
+
// Expected / actual for the diff view, rendered as ReScript syntax.
|
|
298
|
+
let (expected, actual) = switch m {
|
|
299
|
+
| EventsMismatch({expected, actual}) => (
|
|
300
|
+
Some(RenderRescript.renderMany(expected)),
|
|
301
|
+
Some(RenderRescript.renderMany(actual)),
|
|
302
|
+
)
|
|
303
|
+
| ErrorMismatch({expected, actual, actualEvents}) =>
|
|
304
|
+
let actStr = switch actual {
|
|
305
|
+
| Some(v) => "Error(" ++ RenderRescript.render(v) ++ ")"
|
|
306
|
+
| None => "Ok(" ++ RenderRescript.renderMany(actualEvents) ++ ")"
|
|
307
|
+
}
|
|
308
|
+
(Some("Error(" ++ RenderRescript.render(expected) ++ ")"), Some(actStr))
|
|
309
|
+
| StateMismatch({expected, actual, _}) => (
|
|
310
|
+
Some(RenderRescript.renderOption(expected)),
|
|
311
|
+
Some(RenderRescript.renderOption(actual)),
|
|
312
|
+
)
|
|
313
|
+
| NoEventExpected({actual}) => (Some("[]"), Some(RenderRescript.renderMany(actual)))
|
|
314
|
+
| QueryRowsMismatch({expected, actual}) => (
|
|
315
|
+
Some(RenderRescript.renderMany(expected)),
|
|
316
|
+
Some(RenderRescript.renderMany(actual)),
|
|
317
|
+
)
|
|
318
|
+
| PublishedActionsMismatch({expected, actual}) => (
|
|
319
|
+
Some(RenderRescript.renderMany(expected)),
|
|
320
|
+
Some(RenderRescript.renderMany(actual)),
|
|
321
|
+
)
|
|
322
|
+
| AppendConditionMismatch({expected, actual}) => (
|
|
323
|
+
Some(RenderRescript.render(expected)),
|
|
324
|
+
Some(RenderRescript.render(actual)),
|
|
325
|
+
)
|
|
326
|
+
| TranslateError({expected, actual}) => (Some(expected), Some(actual->Option.getOr("(none)")))
|
|
327
|
+
| TodoMismatch(_) => (Some(Outcome.format(m)), Some(""))
|
|
328
|
+
| Throw({error}) => (Some(""), Some(error))
|
|
329
|
+
}
|
|
330
|
+
// Location points at the test file — prefer the sibling `.res` resolved via the
|
|
331
|
+
// test label so Cmd+Click jumps to readable source, not compiled `.res.mjs`.
|
|
332
|
+
let location =
|
|
333
|
+
t.location->Option.map(loc => locationOf(locateInSource(loc.file, t.name)->Option.getOr(loc)))
|
|
334
|
+
{
|
|
335
|
+
message: hint.message,
|
|
336
|
+
// The mismatch family — lets a client gate the apply-expected quick-fix.
|
|
337
|
+
// `Outcome.kindName` stays string-returning (JUnit/JSON formatters consume it
|
|
338
|
+
// raw); its spellings are the assertionKind constructor names, so the identity
|
|
339
|
+
// conversion types it losslessly.
|
|
340
|
+
kind: ?Some(P.assertionKindOfString(Outcome.kindName(m))),
|
|
341
|
+
expected: ?expected,
|
|
342
|
+
actual: ?actual,
|
|
343
|
+
location: ?location,
|
|
344
|
+
}
|
|
345
|
+
| None => {message: "failed"}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
let testFail = (t: RunnerTypes.testResult, id: string) =>
|
|
349
|
+
emit(TestFail({id, durationMs: t.durationMs, messages: [messagePayload(t)]}))
|
|
350
|
+
|
|
351
|
+
let runEnd = (s: RunnerTypes.summary) =>
|
|
352
|
+
emit(RunEnd({passed: s.passed, failed: s.failed, skipped: s.skipped, durationMs: s.durationMs}))
|