@c9up/helix 0.1.3 → 0.1.5

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.
Files changed (235) hide show
  1. package/dist/cli/coverage/aggregate.d.ts +24 -0
  2. package/dist/cli/coverage/aggregate.d.ts.map +1 -0
  3. package/dist/cli/coverage/aggregate.js +215 -0
  4. package/dist/cli/coverage/aggregate.js.map +1 -0
  5. package/dist/cli/coverage/collect.d.ts +13 -0
  6. package/dist/cli/coverage/collect.d.ts.map +1 -0
  7. package/dist/cli/coverage/collect.js +66 -0
  8. package/dist/cli/coverage/collect.js.map +1 -0
  9. package/dist/cli/coverage/diff/base.d.ts +17 -0
  10. package/dist/cli/coverage/diff/base.d.ts.map +1 -0
  11. package/dist/cli/coverage/diff/base.js +44 -0
  12. package/dist/cli/coverage/diff/base.js.map +1 -0
  13. package/dist/cli/coverage/diff/index.d.ts +25 -0
  14. package/dist/cli/coverage/diff/index.d.ts.map +1 -0
  15. package/dist/cli/coverage/diff/index.js +119 -0
  16. package/dist/cli/coverage/diff/index.js.map +1 -0
  17. package/dist/cli/coverage/diff/overlay.d.ts +19 -0
  18. package/dist/cli/coverage/diff/overlay.d.ts.map +1 -0
  19. package/dist/cli/coverage/diff/overlay.js +58 -0
  20. package/dist/cli/coverage/diff/overlay.js.map +1 -0
  21. package/dist/cli/coverage/diff/parse.d.ts +27 -0
  22. package/dist/cli/coverage/diff/parse.d.ts.map +1 -0
  23. package/dist/cli/coverage/diff/parse.js +105 -0
  24. package/dist/cli/coverage/diff/parse.js.map +1 -0
  25. package/dist/cli/coverage/diff/reporters.d.ts +12 -0
  26. package/dist/cli/coverage/diff/reporters.d.ts.map +1 -0
  27. package/dist/cli/coverage/diff/reporters.js +59 -0
  28. package/dist/cli/coverage/diff/reporters.js.map +1 -0
  29. package/dist/cli/coverage/diff/types.d.ts +46 -0
  30. package/dist/cli/coverage/diff/types.d.ts.map +1 -0
  31. package/dist/cli/coverage/diff/types.js +15 -0
  32. package/dist/cli/coverage/diff/types.js.map +1 -0
  33. package/dist/cli/coverage/filter.d.ts +13 -0
  34. package/dist/cli/coverage/filter.d.ts.map +1 -0
  35. package/dist/cli/coverage/filter.js +60 -0
  36. package/dist/cli/coverage/filter.js.map +1 -0
  37. package/dist/cli/coverage/glob.d.ts +4 -0
  38. package/dist/cli/coverage/glob.d.ts.map +1 -0
  39. package/dist/cli/coverage/glob.js +0 -0
  40. package/dist/cli/coverage/glob.js.map +1 -0
  41. package/dist/cli/coverage/index.d.ts +40 -0
  42. package/dist/cli/coverage/index.d.ts.map +1 -0
  43. package/dist/cli/coverage/index.js +85 -0
  44. package/dist/cli/coverage/index.js.map +1 -0
  45. package/dist/cli/coverage/reporters/json.d.ts +13 -0
  46. package/dist/cli/coverage/reporters/json.d.ts.map +1 -0
  47. package/dist/cli/coverage/reporters/json.js +37 -0
  48. package/dist/cli/coverage/reporters/json.js.map +1 -0
  49. package/dist/cli/coverage/reporters/lcov.d.ts +13 -0
  50. package/dist/cli/coverage/reporters/lcov.d.ts.map +1 -0
  51. package/dist/cli/coverage/reporters/lcov.js +51 -0
  52. package/dist/cli/coverage/reporters/lcov.js.map +1 -0
  53. package/dist/cli/coverage/reporters/text.d.ts +8 -0
  54. package/dist/cli/coverage/reporters/text.d.ts.map +1 -0
  55. package/dist/cli/coverage/reporters/text.js +39 -0
  56. package/dist/cli/coverage/reporters/text.js.map +1 -0
  57. package/dist/cli/coverage/thresholds.d.ts +14 -0
  58. package/dist/cli/coverage/thresholds.d.ts.map +1 -0
  59. package/dist/cli/coverage/thresholds.js +58 -0
  60. package/dist/cli/coverage/thresholds.js.map +1 -0
  61. package/dist/cli/coverage/types.d.ts +118 -0
  62. package/dist/cli/coverage/types.d.ts.map +1 -0
  63. package/dist/cli/coverage/types.js +8 -0
  64. package/dist/cli/coverage/types.js.map +1 -0
  65. package/dist/cli/discover.d.ts +27 -0
  66. package/dist/cli/discover.d.ts.map +1 -0
  67. package/dist/cli/discover.js +142 -0
  68. package/dist/cli/discover.js.map +1 -0
  69. package/dist/cli/native.d.ts +43 -0
  70. package/dist/cli/native.d.ts.map +1 -0
  71. package/dist/cli/native.js +64 -0
  72. package/dist/cli/native.js.map +1 -0
  73. package/dist/cli/pool.d.ts +55 -0
  74. package/dist/cli/pool.d.ts.map +1 -0
  75. package/dist/cli/pool.js +404 -0
  76. package/dist/cli/pool.js.map +1 -0
  77. package/dist/cli/reporter.d.ts +51 -0
  78. package/dist/cli/reporter.d.ts.map +1 -0
  79. package/dist/cli/reporter.js +124 -0
  80. package/dist/cli/reporter.js.map +1 -0
  81. package/dist/cli/run.d.ts +61 -0
  82. package/dist/cli/run.d.ts.map +1 -0
  83. package/dist/cli/run.js +302 -0
  84. package/dist/cli/run.js.map +1 -0
  85. package/dist/cli/summary.d.ts +18 -0
  86. package/dist/cli/summary.d.ts.map +1 -0
  87. package/dist/cli/summary.js +20 -0
  88. package/dist/cli/summary.js.map +1 -0
  89. package/dist/cli/watch/loop.d.ts +26 -0
  90. package/dist/cli/watch/loop.d.ts.map +1 -0
  91. package/dist/cli/watch/loop.js +133 -0
  92. package/dist/cli/watch/loop.js.map +1 -0
  93. package/dist/cli/watch/types.d.ts +22 -0
  94. package/dist/cli/watch/types.d.ts.map +1 -0
  95. package/dist/cli/watch/types.js +6 -0
  96. package/dist/cli/watch/types.js.map +1 -0
  97. package/dist/cli/watch/watcher.d.ts +25 -0
  98. package/dist/cli/watch/watcher.d.ts.map +1 -0
  99. package/dist/cli/watch/watcher.js +122 -0
  100. package/dist/cli/watch/watcher.js.map +1 -0
  101. package/dist/container/index.d.ts +7 -0
  102. package/dist/container/index.d.ts.map +1 -0
  103. package/dist/container/index.js +6 -0
  104. package/dist/container/index.js.map +1 -0
  105. package/{src/container/override.ts → dist/container/override.d.ts} +6 -46
  106. package/dist/container/override.d.ts.map +1 -0
  107. package/dist/container/override.js +67 -0
  108. package/dist/container/override.js.map +1 -0
  109. package/{src/container/spy.ts → dist/container/spy.d.ts} +2 -6
  110. package/dist/container/spy.d.ts.map +1 -0
  111. package/dist/container/spy.js +23 -0
  112. package/dist/container/spy.js.map +1 -0
  113. package/dist/index.d.ts +20 -0
  114. package/dist/index.d.ts.map +1 -0
  115. package/{src/index.ts → dist/index.js} +2 -25
  116. package/dist/index.js.map +1 -0
  117. package/dist/runtime/assertion-error.d.ts +20 -0
  118. package/dist/runtime/assertion-error.d.ts.map +1 -0
  119. package/dist/runtime/assertion-error.js +23 -0
  120. package/dist/runtime/assertion-error.js.map +1 -0
  121. package/dist/runtime/cli-worker.d.ts +25 -0
  122. package/dist/runtime/cli-worker.d.ts.map +1 -0
  123. package/dist/runtime/cli-worker.js +128 -0
  124. package/dist/runtime/cli-worker.js.map +1 -0
  125. package/dist/runtime/equals.d.ts +25 -0
  126. package/dist/runtime/equals.d.ts.map +1 -0
  127. package/dist/runtime/equals.js +343 -0
  128. package/dist/runtime/equals.js.map +1 -0
  129. package/dist/runtime/expect.d.ts +32 -0
  130. package/dist/runtime/expect.d.ts.map +1 -0
  131. package/dist/runtime/expect.js +121 -0
  132. package/dist/runtime/expect.js.map +1 -0
  133. package/{src/runtime/index.ts → dist/runtime/index.d.ts} +5 -27
  134. package/dist/runtime/index.d.ts.map +1 -0
  135. package/dist/runtime/index.js +17 -0
  136. package/dist/runtime/index.js.map +1 -0
  137. package/{src/runtime/lifecycle.ts → dist/runtime/lifecycle.d.ts} +2 -8
  138. package/dist/runtime/lifecycle.d.ts.map +1 -0
  139. package/dist/runtime/lifecycle.js +10 -0
  140. package/dist/runtime/lifecycle.js.map +1 -0
  141. package/dist/runtime/matchers.d.ts +42 -0
  142. package/dist/runtime/matchers.d.ts.map +1 -0
  143. package/dist/runtime/matchers.js +375 -0
  144. package/dist/runtime/matchers.js.map +1 -0
  145. package/dist/runtime/run.d.ts +55 -0
  146. package/dist/runtime/run.d.ts.map +1 -0
  147. package/dist/runtime/run.js +456 -0
  148. package/dist/runtime/run.js.map +1 -0
  149. package/dist/runtime/suite.d.ts +91 -0
  150. package/dist/runtime/suite.d.ts.map +1 -0
  151. package/dist/runtime/suite.js +206 -0
  152. package/dist/runtime/suite.js.map +1 -0
  153. package/dist/runtime/test-context.d.ts +25 -0
  154. package/dist/runtime/test-context.d.ts.map +1 -0
  155. package/dist/runtime/test-context.js +52 -0
  156. package/dist/runtime/test-context.js.map +1 -0
  157. package/dist/runtime/vi/fake-timers.d.ts +48 -0
  158. package/dist/runtime/vi/fake-timers.d.ts.map +1 -0
  159. package/dist/runtime/vi/fake-timers.js +317 -0
  160. package/dist/runtime/vi/fake-timers.js.map +1 -0
  161. package/dist/runtime/vi/index.d.ts +70 -0
  162. package/dist/runtime/vi/index.d.ts.map +1 -0
  163. package/dist/runtime/vi/index.js +209 -0
  164. package/dist/runtime/vi/index.js.map +1 -0
  165. package/dist/runtime/vi/spy.d.ts +54 -0
  166. package/dist/runtime/vi/spy.d.ts.map +1 -0
  167. package/dist/runtime/vi/spy.js +159 -0
  168. package/dist/runtime/vi/spy.js.map +1 -0
  169. package/dist/runtime/vi/spyOn.d.ts +28 -0
  170. package/dist/runtime/vi/spyOn.d.ts.map +1 -0
  171. package/dist/runtime/vi/spyOn.js +121 -0
  172. package/dist/runtime/vi/spyOn.js.map +1 -0
  173. package/dist/runtime/vi/system-time.d.ts +30 -0
  174. package/dist/runtime/vi/system-time.d.ts.map +1 -0
  175. package/dist/runtime/vi/system-time.js +100 -0
  176. package/dist/runtime/vi/system-time.js.map +1 -0
  177. package/dist/runtime/worker.d.ts +26 -0
  178. package/dist/runtime/worker.d.ts.map +1 -0
  179. package/dist/runtime/worker.js +195 -0
  180. package/dist/runtime/worker.js.map +1 -0
  181. package/dist/time/freeze.d.ts +80 -0
  182. package/dist/time/freeze.d.ts.map +1 -0
  183. package/dist/time/freeze.js +184 -0
  184. package/dist/time/freeze.js.map +1 -0
  185. package/{src/time/index.ts → dist/time/index.d.ts} +1 -1
  186. package/dist/time/index.d.ts.map +1 -0
  187. package/dist/time/index.js +15 -0
  188. package/dist/time/index.js.map +1 -0
  189. package/index.darwin-arm64.node +0 -0
  190. package/index.darwin-x64.node +0 -0
  191. package/index.linux-arm64-gnu.node +0 -0
  192. package/index.linux-x64-gnu.node +0 -0
  193. package/index.win32-x64-msvc.node +0 -0
  194. package/package.json +2 -2
  195. package/src/cli/coverage/aggregate.ts +0 -231
  196. package/src/cli/coverage/collect.ts +0 -63
  197. package/src/cli/coverage/diff/base.ts +0 -46
  198. package/src/cli/coverage/diff/index.ts +0 -160
  199. package/src/cli/coverage/diff/overlay.ts +0 -62
  200. package/src/cli/coverage/diff/parse.ts +0 -121
  201. package/src/cli/coverage/diff/reporters.ts +0 -82
  202. package/src/cli/coverage/diff/types.ts +0 -46
  203. package/src/cli/coverage/filter.ts +0 -71
  204. package/src/cli/coverage/glob.ts +0 -0
  205. package/src/cli/coverage/index.ts +0 -126
  206. package/src/cli/coverage/reporters/json.ts +0 -40
  207. package/src/cli/coverage/reporters/lcov.ts +0 -54
  208. package/src/cli/coverage/reporters/text.ts +0 -48
  209. package/src/cli/coverage/thresholds.ts +0 -73
  210. package/src/cli/coverage/types.ts +0 -93
  211. package/src/cli/discover.ts +0 -174
  212. package/src/cli/native.ts +0 -104
  213. package/src/cli/pool.ts +0 -486
  214. package/src/cli/reporter.ts +0 -155
  215. package/src/cli/run.ts +0 -440
  216. package/src/cli/summary.ts +0 -42
  217. package/src/cli/watch/loop.ts +0 -159
  218. package/src/cli/watch/types.ts +0 -22
  219. package/src/cli/watch/watcher.ts +0 -145
  220. package/src/container/index.ts +0 -16
  221. package/src/runtime/assertion-error.ts +0 -38
  222. package/src/runtime/cli-worker.ts +0 -140
  223. package/src/runtime/equals.ts +0 -400
  224. package/src/runtime/expect.ts +0 -173
  225. package/src/runtime/matchers.ts +0 -452
  226. package/src/runtime/run.ts +0 -573
  227. package/src/runtime/suite.ts +0 -310
  228. package/src/runtime/test-context.ts +0 -59
  229. package/src/runtime/vi/fake-timers.ts +0 -410
  230. package/src/runtime/vi/index.ts +0 -254
  231. package/src/runtime/vi/spy.ts +0 -224
  232. package/src/runtime/vi/spyOn.ts +0 -155
  233. package/src/runtime/vi/system-time.ts +0 -121
  234. package/src/runtime/worker.ts +0 -239
  235. package/src/time/freeze.ts +0 -229
