@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,212 @@
|
|
|
1
|
+
// Module-level collector used by the CLI runner.
|
|
2
|
+
//
|
|
3
|
+
// When the CLI loads a test file via dynamic import, each `describe`/`test`
|
|
4
|
+
// call routes through `Bind` or `JestBind` and pushes an entry into this
|
|
5
|
+
// collector. After the import resolves (all registrations complete), the CLI
|
|
6
|
+
// drains the entries, executes each body asynchronously, and feeds the results
|
|
7
|
+
// into a formatter.
|
|
8
|
+
//
|
|
9
|
+
// Activation is toggled by the CLI — when inactive, `JestBind` falls back to
|
|
10
|
+
// Jest globals, so existing Jest test runs are unaffected.
|
|
11
|
+
|
|
12
|
+
type status = Skipped | Runnable | Only
|
|
13
|
+
|
|
14
|
+
type location = {
|
|
15
|
+
file: string,
|
|
16
|
+
line: int,
|
|
17
|
+
column: int,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type entry = {
|
|
21
|
+
id: string,
|
|
22
|
+
name: string,
|
|
23
|
+
describePath: array<string>,
|
|
24
|
+
slice: option<string>,
|
|
25
|
+
body: unit => promise<Outcome.outcome>,
|
|
26
|
+
status: status,
|
|
27
|
+
location: option<location>,
|
|
28
|
+
// Per-test deadline in ms (from `~timeout` on `testPromise`). `None` = use the
|
|
29
|
+
// runner default. The CLI runner races the body against this; the Jest arm
|
|
30
|
+
// passes it to Jest's own timeout instead.
|
|
31
|
+
timeout: option<int>,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let active = ref(false)
|
|
35
|
+
let describeStack: ref<array<string>> = ref([])
|
|
36
|
+
let entries: ref<array<entry>> = ref([])
|
|
37
|
+
let hasOnly = ref(false)
|
|
38
|
+
let skipNext = ref(false)
|
|
39
|
+
let onlyNext = ref(false)
|
|
40
|
+
let skipDepth = ref(0)
|
|
41
|
+
let currentFile: ref<option<string>> = ref(None)
|
|
42
|
+
|
|
43
|
+
let activate = () => {
|
|
44
|
+
active := true
|
|
45
|
+
describeStack := []
|
|
46
|
+
entries := []
|
|
47
|
+
hasOnly := false
|
|
48
|
+
skipNext := false
|
|
49
|
+
onlyNext := false
|
|
50
|
+
// Reset the skip nesting depth too: a throwing `xdescribe` body in a prior
|
|
51
|
+
// file could otherwise leave it > 0, silently skipping every test in every
|
|
52
|
+
// subsequently loaded file.
|
|
53
|
+
skipDepth := 0
|
|
54
|
+
currentFile := None
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let deactivate = () => active := false
|
|
58
|
+
let isActive = () => active.contents
|
|
59
|
+
|
|
60
|
+
let setCurrentFile = path => currentFile := Some(path)
|
|
61
|
+
let getCurrentFile = () => currentFile.contents
|
|
62
|
+
|
|
63
|
+
let markSkipNext = () => skipNext := true
|
|
64
|
+
let markOnlyNext = () => {
|
|
65
|
+
onlyNext := true
|
|
66
|
+
hasOnly := true
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let pushDescribe = (label: string, body: unit => unit) => {
|
|
70
|
+
describeStack := Array.concat(describeStack.contents, [label])
|
|
71
|
+
body()
|
|
72
|
+
let ds = describeStack.contents
|
|
73
|
+
describeStack := ds->Array.slice(~start=0, ~end=ds->Array.length - 1)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let buildId = (describePath, name) => {
|
|
77
|
+
let prefix = describePath->Array.join("::")
|
|
78
|
+
prefix == "" ? name : prefix ++ "::" ++ name
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let push = (
|
|
82
|
+
~slice=?,
|
|
83
|
+
~location=?,
|
|
84
|
+
~timeout=?,
|
|
85
|
+
name: string,
|
|
86
|
+
body: unit => promise<Outcome.outcome>,
|
|
87
|
+
) => {
|
|
88
|
+
let status = if skipDepth.contents > 0 {
|
|
89
|
+
Skipped
|
|
90
|
+
} else if skipNext.contents {
|
|
91
|
+
skipNext := false
|
|
92
|
+
Skipped
|
|
93
|
+
} else if onlyNext.contents {
|
|
94
|
+
onlyNext := false
|
|
95
|
+
Only
|
|
96
|
+
} else {
|
|
97
|
+
Runnable
|
|
98
|
+
}
|
|
99
|
+
let describePath = describeStack.contents
|
|
100
|
+
let id = buildId(describePath, name)
|
|
101
|
+
let entry = {
|
|
102
|
+
id,
|
|
103
|
+
name,
|
|
104
|
+
describePath,
|
|
105
|
+
slice,
|
|
106
|
+
body,
|
|
107
|
+
status,
|
|
108
|
+
location,
|
|
109
|
+
timeout,
|
|
110
|
+
}
|
|
111
|
+
entries.contents->Array.push(entry)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// A `todo` placeholder: a spec-less slice still ships a discoverable, compiling
|
|
115
|
+
// `_GWT.res`. Register it as a Skipped entry with a no-op passing body so the
|
|
116
|
+
// CLI runner lists it without executing or failing.
|
|
117
|
+
let pushTodo = (name: string) => {
|
|
118
|
+
let describePath = describeStack.contents
|
|
119
|
+
let id = buildId(describePath, name)
|
|
120
|
+
let entry = {
|
|
121
|
+
id,
|
|
122
|
+
name,
|
|
123
|
+
describePath,
|
|
124
|
+
slice: None,
|
|
125
|
+
body: () => Promise.resolve(Outcome.pass),
|
|
126
|
+
status: Skipped,
|
|
127
|
+
location: None,
|
|
128
|
+
timeout: None,
|
|
129
|
+
}
|
|
130
|
+
entries.contents->Array.push(entry)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let drain = () => {
|
|
134
|
+
let result = entries.contents
|
|
135
|
+
entries := []
|
|
136
|
+
describeStack := []
|
|
137
|
+
result
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Parse a single V8 stack frame line like:
|
|
141
|
+
// " at body (/abs/path/File.res.mjs:42:13)"
|
|
142
|
+
// " at /abs/path/File.res.mjs:42:13"
|
|
143
|
+
// Returns (file, line, column) if the frame belongs to a non-internal user file.
|
|
144
|
+
let parseFrame = (frame: string): option<location> => {
|
|
145
|
+
let trimmed = frame->String.trim
|
|
146
|
+
let body = if String.startsWith(trimmed, "at ") {
|
|
147
|
+
String.slice(trimmed, ~start=3, ~end=String.length(trimmed))
|
|
148
|
+
} else {
|
|
149
|
+
trimmed
|
|
150
|
+
}
|
|
151
|
+
let withLoc = switch String.lastIndexOf(body, "(") {
|
|
152
|
+
| -1 => body
|
|
153
|
+
| i =>
|
|
154
|
+
let rest = String.slice(body, ~start=i + 1, ~end=String.length(body))
|
|
155
|
+
switch String.lastIndexOf(rest, ")") {
|
|
156
|
+
| -1 => rest
|
|
157
|
+
| j => String.slice(rest, ~start=0, ~end=j)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
let parts = withLoc->String.split(":")
|
|
161
|
+
let n = parts->Array.length
|
|
162
|
+
if n < 3 {
|
|
163
|
+
None
|
|
164
|
+
} else {
|
|
165
|
+
let col = parts->Array.getUnsafe(n - 1)->Int.fromString
|
|
166
|
+
let line = parts->Array.getUnsafe(n - 2)->Int.fromString
|
|
167
|
+
let file =
|
|
168
|
+
parts->Array.slice(~start=0, ~end=n - 2)->Array.join(":")
|
|
169
|
+
let file = if String.startsWith(file, "file://") {
|
|
170
|
+
String.slice(file, ~start=7, ~end=String.length(file))
|
|
171
|
+
} else {
|
|
172
|
+
file
|
|
173
|
+
}
|
|
174
|
+
// Strip the Loader's `?t=N` cache-busting query so locations point at the
|
|
175
|
+
// real file path.
|
|
176
|
+
let file = switch String.indexOf(file, "?") {
|
|
177
|
+
| -1 => file
|
|
178
|
+
| q => String.slice(file, ~start=0, ~end=q)
|
|
179
|
+
}
|
|
180
|
+
switch (line, col) {
|
|
181
|
+
| (Some(l), Some(c)) => Some({file, line: l, column: c})
|
|
182
|
+
| _ => None
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Walk a captured stack and return the first frame that lives in a test
|
|
188
|
+
// file outside the reventless-gwt package itself.
|
|
189
|
+
let captureLocation = (skip: int): option<location> => {
|
|
190
|
+
let stack: option<string> = %raw(`(new Error()).stack`)
|
|
191
|
+
switch stack {
|
|
192
|
+
| None => None
|
|
193
|
+
| Some(s) =>
|
|
194
|
+
let lines = s->String.split("\n")
|
|
195
|
+
let total = lines->Array.length
|
|
196
|
+
// Skip the "Error" header, plus `skip` internal frames.
|
|
197
|
+
let rec find = i =>
|
|
198
|
+
if i >= total {
|
|
199
|
+
None
|
|
200
|
+
} else {
|
|
201
|
+
let frame = lines->Array.getUnsafe(i)
|
|
202
|
+
switch parseFrame(frame) {
|
|
203
|
+
| Some(loc)
|
|
204
|
+
if !(loc.file->String.includes("reventless-gwt/src/")) &&
|
|
205
|
+
!(loc.file->String.includes("node_modules/")) =>
|
|
206
|
+
Some(loc)
|
|
207
|
+
| _ => find(i + 1)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
find(skip + 1)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_Int from "@rescript/runtime/lib/es6/Stdlib_Int.js";
|
|
4
|
+
import * as Outcome$ReventlessGwt from "./Outcome.res.mjs";
|
|
5
|
+
|
|
6
|
+
let active = {
|
|
7
|
+
contents: false
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
let describeStack = {
|
|
11
|
+
contents: []
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
let entries = {
|
|
15
|
+
contents: []
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
let hasOnly = {
|
|
19
|
+
contents: false
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
let skipNext = {
|
|
23
|
+
contents: false
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
let onlyNext = {
|
|
27
|
+
contents: false
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
let skipDepth = {
|
|
31
|
+
contents: 0
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
let currentFile = {
|
|
35
|
+
contents: undefined
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
function activate() {
|
|
39
|
+
active.contents = true;
|
|
40
|
+
describeStack.contents = [];
|
|
41
|
+
entries.contents = [];
|
|
42
|
+
hasOnly.contents = false;
|
|
43
|
+
skipNext.contents = false;
|
|
44
|
+
onlyNext.contents = false;
|
|
45
|
+
skipDepth.contents = 0;
|
|
46
|
+
currentFile.contents = undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function deactivate() {
|
|
50
|
+
active.contents = false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isActive() {
|
|
54
|
+
return active.contents;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function setCurrentFile(path) {
|
|
58
|
+
currentFile.contents = path;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getCurrentFile() {
|
|
62
|
+
return currentFile.contents;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function markSkipNext() {
|
|
66
|
+
skipNext.contents = true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function markOnlyNext() {
|
|
70
|
+
onlyNext.contents = true;
|
|
71
|
+
hasOnly.contents = true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function pushDescribe(label, body) {
|
|
75
|
+
describeStack.contents = describeStack.contents.concat([label]);
|
|
76
|
+
body();
|
|
77
|
+
let ds = describeStack.contents;
|
|
78
|
+
describeStack.contents = ds.slice(0, ds.length - 1 | 0);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function buildId(describePath, name) {
|
|
82
|
+
let prefix = describePath.join("::");
|
|
83
|
+
if (prefix === "") {
|
|
84
|
+
return name;
|
|
85
|
+
} else {
|
|
86
|
+
return prefix + "::" + name;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function push(slice, location, timeout, name, body) {
|
|
91
|
+
let status = skipDepth.contents > 0 ? "Skipped" : (
|
|
92
|
+
skipNext.contents ? (skipNext.contents = false, "Skipped") : (
|
|
93
|
+
onlyNext.contents ? (onlyNext.contents = false, "Only") : "Runnable"
|
|
94
|
+
)
|
|
95
|
+
);
|
|
96
|
+
let describePath = describeStack.contents;
|
|
97
|
+
let id = buildId(describePath, name);
|
|
98
|
+
let entry = {
|
|
99
|
+
id: id,
|
|
100
|
+
name: name,
|
|
101
|
+
describePath: describePath,
|
|
102
|
+
slice: slice,
|
|
103
|
+
body: body,
|
|
104
|
+
status: status,
|
|
105
|
+
location: location,
|
|
106
|
+
timeout: timeout
|
|
107
|
+
};
|
|
108
|
+
entries.contents.push(entry);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function pushTodo(name) {
|
|
112
|
+
let describePath = describeStack.contents;
|
|
113
|
+
let id = buildId(describePath, name);
|
|
114
|
+
let entry_body = () => Promise.resolve(Outcome$ReventlessGwt.pass);
|
|
115
|
+
let entry = {
|
|
116
|
+
id: id,
|
|
117
|
+
name: name,
|
|
118
|
+
describePath: describePath,
|
|
119
|
+
slice: undefined,
|
|
120
|
+
body: entry_body,
|
|
121
|
+
status: "Skipped",
|
|
122
|
+
location: undefined,
|
|
123
|
+
timeout: undefined
|
|
124
|
+
};
|
|
125
|
+
entries.contents.push(entry);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function drain() {
|
|
129
|
+
let result = entries.contents;
|
|
130
|
+
entries.contents = [];
|
|
131
|
+
describeStack.contents = [];
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function parseFrame(frame) {
|
|
136
|
+
let trimmed = frame.trim();
|
|
137
|
+
let body = trimmed.startsWith("at ") ? trimmed.slice(3, trimmed.length) : trimmed;
|
|
138
|
+
let i = body.lastIndexOf("(");
|
|
139
|
+
let withLoc;
|
|
140
|
+
if (i !== -1) {
|
|
141
|
+
let rest = body.slice(i + 1 | 0, body.length);
|
|
142
|
+
let j = rest.lastIndexOf(")");
|
|
143
|
+
withLoc = j !== -1 ? rest.slice(0, j) : rest;
|
|
144
|
+
} else {
|
|
145
|
+
withLoc = body;
|
|
146
|
+
}
|
|
147
|
+
let parts = withLoc.split(":");
|
|
148
|
+
let n = parts.length;
|
|
149
|
+
if (n < 3) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
let col = Stdlib_Int.fromString(parts[n - 1 | 0], undefined);
|
|
153
|
+
let line = Stdlib_Int.fromString(parts[n - 2 | 0], undefined);
|
|
154
|
+
let file = parts.slice(0, n - 2 | 0).join(":");
|
|
155
|
+
let file$1 = file.startsWith("file://") ? file.slice(7, file.length) : file;
|
|
156
|
+
let q = file$1.indexOf("?");
|
|
157
|
+
let file$2 = q !== -1 ? file$1.slice(0, q) : file$1;
|
|
158
|
+
if (line !== undefined && col !== undefined) {
|
|
159
|
+
return {
|
|
160
|
+
file: file$2,
|
|
161
|
+
line: line,
|
|
162
|
+
column: col
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function captureLocation(skip) {
|
|
168
|
+
let stack = ((new Error()).stack);
|
|
169
|
+
if (stack === undefined) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
let lines = stack.split("\n");
|
|
173
|
+
let total = lines.length;
|
|
174
|
+
let _i = skip + 1 | 0;
|
|
175
|
+
while (true) {
|
|
176
|
+
let i = _i;
|
|
177
|
+
if (i >= total) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
let frame = lines[i];
|
|
181
|
+
let loc = parseFrame(frame);
|
|
182
|
+
if (loc !== undefined) {
|
|
183
|
+
if (!loc.file.includes("reventless-gwt/src/") && !loc.file.includes("node_modules/")) {
|
|
184
|
+
return loc;
|
|
185
|
+
}
|
|
186
|
+
_i = i + 1 | 0;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
_i = i + 1 | 0;
|
|
190
|
+
continue;
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export {
|
|
195
|
+
active,
|
|
196
|
+
describeStack,
|
|
197
|
+
entries,
|
|
198
|
+
hasOnly,
|
|
199
|
+
skipNext,
|
|
200
|
+
onlyNext,
|
|
201
|
+
skipDepth,
|
|
202
|
+
currentFile,
|
|
203
|
+
activate,
|
|
204
|
+
deactivate,
|
|
205
|
+
isActive,
|
|
206
|
+
setCurrentFile,
|
|
207
|
+
getCurrentFile,
|
|
208
|
+
markSkipNext,
|
|
209
|
+
markOnlyNext,
|
|
210
|
+
pushDescribe,
|
|
211
|
+
buildId,
|
|
212
|
+
push,
|
|
213
|
+
pushTodo,
|
|
214
|
+
drain,
|
|
215
|
+
parseFrame,
|
|
216
|
+
captureLocation,
|
|
217
|
+
}
|
|
218
|
+
/* No side effect */
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Pure helpers mapping a file path to its Reventless component {kind, name}
|
|
2
|
+
// using the folder-name convention (the same vocabulary the plugin generator
|
|
3
|
+
// classifies by). Shared by the discovery `item` annotation (FormatterVsCode)
|
|
4
|
+
// and the src/ component inventory (ComponentScan). No I/O — path strings only.
|
|
5
|
+
|
|
6
|
+
type component = {kind: string, name: string}
|
|
7
|
+
|
|
8
|
+
// The folder→kind vocabulary and body-file suffixes are the single source in
|
|
9
|
+
// `Reventless.ComponentKind` (shared with the plugin generator). Deriving from it
|
|
10
|
+
// means the gwt discovery recognises exactly the folder spellings the generator
|
|
11
|
+
// classifies — including the plural / short forms this file used to miss.
|
|
12
|
+
module Kind = Reventless.ComponentKind
|
|
13
|
+
|
|
14
|
+
// Strip a body-file suffix (longest first) to recover the spec stem, so spec and
|
|
15
|
+
// body files in one folder collapse to the same component name.
|
|
16
|
+
let stripBody = (stem: string) =>
|
|
17
|
+
switch Kind.bodySuffixes->Array.find(suf => String.endsWith(stem, suf)) {
|
|
18
|
+
| Some(suf) => String.slice(stem, ~start=0, ~end=String.length(stem) - String.length(suf))
|
|
19
|
+
| None => stem
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Strip the GWT test marker (`_GWT` / `GwtTest` / `Gwt`) — the three shapes
|
|
23
|
+
// `Discovery.isGwtTestFile` recognises.
|
|
24
|
+
let stripGwt = (stem: string) =>
|
|
25
|
+
if String.endsWith(stem, "_GWT") {
|
|
26
|
+
String.slice(stem, ~start=0, ~end=String.length(stem) - 4)
|
|
27
|
+
} else if String.endsWith(stem, "GwtTest") {
|
|
28
|
+
String.slice(stem, ~start=0, ~end=String.length(stem) - 7)
|
|
29
|
+
} else if String.endsWith(stem, "Gwt") {
|
|
30
|
+
String.slice(stem, ~start=0, ~end=String.length(stem) - 3)
|
|
31
|
+
} else {
|
|
32
|
+
stem
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Last path segment (POSIX paths — the monorepo's tooling normalises to `/`).
|
|
36
|
+
let basename = (path: string) =>
|
|
37
|
+
switch path->String.split("/")->Array.last {
|
|
38
|
+
| Some(s) => s
|
|
39
|
+
| None => path
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Parent folder name (the segment before the filename), if any.
|
|
43
|
+
let parentFolder = (path: string) => {
|
|
44
|
+
let segs = path->String.split("/")
|
|
45
|
+
let n = segs->Array.length
|
|
46
|
+
n >= 2 ? segs->Array.get(n - 2) : None
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Strip the compiled / source extension from a filename.
|
|
50
|
+
let stem = (filename: string) =>
|
|
51
|
+
if String.endsWith(filename, ".res.mjs") {
|
|
52
|
+
String.slice(filename, ~start=0, ~end=String.length(filename) - 8)
|
|
53
|
+
} else if String.endsWith(filename, ".res.js") {
|
|
54
|
+
String.slice(filename, ~start=0, ~end=String.length(filename) - 7)
|
|
55
|
+
} else if String.endsWith(filename, ".resi") {
|
|
56
|
+
String.slice(filename, ~start=0, ~end=String.length(filename) - 5)
|
|
57
|
+
} else if String.endsWith(filename, ".res") {
|
|
58
|
+
String.slice(filename, ~start=0, ~end=String.length(filename) - 4)
|
|
59
|
+
} else {
|
|
60
|
+
filename
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// {kind, name} for a discovered GWT test file, or None if it isn't inside a
|
|
64
|
+
// recognised kind folder. `kind` is the canonical folder name, so a plural
|
|
65
|
+
// folder (`Aggregates/`) reports the same kind as its singular form.
|
|
66
|
+
let componentOfTestFile = (path: string): option<component> =>
|
|
67
|
+
switch parentFolder(path)->Option.flatMap(Kind.folderToKind) {
|
|
68
|
+
| Some(kind) => Some({kind: Kind.folderName(kind), name: path->basename->stem->stripGwt->stripBody})
|
|
69
|
+
| None => None
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// {kind, name} for a src/ component file (spec or body), or None.
|
|
73
|
+
let componentOfSrcFile = (path: string): option<component> =>
|
|
74
|
+
switch parentFolder(path)->Option.flatMap(Kind.folderToKind) {
|
|
75
|
+
| Some(kind) => Some({kind: Kind.folderName(kind), name: path->basename->stem->stripBody})
|
|
76
|
+
| None => None
|
|
77
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
4
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
|
+
import * as ComponentKind$Reventless from "@reventlessdev/reventless-spec/src/components/ComponentKind.res.mjs";
|
|
6
|
+
|
|
7
|
+
function stripBody(stem) {
|
|
8
|
+
let suf = ComponentKind$Reventless.bodySuffixes.find(suf => stem.endsWith(suf));
|
|
9
|
+
if (suf !== undefined) {
|
|
10
|
+
return stem.slice(0, stem.length - suf.length | 0);
|
|
11
|
+
} else {
|
|
12
|
+
return stem;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function stripGwt(stem) {
|
|
17
|
+
if (stem.endsWith("_GWT")) {
|
|
18
|
+
return stem.slice(0, stem.length - 4 | 0);
|
|
19
|
+
} else if (stem.endsWith("GwtTest")) {
|
|
20
|
+
return stem.slice(0, stem.length - 7 | 0);
|
|
21
|
+
} else if (stem.endsWith("Gwt")) {
|
|
22
|
+
return stem.slice(0, stem.length - 3 | 0);
|
|
23
|
+
} else {
|
|
24
|
+
return stem;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function basename(path) {
|
|
29
|
+
let s = Stdlib_Array.last(path.split("/"));
|
|
30
|
+
if (s !== undefined) {
|
|
31
|
+
return s;
|
|
32
|
+
} else {
|
|
33
|
+
return path;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parentFolder(path) {
|
|
38
|
+
let segs = path.split("/");
|
|
39
|
+
let n = segs.length;
|
|
40
|
+
if (n >= 2) {
|
|
41
|
+
return segs[n - 2 | 0];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function stem(filename) {
|
|
46
|
+
if (filename.endsWith(".res.mjs")) {
|
|
47
|
+
return filename.slice(0, filename.length - 8 | 0);
|
|
48
|
+
} else if (filename.endsWith(".res.js")) {
|
|
49
|
+
return filename.slice(0, filename.length - 7 | 0);
|
|
50
|
+
} else if (filename.endsWith(".resi")) {
|
|
51
|
+
return filename.slice(0, filename.length - 5 | 0);
|
|
52
|
+
} else if (filename.endsWith(".res")) {
|
|
53
|
+
return filename.slice(0, filename.length - 4 | 0);
|
|
54
|
+
} else {
|
|
55
|
+
return filename;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function componentOfTestFile(path) {
|
|
60
|
+
let kind = Stdlib_Option.flatMap(parentFolder(path), ComponentKind$Reventless.folderToKind);
|
|
61
|
+
if (kind !== undefined) {
|
|
62
|
+
return {
|
|
63
|
+
kind: ComponentKind$Reventless.folderName(kind),
|
|
64
|
+
name: stripBody(stripGwt(stem(basename(path))))
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function componentOfSrcFile(path) {
|
|
70
|
+
let kind = Stdlib_Option.flatMap(parentFolder(path), ComponentKind$Reventless.folderToKind);
|
|
71
|
+
if (kind !== undefined) {
|
|
72
|
+
return {
|
|
73
|
+
kind: ComponentKind$Reventless.folderName(kind),
|
|
74
|
+
name: stripBody(stem(basename(path)))
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let Kind;
|
|
80
|
+
|
|
81
|
+
export {
|
|
82
|
+
Kind,
|
|
83
|
+
stripBody,
|
|
84
|
+
stripGwt,
|
|
85
|
+
basename,
|
|
86
|
+
parentFolder,
|
|
87
|
+
stem,
|
|
88
|
+
componentOfTestFile,
|
|
89
|
+
componentOfSrcFile,
|
|
90
|
+
}
|
|
91
|
+
/* No side effect */
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Walks each owning package's src/ tree and enumerates its Reventless
|
|
2
|
+
// components by folder convention — including components with no GWT tests.
|
|
3
|
+
// This is the inventory the `components` NDJSON event carries: the basis for
|
|
4
|
+
// the activity-bar Plugin→kind→component tree and "untested slice" coverage.
|
|
5
|
+
// Computed fresh per discovery so it never drifts as components are added.
|
|
6
|
+
|
|
7
|
+
type component = {
|
|
8
|
+
// Absolute directory of the owning package (matches the `dir` in `packages`).
|
|
9
|
+
dir: string,
|
|
10
|
+
kind: string,
|
|
11
|
+
name: string,
|
|
12
|
+
// Absolute paths of the component's `src/` files (spec + body files like
|
|
13
|
+
// `*_Behavior.res` / `*_Mappings.res`), in walk order. Lets the client list
|
|
14
|
+
// spec / implementation files under each component node.
|
|
15
|
+
files: array<string>,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type dirent = {
|
|
19
|
+
name: string,
|
|
20
|
+
@as("isDirectory") _isDirectory: unit => bool,
|
|
21
|
+
@as("isFile") _isFile: unit => bool,
|
|
22
|
+
}
|
|
23
|
+
type readdirOpts = {withFileTypes: bool}
|
|
24
|
+
@module("node:fs/promises")
|
|
25
|
+
external _readdir: (string, readdirOpts) => promise<array<dirent>> = "readdir"
|
|
26
|
+
@module("node:fs") external _existsSync: string => bool = "existsSync"
|
|
27
|
+
@module("node:path") external join: (string, string) => string = "join"
|
|
28
|
+
|
|
29
|
+
let ignoreNames = ["node_modules", ".git", "dist", "lib", ".history"]
|
|
30
|
+
let shouldIgnore = (name: string) => Array.includes(ignoreNames, name)
|
|
31
|
+
|
|
32
|
+
// A directory carrying this sentinel file — and its whole subtree — is pruned
|
|
33
|
+
// from the component scan, matching `Discovery`'s `.gwtignore` convention so
|
|
34
|
+
// the domain tree and the test tree exclude the same generated/vendored trees.
|
|
35
|
+
let gwtIgnoreFile = ".gwtignore"
|
|
36
|
+
let isPruned = (entries: array<dirent>) => entries->Array.some(e => e.name == gwtIgnoreFile)
|
|
37
|
+
|
|
38
|
+
// Source `.res` only — `.res.mjs` / `.res.js` end in `.mjs` / `.js`, and `.resi`
|
|
39
|
+
// in `.resi`, so a plain `.res` suffix test excludes compiled output + interfaces.
|
|
40
|
+
let isSrcResFile = (name: string) => String.endsWith(name, ".res")
|
|
41
|
+
|
|
42
|
+
let rec walk = async (dir: string, acc: array<string>): array<string> => {
|
|
43
|
+
let entries = try {
|
|
44
|
+
await _readdir(dir, {withFileTypes: true})
|
|
45
|
+
} catch {
|
|
46
|
+
| _ => []
|
|
47
|
+
}
|
|
48
|
+
if isPruned(entries) {
|
|
49
|
+
acc
|
|
50
|
+
} else {
|
|
51
|
+
let found = ref(acc)
|
|
52
|
+
for i in 0 to entries->Array.length - 1 {
|
|
53
|
+
let entry = entries->Array.getUnsafe(i)
|
|
54
|
+
if !shouldIgnore(entry.name) {
|
|
55
|
+
let full = join(dir, entry.name)
|
|
56
|
+
if entry._isDirectory() {
|
|
57
|
+
let nested = await walk(full, [])
|
|
58
|
+
found := Array.concat(found.contents, nested)
|
|
59
|
+
} else if entry._isFile() && isSrcResFile(entry.name) {
|
|
60
|
+
found := Array.concat(found.contents, [full])
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
found.contents
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Enumerate components across the given package directories (deduplicated per
|
|
69
|
+
// dir+kind+name, declaration order preserved). Each component accumulates the
|
|
70
|
+
// source files that map to it (spec + body files).
|
|
71
|
+
type orderEntry = {dir: string, kind: string, name: string, key: string}
|
|
72
|
+
|
|
73
|
+
let scan = async (pkgDirs: array<string>): array<component> => {
|
|
74
|
+
let order = []
|
|
75
|
+
let filesByKey = Dict.make()
|
|
76
|
+
for i in 0 to pkgDirs->Array.length - 1 {
|
|
77
|
+
let dir = pkgDirs->Array.getUnsafe(i)
|
|
78
|
+
let srcDir = join(dir, "src")
|
|
79
|
+
if _existsSync(srcDir) {
|
|
80
|
+
let files = await walk(srcDir, [])
|
|
81
|
+
files->Array.forEach(file =>
|
|
82
|
+
switch ComponentMeta.componentOfSrcFile(file) {
|
|
83
|
+
| Some(c) =>
|
|
84
|
+
let key = dir ++ "::" ++ c.kind ++ "::" ++ c.name
|
|
85
|
+
switch filesByKey->Dict.get(key) {
|
|
86
|
+
| Some(existing) => filesByKey->Dict.set(key, Array.concat(existing, [file]))
|
|
87
|
+
| None => {
|
|
88
|
+
filesByKey->Dict.set(key, [file])
|
|
89
|
+
order->Array.push({dir, kind: c.kind, name: c.name, key})
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
| None => ()
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
order->Array.map(o => {
|
|
98
|
+
dir: o.dir,
|
|
99
|
+
kind: o.kind,
|
|
100
|
+
name: o.name,
|
|
101
|
+
files: filesByKey->Dict.get(o.key)->Option.getOr([]),
|
|
102
|
+
})
|
|
103
|
+
}
|