@remix-run/test 0.2.0 → 0.4.0

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 (79) hide show
  1. package/README.md +43 -44
  2. package/dist/app/client/entry.js +4 -0
  3. package/dist/app/server.d.ts.map +1 -1
  4. package/dist/app/server.js +10 -10
  5. package/dist/cli.d.ts +30 -0
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +87 -23
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/lib/config.d.ts +55 -21
  11. package/dist/lib/config.d.ts.map +1 -1
  12. package/dist/lib/config.js +82 -33
  13. package/dist/lib/context.d.ts +5 -5
  14. package/dist/lib/coverage-loader.js +2 -2
  15. package/dist/lib/coverage.js +1 -1
  16. package/dist/lib/fake-timers.d.ts +39 -0
  17. package/dist/lib/fake-timers.d.ts.map +1 -1
  18. package/dist/lib/fake-timers.js +27 -8
  19. package/dist/lib/framework.d.ts +12 -6
  20. package/dist/lib/framework.d.ts.map +1 -1
  21. package/dist/lib/framework.js +24 -12
  22. package/dist/lib/import-module.d.ts.map +1 -1
  23. package/dist/lib/import-module.js +13 -3
  24. package/dist/lib/reporters/dot.d.ts.map +1 -1
  25. package/dist/lib/reporters/dot.js +10 -0
  26. package/dist/lib/reporters/files.d.ts.map +1 -1
  27. package/dist/lib/reporters/files.js +10 -0
  28. package/dist/lib/reporters/results.d.ts +1 -1
  29. package/dist/lib/reporters/results.d.ts.map +1 -1
  30. package/dist/lib/reporters/spec.d.ts.map +1 -1
  31. package/dist/lib/reporters/spec.js +10 -0
  32. package/dist/lib/reporters/tap.d.ts.map +1 -1
  33. package/dist/lib/reporters/tap.js +10 -0
  34. package/dist/lib/runner-browser.d.ts.map +1 -1
  35. package/dist/lib/runner-browser.js +40 -2
  36. package/dist/lib/runner.d.ts +18 -1
  37. package/dist/lib/runner.d.ts.map +1 -1
  38. package/dist/lib/runner.js +187 -38
  39. package/dist/lib/worker-e2e-file.d.ts +11 -0
  40. package/dist/lib/worker-e2e-file.d.ts.map +1 -0
  41. package/dist/lib/worker-e2e-file.js +69 -0
  42. package/dist/lib/worker-e2e.js +11 -47
  43. package/dist/lib/worker-process.d.ts +2 -0
  44. package/dist/lib/worker-process.d.ts.map +1 -0
  45. package/dist/lib/worker-process.js +55 -0
  46. package/dist/lib/worker-results.d.ts +3 -0
  47. package/dist/lib/worker-results.d.ts.map +1 -0
  48. package/dist/lib/worker-results.js +20 -0
  49. package/dist/lib/worker-server.d.ts +10 -0
  50. package/dist/lib/worker-server.d.ts.map +1 -0
  51. package/dist/lib/worker-server.js +112 -0
  52. package/dist/lib/worker.js +6 -55
  53. package/package.json +5 -5
  54. package/src/app/client/entry.ts +4 -0
  55. package/src/app/server.ts +11 -10
  56. package/src/cli.ts +121 -28
  57. package/src/index.ts +1 -1
  58. package/src/lib/config.ts +144 -58
  59. package/src/lib/context.ts +5 -5
  60. package/src/lib/coverage-loader.ts +2 -2
  61. package/src/lib/coverage.ts +1 -1
  62. package/src/lib/fake-timers.ts +65 -8
  63. package/src/lib/framework.ts +53 -36
  64. package/src/lib/import-module.ts +14 -3
  65. package/src/lib/reporters/dot.ts +9 -0
  66. package/src/lib/reporters/files.ts +9 -0
  67. package/src/lib/reporters/results.ts +1 -1
  68. package/src/lib/reporters/spec.ts +9 -0
  69. package/src/lib/reporters/tap.ts +9 -0
  70. package/src/lib/runner-browser.ts +46 -2
  71. package/src/lib/runner.ts +253 -50
  72. package/src/lib/ts-transform.ts +1 -1
  73. package/src/lib/worker-e2e-file.ts +98 -0
  74. package/src/lib/worker-e2e.ts +14 -51
  75. package/src/lib/worker-process.ts +69 -0
  76. package/src/lib/worker-results.ts +22 -0
  77. package/src/lib/worker-server.ts +123 -0
  78. package/src/lib/worker.ts +7 -47
  79. package/tsconfig.json +6 -3
@@ -40,19 +40,23 @@ const cliOptions = {
40
40
  },
41
41
  'glob.browser': {
42
42
  type: 'string',
43
- description: 'Glob pattern for browser test files',
43
+ multiple: true,
44
+ description: 'Glob pattern(s) for browser test files',
44
45
  },
45
46
  'glob.e2e': {
46
47
  type: 'string',
47
- description: 'Glob pattern for E2E test files',
48
+ multiple: true,
49
+ description: 'Glob pattern(s) for E2E test files',
48
50
  },