@@ -1,573 +0,0 @@
1
- /**
2
- * Execution engine — walks the suite tree collected by `suite.ts`,
3
- * runs each test with its inherited hook chain, and produces a structured
4
- * `FileResult` for the orchestrator.
5
- *
6
- * `.only` handling: if any test or suite is marked `only` anywhere in the
7
- * tree, non-`only` paths are downgraded to `skip` before execution.
8
- */
9
-
10
- import { AssertionError } from "./assertion-error.js";
11
- import type { SuiteNode, TestFn, TestNode } from "./suite.js";
12
- import { withTestContext } from "./test-context.js";
13
-
14
- export interface TestResult {
15
- name: string;
16
- fullName: string;
17
- status: "pass" | "fail" | "skip" | "todo";
18
- durationMs: number;
19
- error?: SerializedError;
20
- }
21
-
22
- export interface SuiteResult {
23
- name: string;
24
- fullName: string;
25
- children: Array<SuiteResult | TestResult>;
26
- status: "pass" | "fail" | "skip";
27
- durationMs: number;
28
- /** Hook errors attributed to this suite (beforeAll/afterAll). */
29
- hookErrors: SerializedError[];
30
- }
31
-
32
- export interface SerializedError {
33
- name: string;
34
- message: string;
35
- stack?: string;
36
- actual?: unknown;
37
- expected?: unknown;
38
- operator?: string;
39
- }
40
-
41
- export interface FileResult {
42
- file: string;
43
- suites: SuiteResult[];
44
- tests: TestResult[];
45
- totals: {
46
- pass: number;
47
- fail: number;
48
- skip: number;
49
- todo: number;
50
- };
51
- durationMs: number;
52
- }
53
-
54
- export interface ExecuteOptions {
55
- /**
56
- * Per-test timeout in ms. `0` disables (default). When exceeded, the
57
- * test is marked failed with a timeout error; the hanging promise is not
58
- * awaited further.
59
- */
60
- timeoutMs?: number;
61
- }
62
-
63
- function serializeError(err: unknown): SerializedError {
64
- if (err instanceof AssertionError) {
65
- return {
66
- name: err.name,
67
- message: err.message,
68
- stack: err.stack,
69
- actual: err.actual,
70
- expected: err.expected,
71
- operator: err.operator,
72
- };
73
- }
74
- if (err instanceof Error) {
75
- return { name: err.name, message: err.message, stack: err.stack };
76
- }
77
- return { name: "NonError", message: String(err) };
78
- }
79
-
80
- function hasOnly(node: SuiteNode | TestNode): boolean {
81
- if (node.mode === "only") return true;
82
- if (node.kind === "test") return false;
83
- return node.children.some(hasOnly);
84
- }
85
-
86
- function ancestorHasOnly(node: SuiteNode | TestNode): boolean {
87
- let cursor: SuiteNode | undefined = node.parent;
88
- while (cursor) {
89
- if (cursor.mode === "only") return true;
90
- cursor = cursor.parent;
91
- }
92
- return false;
93
- }
94
-
95
- function pathLeadsToOnly(node: SuiteNode | TestNode): boolean {
96
- if (node.mode === "only") return true;
97
- if (ancestorHasOnly(node)) return true;
98
- if (node.kind === "suite") {
99
- return node.children.some(hasOnly);
100
- }
101
- return false;
102
- }
103
-
104
- function collectHookChain(
105
- leaf: SuiteNode,
106
- type: "beforeEach" | "afterEach",
107
- ): TestFn[] {
108
- // beforeEach: outermost first; afterEach: innermost first.
109
- const suites: SuiteNode[] = [];
110
- let cursor: SuiteNode | undefined = leaf;
111
- while (cursor) {
112
- suites.push(cursor);
113
- cursor = cursor.parent;
114
- }
115
- const ordered = type === "beforeEach" ? [...suites].reverse() : suites;
116
- const chain: TestFn[] = [];
117
- for (const s of ordered) {
118
- for (const h of s.hooks) {
119
- if (h.type === type) chain.push(h.fn);
120
- }
121
- }
122
- return chain;
123
- }
124
-
125
- async function runHooks(hooks: TestFn[]): Promise<SerializedError | undefined> {
126
- for (const h of hooks) {
127
- try {
128
- await h();
129
- } catch (err) {
130
- return serializeError(err);
131
- }
132
- }
133
- return undefined;
134
- }
135
-
136
- function joinName(parent: string, name: string): string {
137
- if (!parent) return name;
138
- if (!name) return parent;
139
- return `${parent} > ${name}`;
140
- }
141
-
142
- function combineErrors(
143
- primary: SerializedError | undefined,
144
- secondary: SerializedError | undefined,
145
- ): SerializedError | undefined {
146
- if (!primary) return secondary;
147
- if (!secondary) return primary;
148
- return {
149
- name: primary.name,
150
- message: `${primary.message}\n+ teardown also failed: ${secondary.message}`,
151
- stack: primary.stack,
152
- actual: primary.actual,
153
- expected: primary.expected,
154
- operator: primary.operator,
155
- };
156
- }
157
-
158
- async function withTimeout<T>(
159
- p: Promise<T> | T,
160
- ms: number,
161
- label: string,
162
- ): Promise<T> {
163
- if (ms <= 0) return await p;
164
- let timer: NodeJS.Timeout | undefined;
165
- const timeout = new Promise<T>((_, reject) => {
166
- timer = setTimeout(() => {
167
- reject(new Error(`${label} exceeded ${ms}ms timeout`));
168
- }, ms);
169
- // Don't keep the event loop alive just for this watchdog.
170
- timer.unref?.();
171
- });
172
- try {
173
- return await Promise.race([Promise.resolve(p), timeout]);
174
- } finally {
175
- if (timer) clearTimeout(timer);
176
- }
177
- }
178
-
179
- interface RunCtx {
180
- onlyActive: boolean;
181
- flatTests: TestResult[];
182
- timeoutMs: number;
183
- }
184
-
185
- async function runTest(
186
- node: TestNode,
187
- parentFullName: string,
188
- ctx: RunCtx,
189
- ): Promise<TestResult> {
190
- const fullName = joinName(parentFullName, node.name);
191
- if (node.mode === "todo") {
192
- const r: TestResult = {
193
- name: node.name,
194
- fullName,
195
- status: "todo",
196
- durationMs: 0,
197
- };
198
- ctx.flatTests.push(r);
199
- return r;
200
- }
201
- if (node.mode === "skip" || (ctx.onlyActive && !pathLeadsToOnly(node))) {
202
- const r: TestResult = {
203
- name: node.name,
204
- fullName,
205
- status: "skip",
206
- durationMs: 0,
207
- };
208
- ctx.flatTests.push(r);
209
- return r;
210
- }
211
- const before = collectHookChain(node.parent, "beforeEach");
212
- const after = collectHookChain(node.parent, "afterEach");
213
- const start = Date.now();
214
- // Wrap the whole cycle (beforeEach + body + afterEach) in a per-test
215
- // frame so test-scoped cleanups (e.g. container overrides registered
216
- // via `helix.override`) drain at the right time. Frame closes AFTER
217
- // afterEach so user teardown can still observe test-scoped state.
218
- const r = await withTestContext<TestResult>(async () => {
219
- const beforeErr = await runHooks(before);
220
- if (beforeErr) {
221
- const afterErrBE = await runHooks(after);
222
- return {
223
- name: node.name,
224
- fullName,
225
- status: "fail",
226
- durationMs: Date.now() - start,
227
- error: combineErrors(beforeErr, afterErrBE),
228
- };
229
- }
230
- let testErr: SerializedError | undefined;
231
- try {
232
- const result = node.fn?.();
233
- if (
234
- result &&
235
- typeof (result as PromiseLike<unknown>).then === "function"
236
- ) {
237
- await withTimeout(
238
- result as Promise<unknown>,
239
- ctx.timeoutMs,
240
- `test "${fullName}"`,
241
- );
242
- }
243
- } catch (err) {
244
- testErr = serializeError(err);
245
- }
246
- const afterErr = await runHooks(after);
247
- const finalErr = combineErrors(testErr, afterErr);
248
- return {
249
- name: node.name,
250
- fullName,
251
- status: finalErr ? "fail" : "pass",
252
- durationMs: Date.now() - start,
253
- error: finalErr,
254
- };
255
- });
256
- ctx.flatTests.push(r);
257
- return r;
258
- }
259
-
260
- /**
261
- * Recursively mark every descendant test as failed due to an ancestor
262
- * hook error. Ensures `flatTests` is complete and reporters can navigate
263
- * the whole tree.
264
- */
265
- function attributeHookFailure(
266
- node: SuiteNode,
267
- parentFullName: string,
268
- err: SerializedError,
269
- ctx: RunCtx,
270
- ): Array<SuiteResult | TestResult> {
271
- const children: Array<SuiteResult | TestResult> = [];
272
- const fullName = joinName(parentFullName, node.name);
273
- for (const child of node.children) {
274
- if (child.kind === "test") {
275
- const r: TestResult = {
276
- name: child.name,
277
- fullName: joinName(fullName, child.name),
278
- status: child.mode === "todo" ? "todo" : "fail",
279
- durationMs: 0,
280
- error: child.mode === "todo" ? undefined : err,
281
- };
282
- children.push(r);
283
- ctx.flatTests.push(r);
284
- } else {
285
- const innerChildren = attributeHookFailure(child, fullName, err, ctx);
286
- children.push({
287
- name: child.name,
288
- fullName: joinName(fullName, child.name),
289
- children: innerChildren,
290
- status: "fail",
291
- durationMs: 0,
292
- hookErrors: [],
293
- });
294
- }
295
- }
296
- return children;
297
- }
298
-
299
- async function runSuite(
300
- node: SuiteNode,
301
- parentFullName: string,
302
- ctx: RunCtx,
303
- ): Promise<SuiteResult> {
304
- const fullName = joinName(parentFullName, node.name);
305
- const start = Date.now();
306
-
307
- // `.skip` takes precedence over descendant `.only` — matches Vitest.
308
- // `.todo` likewise.
309
- const skipEntire =
310
- node.mode === "skip" ||
311
- node.mode === "todo" ||
312
- (ctx.onlyActive && !pathLeadsToOnly(node));
313
- if (skipEntire) return runSkippedSuite(node, fullName, ctx);
314
-
315
- const hookErrors: SerializedError[] = [];
316
-
317
- // beforeAll: when one throws, every descendant test inherits the failure
318
- // and the children list is replaced with those attributed results.
319
- const attributed = await runBeforeAllHooks(
320
- node,
321
- parentFullName,
322
- ctx,
323
- hookErrors,
324
- );
325
- const beforeAllFailed = attributed !== null;
326
- const children: Array<SuiteResult | TestResult> = attributed ?? [];
327
-
328
- if (!beforeAllFailed) {
329
- for (const child of node.children) {
330
- children.push(
331
- child.kind === "test"
332
- ? await runTest(child, fullName, ctx)
333
- : await runSuite(child, fullName, ctx),
334
- );
335
- }
336
- }
337
-
338
- await runAfterAllHooks(node, hookErrors);
339
-
340
- // Surface afterAll failures as a synthetic test so `totals.fail` reflects
341
- // them and CI exits nonzero.
342
- if (!beforeAllFailed && hookErrors.length > 0) {
343
- const synthetic: TestResult = {
344
- name: "afterAll",
345
- fullName: joinName(fullName, "afterAll"),
346
- status: "fail",
347
- durationMs: 0,
348
- error: hookErrors[hookErrors.length - 1],
349
- };
350
- children.push(synthetic);
351
- ctx.flatTests.push(synthetic);
352
- }
353
-
354
- return {
355
- name: node.name,
356
- fullName,
357
- children,
358
- status: suiteStatus(children, hookErrors),
359
- durationMs: Date.now() - start,
360
- hookErrors,
361
- };
362
- }
363
-
364
- /** Build skip/todo results for an entirely-skipped suite (no hooks run). */
365
- async function runSkippedSuite(
366
- node: SuiteNode,
367
- fullName: string,
368
- ctx: RunCtx,
369
- ): Promise<SuiteResult> {
370
- const children: Array<SuiteResult | TestResult> = [];
371
- for (const child of node.children) {
372
- if (child.kind === "test") {
373
- const r: TestResult = {
374
- name: child.name,
375
- fullName: joinName(fullName, child.name),
376
- status: child.mode === "todo" ? "todo" : "skip",
377
- durationMs: 0,
378
- };
379
- children.push(r);
380
- ctx.flatTests.push(r);
381
- } else {
382
- children.push(await runSuiteSkip(child, fullName, ctx));
383
- }
384
- }
385
- return {
386
- name: node.name,
387
- fullName,
388
- children,
389
- status: "skip",
390
- durationMs: 0,
391
- hookErrors: [],
392
- };
393
- }
394
-
395
- /**
396
- * Run the suite's `beforeAll` hooks. On the first failure, push the serialized
397
- * error to `hookErrors` and return the descendant tests attributed with that
398
- * failure; return `null` when all hooks pass.
399
- */
400
- async function runBeforeAllHooks(
401
- node: SuiteNode,
402
- parentFullName: string,
403
- ctx: RunCtx,
404
- hookErrors: SerializedError[],
405
- ): Promise<Array<SuiteResult | TestResult> | null> {
406
- for (const h of node.hooks) {
407
- if (h.type !== "beforeAll") continue;
408
- try {
409
- await h.fn();
410
- } catch (err) {
411
- const serialized = serializeError(err);
412
- hookErrors.push(serialized);
413
- // Attribute the error to every descendant test (direct AND nested).
414
- return attributeHookFailure(node, parentFullName, serialized, ctx);
415
- }
416
- }
417
- return null;
418
- }
419
-
420
- /**
421
- * Run `afterAll` hooks unconditionally so partial setup from a failed beforeAll
422
- * can be released. Errors are captured into `hookErrors`, not thrown.
423
- */
424
- async function runAfterAllHooks(
425
- node: SuiteNode,
426
- hookErrors: SerializedError[],
427
- ): Promise<void> {
428
- for (const h of node.hooks) {
429
- if (h.type !== "afterAll") continue;
430
- try {
431
- await h.fn();
432
- } catch (err) {
433
- hookErrors.push(serializeError(err));
434
- }
435
- }
436
- }
437
-
438
- /** Roll a suite's child statuses + hook errors up into its own status. */
439
- function suiteStatus(
440
- children: Array<SuiteResult | TestResult>,
441
- hookErrors: SerializedError[],
442
- ): "fail" | "skip" | "pass" {
443
- if (hookErrors.length > 0 || children.some((c) => c.status === "fail")) {
444
- return "fail";
445
- }
446
- if (children.length > 0 && children.every((c) => c.status === "skip")) {
447
- return "skip";
448
- }
449
- return "pass";
450
- }
451
-
452
- async function runSuiteSkip(
453
- node: SuiteNode,
454
- parentFullName: string,
455
- ctx: RunCtx,
456
- ): Promise<SuiteResult> {
457
- const fullName = joinName(parentFullName, node.name);
458
- const children: Array<SuiteResult | TestResult> = [];
459
- for (const child of node.children) {
460
- if (child.kind === "test") {
461
- const r: TestResult = {
462
- name: child.name,
463
- fullName: joinName(fullName, child.name),
464
- status: child.mode === "todo" ? "todo" : "skip",
465
- durationMs: 0,
466
- };
467
- children.push(r);
468
- ctx.flatTests.push(r);
469
- } else {
470
- children.push(await runSuiteSkip(child, fullName, ctx));
471
- }
472
- }
473
- return {
474
- name: node.name,
475
- fullName,
476
- children,
477
- status: "skip",
478
- durationMs: 0,
479
- hookErrors: [],
480
- };
481
- }
482
-
483
- export async function executeRoot(
484
- root: SuiteNode,
485
- file: string,
486
- options: ExecuteOptions = {},
487
- ): Promise<FileResult> {
488
- const start = Date.now();
489
- const onlyActive = root.children.some(hasOnly);
490
- const ctx: RunCtx = {
491
- onlyActive,
492
- flatTests: [],
493
- timeoutMs: options.timeoutMs ?? 0,
494
- };
495
- const suites: SuiteResult[] = [];
496
-
497
- // Root-level beforeAll: run once before anything, root-level afterAll:
498
- // once after everything. Errors attribute to a synthetic test entry so
499
- // they show up in totals.
500
- const rootHookErrors: SerializedError[] = [];
501
- let rootBeforeAllFailed = false;
502
- for (const h of root.hooks) {
503
- if (h.type !== "beforeAll") continue;
504
- try {
505
- await h.fn();
506
- } catch (err) {
507
- const serialized = serializeError(err);
508
- rootHookErrors.push(serialized);
509
- const synthetic: TestResult = {
510
- name: "beforeAll",
511
- fullName: "beforeAll",
512
- status: "fail",
513
- durationMs: 0,
514
- error: serialized,
515
- };
516
- ctx.flatTests.push(synthetic);
517
- rootBeforeAllFailed = true;
518
- break;
519
- }
520
- }
521
-
522
- if (!rootBeforeAllFailed) {
523
- for (const child of root.children) {
524
- if (child.kind === "test") {
525
- const tr = await runTest(child, "", ctx);
526
- suites.push({
527
- name: "",
528
- fullName: "",
529
- children: [tr],
530
- status:
531
- tr.status === "fail"
532
- ? "fail"
533
- : tr.status === "pass"
534
- ? "pass"
535
- : "skip",
536
- durationMs: tr.durationMs,
537
- hookErrors: [],
538
- });
539
- } else {
540
- suites.push(await runSuite(child, "", ctx));
541
- }
542
- }
543
- }
544
-
545
- // Root afterAll — always try.
546
- for (const h of root.hooks) {
547
- if (h.type !== "afterAll") continue;
548
- try {
549
- await h.fn();
550
- } catch (err) {
551
- const serialized = serializeError(err);
552
- rootHookErrors.push(serialized);
553
- const synthetic: TestResult = {
554
- name: "afterAll",
555
- fullName: "afterAll",
556
- status: "fail",
557
- durationMs: 0,
558
- error: serialized,
559
- };
560
- ctx.flatTests.push(synthetic);
561
- }
562
- }
563
-
564
- const totals = { pass: 0, fail: 0, skip: 0, todo: 0 };
565
- for (const t of ctx.flatTests) totals[t.status] += 1;
566
- return {
567
- file,
568
- suites,
569
- tests: ctx.flatTests,
570
- totals,
571
- durationMs: Date.now() - start,
572
- };
573
- }