49
51
  'glob.exclude': {
50
52
  type: 'string',
51
- description: 'Glob pattern for paths to exclude from discovery',
53
+ multiple: true,
54
+ description: 'Glob pattern(s) for paths to exclude from discovery',
52
55
  },
53
56
  'glob.test': {
54
57
  type: 'string',
55
- description: 'Glob pattern for all test files',
58
+ multiple: true,
59
+ description: 'Glob pattern(s) for all test files',
56
60
  },
57
61
  concurrency: {
58
62
  type: 'string',
@@ -109,7 +113,12 @@ const cliOptions = {
109
113
  project: {
110
114
  type: 'string',
111
115
  short: 'p',
112
- description: 'Filter to a specific Playwright project (comma-separated)',
116
+ multiple: true,
117
+ description: 'Filter to specific Playwright project(s)',
118
+ },
119
+ pool: {
120
+ type: 'string',
121
+ description: 'Pool used to run server and E2E test files: forks, threads (default: forks)',
113
122
  },
114
123
  reporter: {
115
124
  type: 'string',
@@ -119,7 +128,8 @@ const cliOptions = {
119
128
  type: {
120
129
  type: 'string',
121
130
  short: 't',
122
- description: 'Comma-separated test types to run (default: server,browser,e2e)',
131
+ multiple: true,
132
+ description: 'Test types to run (default: server, browser, e2e)',
123
133
  },
124
134
  watch: {
125
135
  type: 'boolean',
@@ -143,16 +153,17 @@ const defaultValues = {
143
153
  functions: undefined,
144
154
  },
145
155
  glob: {
146
- test: '**/*.test{,.e2e,.browser}.{ts,tsx}',
147
- browser: '**/*.test.browser.{ts,tsx}',
148
- e2e: '**/*.test.e2e.{ts,tsx}',
149
- exclude: 'node_modules/**',
156
+ test: ['**/*.test{,.e2e,.browser}.{ts,tsx}'],
157
+ browser: ['**/*.test.browser.{ts,tsx}'],
158
+ e2e: ['**/*.test.e2e.{ts,tsx}'],
159
+ exclude: ['node_modules/**'],
150
160
  },
151
- reporter: process.env.CI === 'true' ? 'files' : 'spec',
152
- type: 'server,browser,e2e',
153
- setup: undefined,
161
+ pool: 'forks',
154
162
  playwrightConfig: undefined,
155
163
  project: undefined,
164
+ reporter: process.env.CI === 'true' ? 'files' : 'spec',
165
+ setup: undefined,
166
+ type: ['server', 'browser', 'e2e'],
156
167
  watch: false,
157
168
  };
158
169
  export async function loadConfig(args = process.argv.slice(2), cwd = process.cwd()) {
@@ -161,12 +172,21 @@ export async function loadConfig(args = process.argv.slice(2), cwd = process.cwd
161
172
  let config = resolveConfig(fileConfig, parsed);
162
173
  return config;
163
174
  }
175
+ /**
176
+ * Returns the formatted `remix-test --help` text. Useful for embedding the
177
+ * runner's CLI options in higher-level tooling.
178
+ *
179
+ * @param _target Output stream the help text will be written to. Reserved
180
+ * for future use (e.g. width-aware formatting); currently
181
+ * unused.
182
+ * @returns The help text as a single string ready to write to a stream.
183
+ */
164
184
  export function getRemixTestHelpText(_target = process.stdout) {
165
185
  let lines = [
166
- 'Usage: remix-test [glob] [options]',
186
+ 'Usage: remix-test [glob...] [options]',
167
187
  '',
168
188
  'Arguments:',
169
- ` glob Glob pattern for test files (default: "${defaultValues.glob.test}")`,
189
+ ` glob Glob pattern(s) for test files (default: "${defaultValues.glob.test.join(', ')}")`,
170
190
  '',
171
191
  'Options:',
172
192
  ];
@@ -181,17 +201,25 @@ export function getRemixTestHelpText(_target = process.stdout) {
181
201
  function parseCliArgs(args) {
182
202
  return util.parseArgs({ args, options: cliOptions, allowPositionals: true });
183
203
  }
204
+ function toArray(value) {
205
+ return Array.isArray(value) ? [...value] : [value];
206
+ }
207
+ function toCommaSeparatedArray(value) {
208
+ return toArray(value).flatMap((item) => item
209
+ .split(',')
210
+ .map((part) => part.trim())
211
+ .filter(Boolean));
212
+ }
184
213
  function resolveConfig(fileConfig, { values: cliValues, positionals }) {
185
214
  let fileCoverage = typeof fileConfig.coverage === 'boolean' ? {} : fileConfig.coverage || {};
186
215
  return {
187
216
  glob: {
188
- test: positionals[0] ??
189
- cliValues['glob.test'] ??
190
- fileConfig.glob?.test ??
191
- defaultValues.glob.test,
192
- browser: cliValues['glob.browser'] ?? fileConfig.glob?.browser ?? defaultValues.glob.browser,
193
- e2e: cliValues['glob.e2e'] ?? fileConfig.glob?.e2e ?? defaultValues.glob.e2e,
194
- exclude: cliValues['glob.exclude'] ?? fileConfig.glob?.exclude ?? defaultValues.glob.exclude,
217
+ test: toArray(positionals.length > 0
218
+ ? positionals
219
+ : (cliValues['glob.test'] ?? fileConfig.glob?.test ?? defaultValues.glob.test)),
220
+ browser: toArray(cliValues['glob.browser'] ?? fileConfig.glob?.browser ?? defaultValues.glob.browser),
221
+ e2e: toArray(cliValues['glob.e2e'] ?? fileConfig.glob?.e2e ?? defaultValues.glob.e2e),
222
+ exclude: toArray(cliValues['glob.exclude'] ?? fileConfig.glob?.exclude ?? defaultValues.glob.exclude),
195
223
  },
196
224
  browser: {
197
225
  echo: cliValues['browser.echo'] ?? fileConfig.browser?.echo ?? defaultValues.browser.echo,
@@ -201,12 +229,18 @@ function resolveConfig(fileConfig, { values: cliValues, positionals }) {
201
229
  coverage: cliValues.coverage === true || !!fileConfig.coverage
202
230
  ? {
203
231
  dir: cliValues['coverage.dir'] ?? fileCoverage.dir ?? defaultValues.coverage.dir,
204
- include: cliValues['coverage.include'] ??
205
- fileCoverage.include ??
206
- defaultValues.coverage.include,
207
- exclude: cliValues['coverage.exclude'] ??
208
- fileCoverage.exclude ??
209
- defaultValues.coverage.exclude,
232
+ include: (() => {
233
+ let raw = cliValues['coverage.include'] ??
234
+ fileCoverage.include ??
235
+ defaultValues.coverage.include;
236
+ return raw === undefined ? undefined : toArray(raw);
237
+ })(),
238
+ exclude: (() => {
239
+ let raw = cliValues['coverage.exclude'] ??
240
+ fileCoverage.exclude ??
241
+ defaultValues.coverage.exclude;
242
+ return raw === undefined ? undefined : toArray(raw);
243
+ })(),
210
244
  statements: cliValues['coverage.statements'] !== undefined
211
245
  ? Number(cliValues['coverage.statements'])
212
246
  : fileCoverage.statements !== undefined
@@ -231,12 +265,22 @@ function resolveConfig(fileConfig, { values: cliValues, positionals }) {
231
265
  : undefined,
232
266
  setup: cliValues.setup ?? fileConfig.setup ?? defaultValues.setup,
233
267
  playwrightConfig: cliValues.playwrightConfig ?? fileConfig.playwrightConfig ?? defaultValues.playwrightConfig,
234
- project: cliValues.project ?? fileConfig.project ?? defaultValues.project,
268
+ pool: resolvePool(cliValues.pool ?? fileConfig.pool ?? defaultValues.pool),
269
+ project: (() => {
270
+ let raw = cliValues.project ?? fileConfig.project ?? defaultValues.project;
271
+ return raw === undefined ? undefined : toCommaSeparatedArray(raw);
272
+ })(),
235
273
  reporter: cliValues.reporter ?? fileConfig.reporter ?? defaultValues.reporter,
236
- type: cliValues.type ?? fileConfig.type ?? defaultValues.type,
274
+ type: toCommaSeparatedArray(cliValues.type ?? fileConfig.type ?? defaultValues.type),
237
275
  watch: cliValues.watch ?? fileConfig.watch ?? defaultValues.watch,
238
276
  };
239
277
  }
278
+ function resolvePool(value) {
279
+ if (value === 'forks' || value === 'threads') {
280
+ return value;
281
+ }
282
+ throw new Error(`Unsupported test pool "${value}". Supported pools are: forks, threads`);
283
+ }
240
284
  async function loadConfigFile(configPath, cwd) {
241
285
  let candidates = configPath
242
286
  ? [path.resolve(cwd, configPath)]
@@ -244,12 +288,17 @@ async function loadConfigFile(configPath, cwd) {
244
288
  for (let candidate of candidates) {
245
289
  try {
246
290
  await fsp.access(candidate);
247
- let mod = await importModule(candidate, import.meta);
248
- return mod.default ?? mod;
249
291
  }
250
292
  catch {
251
- // not found or failed to load — try next
293
+ // not found — try the next candidate
294
+ continue;
252
295
  }
296
+ // The file exists; let import errors propagate rather than silently
297
+ // falling through to defaults — that masking is what hid "Windows
298
+ // absolute paths aren't valid ESM specifiers" by classifying every
299
+ // browser test as a server test.
300
+ let mod = await importModule(candidate, import.meta);
301
+ return mod.default ?? mod;
253
302
  }
254
303
  return {};
255
304
  }
@@ -13,7 +13,7 @@ export interface TestServer {
13
13
  close(): Promise<void>;
14
14
  }
15
15
  /**
16
- * Test Context providing utilities for testing via remix-test. The context is
16
+ * Test Context providing utilities for testing via `remix-test`. The context is
17
17
  * passed as the first argument to the {@link test}/{@link it} functions.
18
18
  *
19
19
  * @example
@@ -33,8 +33,8 @@ export interface TestContext {
33
33
  */
34
34
  after(fn: () => void): void;
35
35
  /**
36
- * Mock tracker for the current test. Mirrors the shape of Node's
37
- * `t.mock`. Method mocks created here are auto-restored on test completion.
36
+ * Mock tracker for the current test using {@link mock}. Mirrors the shape of Node's
37
+ * `t.mock`. Method mocks created via `t.mock` are auto-restored on test completion.
38
38
  */
39
39
  mock: {
40
40
  /**
@@ -67,8 +67,8 @@ export interface TestContext {
67
67
  /**
68
68
  * Wires a running test server up to a Playwright page so the test can drive
69
69
  * it. The server is closed automatically when the test ends. Pair with
70
- * `createTestServer` from `@remix-run/node-fetch-server/test` (or any other
71
- * source of a `{ baseUrl, close }` handle) to spin up the server first.
70
+ * {@link createTestServer} from `@remix-run/node-fetch-server/test` to spin
71
+ * up the server.
72
72
  *
73
73
  * @param server - The running server the page should target
74
74
  * @returns A `Page` whose `baseURL` is set to `server.baseUrl`.
@@ -3,8 +3,8 @@ import { fileURLToPath } from 'node:url';
3
3
  import { transformTypeScript } from "./ts-transform.js";
4
4
  // Custom ESM loader hook for TypeScript files.
5
5
  //
6
- // Replaces tsx's minified transformation with an un-minified esbuild transform
7
- // that preserves line structure. This ensures V8 coverage byte offsets map
6
+ // Applies an un-minified esbuild transform that preserves line structure.
7
+ // This ensures V8 coverage byte offsets map
8
8
  // cleanly to TypeScript source lines via the inline source map, giving
9
9
  // accurate per-line coverage rather than collapsing multiple statements onto
10
10
  // a single minified line.
@@ -151,7 +151,7 @@ export async function collectServerCoverageMap(coverageDataDir, cwd, testFiles)
151
151
  if (success)
152
152
  converted++;
153
153
  }
154
- catch (e) {
154
+ catch {
155
155
  // Skip files that can't be converted
156
156
  }
157
157
  }
@@ -1,5 +1,44 @@
1
+ /**
2
+ * Handle returned by `mock.timers.enable()` for driving fake timers during a
3
+ * test. While enabled, `setTimeout`, `setInterval`, `clearTimeout`,
4
+ * `clearInterval`, and `Date.now` use the fake clock instead of the real one;
5
+ * timers fire only when the test calls `advance` (or `advanceAsync`).
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * it('debounces save calls', (t) => {
10
+ * let timers = t.mock.timers.enable()
11
+ * let save = t.mock.fn()
12
+ * let debounced = debounce(save, 100)
13
+ * debounced(); debounced(); debounced()
14
+ * timers.advance(100)
15
+ * assert.equal(save.mock.calls.length, 1)
16
+ * })
17
+ * ```
18
+ */
1
19
  export interface FakeTimers {
20
+ /**
21
+ * Advance the fake clock by `ms` milliseconds, synchronously firing every
22
+ * timer whose deadline is reached during the advance.
23
+ *
24
+ * @param ms Number of milliseconds to advance.
25
+ */
2
26
  advance(ms: number): void;
27
+ /**
28
+ * Like `advance`, but yields to microtasks between each timer firing so
29
+ * Promise continuations (and any timers they schedule) can settle before
30
+ * the next firing is processed. Use this when a callback awaits work that
31
+ * itself depends on the fake clock.
32
+ *
33
+ * @param ms Number of milliseconds to advance.
34
+ * @returns A promise that resolves once all reachable timers have fired.
35
+ */
36
+ advanceAsync(ms: number): Promise<void>;
37
+ /**
38
+ * Restore the original timer functions and the real clock. Called
39
+ * automatically after the test finishes; may also be called early to
40
+ * disable fake timers mid-test.
41
+ */
3
42
  restore(): void;
4
43
  }
5
44
  export declare function createFakeTimers(): FakeTimers;
@@ -1 +1 @@
1
- {"version":3,"file":"fake-timers.d.ts","sourceRoot":"","sources":["../../src/lib/fake-timers.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,OAAO,IAAI,IAAI,CAAA;CAChB;AAED,wBAAgB,gBAAgB,IAAI,UAAU,CAwD7C"}
1
+ {"version":3,"file":"fake-timers.d.ts","sourceRoot":"","sources":["../../src/lib/fake-timers.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,UAAU;IACzB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACvC;;;;OAIG;IACH,OAAO,IAAI,IAAI,CAAA;CAChB;AAED,wBAAgB,gBAAgB,IAAI,UAAU,CA0E7C"}
@@ -15,21 +15,40 @@ export function createFakeTimers() {
15
15
  let clearTimeoutMock = mock.method(globalThis, 'clearTimeout', cancel);
16
16
  let setIntervalMock = mock.method(globalThis, 'setInterval', ((fn, delay = 0) => schedule(fn, delay, Math.max(0, delay))));
17
17
  let clearIntervalMock = mock.method(globalThis, 'clearInterval', cancel);
18
+ function takeNext(targetTime) {
19
+ let next = pending.filter((t) => t.time <= targetTime).sort((a, b) => a.time - b.time)[0];
20
+ if (!next)
21
+ return null;
22
+ currentTime = next.time;
23
+ pending = pending.filter((t) => t.id !== next.id);
24
+ // Requeue intervals before running the callback so that calling
25
+ // clearInterval(id) from inside the callback can cancel the next firing.
26
+ if (next.repeatMs !== undefined) {
27
+ pending.push({ ...next, time: next.time + Math.max(1, next.repeatMs) });
28
+ }
29
+ return next;
30
+ }
18
31
  return {
19
32
  advance(ms) {
20
33
  let targetTime = currentTime + ms;
21
34
  while (true) {
22
- let next = pending.filter((t) => t.time <= targetTime).sort((a, b) => a.time - b.time)[0];
35
+ let next = takeNext(targetTime);
36
+ if (!next)
37
+ break;
38
+ next.fn();
39
+ }
40
+ currentTime = targetTime;
41
+ },
42
+ async advanceAsync(ms) {
43
+ let targetTime = currentTime + ms;
44
+ while (true) {
45
+ let next = takeNext(targetTime);
23
46
  if (!next)
24
47
  break;
25
- currentTime = next.time;
26
- pending = pending.filter((t) => t.id !== next.id);
27
- // Requeue intervals before running the callback so that calling
28
- // clearInterval(id) from inside the callback can cancel the next firing.
29
- if (next.repeatMs !== undefined) {
30
- pending.push({ ...next, time: next.time + Math.max(1, next.repeatMs) });
31
- }
32
48
  next.fn();
49
+ // Drain microtasks so Promise continuations (and any timers they
50
+ // schedule) can settle before we look for the next firing.
51
+ await Promise.resolve();
33
52
  }
34
53
  currentTime = targetTime;
35
54
  },
@@ -1,7 +1,9 @@
1
1
  import type { TestContext } from './context.ts';
2
+ declare function describeImpl(name: string, fn: () => void): void;
3
+ declare function describeImpl(name: string, meta: SuiteMeta, fn: () => void): void;
2
4
  /**
3
- * Groups related tests into a named suite. Suites can be nested snd will be displayed
4
- * as such or joined with ` > ` in reporter output. Lifecycle hooks registered inside
5
+ * Groups related tests into a named suite. Suites can be nested and will be displayed
6
+ * as such in reporter output. Lifecycle hooks registered inside
5
7
  * a `describe` block apply only to tests within that block.
6
8
  *
7
9
  * @example
@@ -15,9 +17,10 @@ import type { TestContext } from './context.ts';
15
17
  * describe.todo('planned suite')
16
18
  *
17
19
  * @param name - The suite name shown in reporter output.
20
+ * @param meta - Suite metadata such as `skip` or `only`.
18
21
  * @param fn - A function that registers the tests and lifecycle hooks in this suite.
19
22
  */
20
- export declare const describe: ((name: string, metaOrFn: SuiteMeta | (() => void), fn?: (() => void) | undefined) => void) & {
23
+ export declare const describe: typeof describeImpl & {
21
24
  skip: (name: string, fn: () => void) => void;
22
25
  only: (name: string, fn: () => void) => void;
23
26
  todo: (name: string) => void;
@@ -31,6 +34,8 @@ type TestMeta = {
31
34
  only?: boolean;
32
35
  };
33
36
  type TestFn = (t: TestContext) => void | Promise<void>;
37
+ declare function itImpl(name: string, fn: TestFn): void;
38
+ declare function itImpl(name: string, meta: TestMeta, fn: TestFn): void;
34
39
  /**
35
40
  * Defines a single test case. The optional `TestContext` argument `t` provides
36
41
  * mock helpers and per-test cleanup registration.
@@ -47,21 +52,22 @@ type TestFn = (t: TestContext) => void | Promise<void>;
47
52
  * it.todo('coming soon')
48
53
  *
49
54
  * @param name - The test name shown in reporter output.
55
+ * @param meta - Test metadata such as `skip` or `only`.
50
56
  * @param fn - The test body, receiving a {@link TestContext} as its first argument.
51
57
  */
52
- export declare const it: ((name: string, metaOrFn: TestFn | TestMeta, fn?: TestFn | undefined) => void) & {
58
+ export declare const it: typeof itImpl & {
53
59
  skip: (name: string, fn?: TestFn | undefined) => void;
54
60
  only: (name: string, fn: TestFn) => void;
55
61
  todo: (name: string) => void;
56
62
  };
57
63
  /** Alias for {@link describe}. */
58
- export declare const suite: ((name: string, metaOrFn: SuiteMeta | (() => void), fn?: (() => void) | undefined) => void) & {
64
+ export declare const suite: typeof describeImpl & {
59
65
  skip: (name: string, fn: () => void) => void;
60
66
  only: (name: string, fn: () => void) => void;
61
67
  todo: (name: string) => void;
62
68
  };
63
69
  /** Alias for {@link it}. */
64
- export declare const test: ((name: string, metaOrFn: TestFn | TestMeta, fn?: TestFn | undefined) => void) & {
70
+ export declare const test: typeof itImpl & {
65
71
  skip: (name: string, fn?: TestFn | undefined) => void;
66
72
  only: (name: string, fn: TestFn) => void;
67
73
  todo: (name: string) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"framework.d.ts","sourceRoot":"","sources":["../../src/lib/framework.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAkF/C;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,QAAQ;;;;CAiBpB,CAAA;AAED,KAAK,SAAS,GAAG;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,CAAA;AACnD,KAAK,QAAQ,GAAG;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,CAAA;AAClD,KAAK,MAAM,GAAG,CAAC,CAAC,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;AAUtD;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,EAAE;;;;CAiBd,CAAA;AAED,kCAAkC;AAClC,eAAO,MAAM,KAAK;;;;CAAW,CAAA;AAC7B,4BAA4B;AAC5B,eAAO,MAAM,IAAI;;;;CAAK,CAAA;AA2BtB;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,QAGxD;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,QAGvD;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,QAGvD;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,QAGtD;AAED,mEAAiE;AACjE,eAAO,MAAM,MAAM,kBAAY,CAAA;AAC/B,kEAAgE;AAChE,eAAO,MAAM,KAAK,iBAAW,CAAA"}
1
+ {"version":3,"file":"framework.d.ts","sourceRoot":"","sources":["../../src/lib/framework.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AA4F/C,iBAAS,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAAA;AACzD,iBAAS,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAAA;AAO1E;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,QAAQ;;;;CAUnB,CAAA;AAEF,KAAK,SAAS,GAAG;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,CAAA;AACnD,KAAK,QAAQ,GAAG;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,CAAA;AAClD,KAAK,MAAM,GAAG,CAAC,CAAC,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;AAatD,iBAAS,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;AAC/C,iBAAS,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;AAO/D;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,EAAE;;;;CAUb,CAAA;AAEF,kCAAkC;AAClC,eAAO,MAAM,KAAK;;;;CAAW,CAAA;AAC7B,4BAA4B;AAC5B,eAAO,MAAM,IAAI;;;;CAAK,CAAA;AA2BtB;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,QAGxD;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,QAGvD;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,QAGvD;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,QAGtD;AAED,mEAAiE;AACjE,eAAO,MAAM,MAAM,kBAAY,CAAA;AAC/B,kEAAgE;AAChE,eAAO,MAAM,KAAK,iBAAW,CAAA"}
@@ -26,6 +26,14 @@ function registerDescribe(name, fn, flags) {
26
26
  throw new Error(`Duplicate suite name: "${fullName}"`);
27
27
  }
28
28
  let suite = { name: fullName, tests: [], ...flags };
29
+ // Children inherit `skip`/`only` from their parent so that
30
+ // `describe.skip('parent', () => describe('child', () => it(...)))` actually
31
+ // skips the child's tests. The executor walks `rootSuites` as a flat list and
32
+ // only inspects each suite's own flag, so the propagation has to happen here.
33
+ if (currentSuite?.skip)
34
+ suite.skip = true;
35
+ if (currentSuite?.only)
36
+ suite.only = true;
29
37
  // Inherit lifecycle hooks from parent suite (or root hooks if at top level)
30
38
  let parent = currentSuite ?? rootHooks;
31
39
  if (parent.beforeEach)
@@ -53,9 +61,14 @@ function registerDescribe(name, fn, flags) {
53
61
  currentSuite = prevSuite;
54
62
  }
55
63
  }
64
+ function describeImpl(name, metaOrFn, fn) {
65
+ let meta = typeof metaOrFn === 'function' ? {} : metaOrFn;
66
+ let suiteFn = typeof metaOrFn === 'function' ? metaOrFn : fn;
67
+ registerDescribe(name, suiteFn, meta);
68
+ }
56
69
  /**
57
- * Groups related tests into a named suite. Suites can be nested snd will be displayed
58
- * as such or joined with ` > ` in reporter output. Lifecycle hooks registered inside
70
+ * Groups related tests into a named suite. Suites can be nested and will be displayed
71
+ * as such in reporter output. Lifecycle hooks registered inside
59
72
  * a `describe` block apply only to tests within that block.
60
73
  *
61
74
  * @example
@@ -69,13 +82,10 @@ function registerDescribe(name, fn, flags) {
69
82
  * describe.todo('planned suite')
70
83
  *
71
84
  * @param name - The suite name shown in reporter output.
85
+ * @param meta - Suite metadata such as `skip` or `only`.
72
86
  * @param fn - A function that registers the tests and lifecycle hooks in this suite.
73
87
  */
74
- export const describe = Object.assign((name, metaOrFn, fn) => {
75
- let meta = typeof metaOrFn === 'function' ? {} : metaOrFn;
76
- let suiteFn = typeof metaOrFn === 'function' ? metaOrFn : fn;
77
- registerDescribe(name, suiteFn, meta);
78
- }, {
88
+ export const describe = Object.assign(describeImpl, {
79
89
  skip: (name, fn) => registerDescribe(name, fn, { skip: true }),
80
90
  only: (name, fn) => registerDescribe(name, fn, { only: true }),
81
91
  todo: (name) => {
@@ -93,6 +103,11 @@ function registerIt(name, fn, flags) {
93
103
  }
94
104
  suite.tests.push({ name, fn, suite, ...flags });
95
105
  }
106
+ function itImpl(name, metaOrFn, fn) {
107
+ let meta = typeof metaOrFn === 'function' ? {} : metaOrFn;
108
+ let testFn = typeof metaOrFn === 'function' ? metaOrFn : fn;
109
+ registerIt(name, testFn, meta);
110
+ }
96
111
  /**
97
112
  * Defines a single test case. The optional `TestContext` argument `t` provides
98
113
  * mock helpers and per-test cleanup registration.
@@ -109,13 +124,10 @@ function registerIt(name, fn, flags) {
109
124
  * it.todo('coming soon')
110
125
  *
111
126
  * @param name - The test name shown in reporter output.
127
+ * @param meta - Test metadata such as `skip` or `only`.
112
128
  * @param fn - The test body, receiving a {@link TestContext} as its first argument.
113
129
  */
114
- export const it = Object.assign((name, metaOrFn, fn) => {
115
- let meta = typeof metaOrFn === 'function' ? {} : metaOrFn;
116
- let testFn = typeof metaOrFn === 'function' ? metaOrFn : fn;
117
- registerIt(name, testFn, meta);
118
- }, {
130
+ export const it = Object.assign(itImpl, {
119
131
  skip: (name, fn) => registerIt(name, fn ?? (() => { }), { skip: true }),
120
132
  only: (name, fn) => registerIt(name, fn, { only: true }),
121
133
  todo: (name) => {
@@ -1 +1 @@
1
- {"version":3,"file":"import-module.d.ts","sourceRoot":"","sources":["../../src/lib/import-module.ts"],"names":[],"mappings":"AAkBA,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,CAUpF"}
1
+ {"version":3,"file":"import-module.d.ts","sourceRoot":"","sources":["../../src/lib/import-module.ts"],"names":[],"mappings":"AAmBA,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,CAoBpF"}
@@ -6,7 +6,8 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
6
6
  }
7
7
  return path;
8
8
  };
9
- import { tsImport } from 'tsx/esm/api';
9
+ import * as path from 'node:path';
10
+ import { pathToFileURL } from 'node:url';
10
11
  import { IS_BUN } from "./runtime.js";
11
12
  function hasImportMetaResolve(meta) {
12
13
  return 'resolve' in meta && typeof meta.resolve === 'function';
@@ -19,11 +20,20 @@ function hasImportMetaResolve(meta) {
19
20
  * @returns The imported module namespace.
20
21
  */
21
22
  export async function importModule(specifier, meta) {
23
+ // Absolute Windows paths (`C:\foo\bar.ts`) aren't valid ESM specifiers — only
24
+ // `file:///C:/foo/bar.ts` URLs, relative specifiers, or POSIX absolute paths
25
+ // are. Convert any absolute filesystem path to its `file:` URL so module
26
+ // loaders and `import()` accept it on every platform. POSIX absolute paths
27
+ // happen to work as specifiers without conversion, but going through
28
+ // `pathToFileURL` is safe and platform-agnostic.
29
+ let resolvedSpecifier = path.isAbsolute(specifier) ? pathToFileURL(specifier).href : specifier;
22
30
  if (IS_BUN) {
23
31
  if (!hasImportMetaResolve(meta)) {
24
32
  throw new Error('importModule() requires import.meta.resolve() in Bun');
25
33
  }
26
- return import(__rewriteRelativeImportExtension(meta.resolve(specifier, meta.url)));
34
+ return import(__rewriteRelativeImportExtension(meta.resolve(resolvedSpecifier, meta.url)));
27
35
  }
28
- return tsImport(specifier, meta.url);
36
+ // node-tsx uses Node APIs that fail in Bun if statically imported
37
+ let { loadModule } = await import('@remix-run/node-tsx/load-module');
38
+ return loadModule(resolvedSpecifier, meta.url);
29
39
  }
@@ -1 +1 @@
1
- {"version":3,"file":"dot.d.ts","sourceRoot":"","sources":["../../../src/lib/reporters/dot.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAC1C,OAAO,KAAK,EAAE,MAAM,EAAc,WAAW,EAAE,MAAM,cAAc,CAAA;AAEnE,qBAAa,WAAY,YAAW,QAAQ;;IAI1C,cAAc,CAAC,MAAM,EAAE,MAAM,QAAI;IAEjC,QAAQ,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE,MAAM,QAc3C;IAED,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,QA6B3C;CACF"}
1
+ {"version":3,"file":"dot.d.ts","sourceRoot":"","sources":["../../../src/lib/reporters/dot.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAC1C,OAAO,KAAK,EAAE,MAAM,EAAc,WAAW,EAAE,MAAM,cAAc,CAAA;AAEnE,qBAAa,WAAY,YAAW,QAAQ;;IAM1C,cAAc,CAAC,MAAM,EAAE,MAAM,QAAI;IAEjC,QAAQ,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE,MAAM,QAmB3C;IAED,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,QA+B3C;CACF"}
@@ -3,8 +3,16 @@ import { normalizeLine } from "../normalize.js";
3
3
  export class DotReporter {
4
4
  #failures = [];
5
5
  #dotCount = 0;
6
+ #files = new Set();
7
+ #suites = new Set();
6
8
  onSectionStart(_label) { }
7
9
  onResult(results, _env) {
10
+ for (let test of results.tests) {
11
+ if (test.filePath)
12
+ this.#files.add(test.filePath);
13
+ if (test.suiteName)
14
+ this.#suites.add(test.suiteName);
15
+ }
8
16
  for (let test of results.tests) {
9
17
  if (test.status === 'passed') {
10
18
  process.stdout.write(colors.green('.'));
@@ -43,6 +51,8 @@ export class DotReporter {
43
51
  let { passed, failed, skipped, todo } = counts;
44
52
  let info = colors.cyan('ℹ');
45
53
  console.log();
54
+ console.log(`${info} files ${this.#files.size}`);
55
+ console.log(`${info} suites ${this.#suites.size}`);
46
56
  console.log(`${info} tests ${passed + failed + skipped + todo}`);
47
57
  console.log(`${info} pass ${passed}`);
48
58
  console.log(`${info} fail ${failed}`);
@@ -1 +1 @@
1
- {"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../../src/lib/reporters/files.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAC1C,OAAO,KAAK,EAAE,MAAM,EAAc,WAAW,EAAE,MAAM,cAAc,CAAA;AAEnE,qBAAa,aAAc,YAAW,QAAQ;;IAG5C,cAAc,CAAC,MAAM,EAAE,MAAM,QAAI;IAEjC,QAAQ,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,CAAC,EAAE,MAAM,QA8B1C;IAED,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,QAgC3C;CACF"}
1
+ {"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../../src/lib/reporters/files.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAC1C,OAAO,KAAK,EAAE,MAAM,EAAc,WAAW,EAAE,MAAM,cAAc,CAAA;AAEnE,qBAAa,aAAc,YAAW,QAAQ;;IAK5C,cAAc,CAAC,MAAM,EAAE,MAAM,QAAI;IAEjC,QAAQ,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,CAAC,EAAE,MAAM,QAmC1C;IAED,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,QAkC3C;CACF"}
@@ -3,8 +3,16 @@ import { colors } from "../colors.js";
3
3
  import { normalizeLine } from "../normalize.js";
4
4
  export class FilesReporter {
5
5
  #failures = [];
6
+ #files = new Set();
7
+ #suites = new Set();
6
8
  onSectionStart(_label) { }
7
9
  onResult(results, env) {
10
+ for (let test of results.tests) {
11
+ if (test.filePath)
12
+ this.#files.add(test.filePath);
13
+ if (test.suiteName)
14
+ this.#suites.add(test.suiteName);
15
+ }
8
16
  let filePath = results.tests[0]?.filePath;
9
17
  let fileName = filePath ? path.relative(process.cwd(), filePath) : '(unknown)';
10
18
  let envLabel = env ? ` ${colors.dim(`[${env}]`)}` : '';
@@ -58,6 +66,8 @@ export class FilesReporter {
58
66
  let { passed, failed, skipped, todo } = counts;
59
67
  let info = colors.cyan('ℹ');
60
68
  console.log();
69
+ console.log(`${info} files ${this.#files.size}`);
70
+ console.log(`${info} suites ${this.#suites.size}`);
61
71
  console.log(`${info} tests ${passed + failed + skipped + todo}`);
62
72
  console.log(`${info} pass ${passed}`);
63
73
  console.log(`${info} fail ${failed}`);
@@ -1,4 +1,4 @@
1
- import type { V8CoverageEntry } from '../coverage';
1
+ import type { V8CoverageEntry } from '../coverage.ts';
2
2
  export interface TestResult {
3
3
  name: string;
4
4
  suiteName: string;
@@ -1 +1 @@
1
- {"version":3,"file":"results.d.ts","sourceRoot":"","sources":["../../../src/lib/reporters/results.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAElD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAA;IAChD,KAAK,CAAC,EAAE;QACN,OAAO,EAAE,MAAM,CAAA;QACf,KAAK,CAAC,EAAE,MAAM,CAAA;KACf,CAAA;IACD,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,yBAAyB,CAAC,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,eAAe,EAAE,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACnF;AAED,MAAM,MAAM,MAAM,GAAG;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;CACb,CAAA"}
1
+ {"version":3,"file":"results.d.ts","sourceRoot":"","sources":["../../../src/lib/reporters/results.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAErD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAA;IAChD,KAAK,CAAC,EAAE;QACN,OAAO,EAAE,MAAM,CAAA;QACf,KAAK,CAAC,EAAE,MAAM,CAAA;KACf,CAAA;IACD,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,yBAAyB,CAAC,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,eAAe,EAAE,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACnF;AAED,MAAM,MAAM,MAAM,GAAG;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;CACb,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"spec.d.ts","sourceRoot":"","sources":["../../../src/lib/reporters/spec.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAC1C,OAAO,KAAK,EAAE,MAAM,EAAc,WAAW,EAAE,MAAM,cAAc,CAAA;AAEnE,qBAAa,YAAa,YAAW,QAAQ;;IAG3C,cAAc,CAAC,KAAK,EAAE,MAAM,QAE3B;IAED,QAAQ,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,CAAC,EAAE,MAAM,QA8H1C;IAED,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,QAgC3C;CACF"}
1
+ {"version":3,"file":"spec.d.ts","sourceRoot":"","sources":["../../../src/lib/reporters/spec.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAC1C,OAAO,KAAK,EAAE,MAAM,EAAc,WAAW,EAAE,MAAM,cAAc,CAAA;AAEnE,qBAAa,YAAa,YAAW,QAAQ;;IAK3C,cAAc,CAAC,KAAK,EAAE,MAAM,QAE3B;IAED,QAAQ,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,CAAC,EAAE,MAAM,QAmI1C;IAED,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,QAkC3C;CACF"}