@remix-run/test 0.0.0 → 0.2.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 (150) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +430 -2
  3. package/dist/app/client/entry.d.ts +2 -0
  4. package/dist/app/client/entry.d.ts.map +1 -0
  5. package/dist/app/client/entry.js +324 -0
  6. package/dist/app/client/iframe.d.ts +2 -0
  7. package/dist/app/client/iframe.d.ts.map +1 -0
  8. package/dist/app/client/iframe.js +22 -0
  9. package/dist/app/server.d.ts +6 -0
  10. package/dist/app/server.d.ts.map +1 -0
  11. package/dist/app/server.js +303 -0
  12. package/dist/cli-entry.d.ts +3 -0
  13. package/dist/cli-entry.d.ts.map +1 -0
  14. package/dist/cli-entry.js +14 -0
  15. package/dist/cli.d.ts +8 -0
  16. package/dist/cli.d.ts.map +1 -0
  17. package/dist/cli.js +305 -0
  18. package/dist/index.d.ts +6 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +2 -0
  21. package/dist/lib/colors.d.ts +2 -0
  22. package/dist/lib/colors.d.ts.map +1 -0
  23. package/dist/lib/colors.js +2 -0
  24. package/dist/lib/config.d.ts +91 -0
  25. package/dist/lib/config.d.ts.map +1 -0
  26. package/dist/lib/config.js +255 -0
  27. package/dist/lib/context.d.ts +93 -0
  28. package/dist/lib/context.d.ts.map +1 -0
  29. package/dist/lib/context.js +65 -0
  30. package/dist/lib/coverage-loader.d.ts +16 -0
  31. package/dist/lib/coverage-loader.d.ts.map +1 -0
  32. package/dist/lib/coverage-loader.js +20 -0
  33. package/dist/lib/coverage.d.ts +28 -0
  34. package/dist/lib/coverage.d.ts.map +1 -0
  35. package/dist/lib/coverage.js +212 -0
  36. package/dist/lib/executor.d.ts +4 -0
  37. package/dist/lib/executor.d.ts.map +1 -0
  38. package/dist/lib/executor.js +128 -0
  39. package/dist/lib/fake-timers.d.ts +6 -0
  40. package/dist/lib/fake-timers.d.ts.map +1 -0
  41. package/dist/lib/fake-timers.js +45 -0
  42. package/dist/lib/framework.d.ts +107 -0
  43. package/dist/lib/framework.d.ts.map +1 -0
  44. package/dist/lib/framework.js +198 -0
  45. package/dist/lib/import-module.d.ts +2 -0
  46. package/dist/lib/import-module.d.ts.map +1 -0
  47. package/dist/lib/import-module.js +29 -0
  48. package/dist/lib/mock.d.ts +52 -0
  49. package/dist/lib/mock.d.ts.map +1 -0
  50. package/dist/lib/mock.js +61 -0
  51. package/dist/lib/normalize.d.ts +2 -0
  52. package/dist/lib/normalize.d.ts.map +1 -0
  53. package/dist/lib/normalize.js +18 -0
  54. package/dist/lib/playwright.d.ts +15 -0
  55. package/dist/lib/playwright.d.ts.map +1 -0
  56. package/dist/lib/playwright.js +81 -0
  57. package/dist/lib/reporters/dot.d.ts +9 -0
  58. package/dist/lib/reporters/dot.d.ts.map +1 -0
  59. package/dist/lib/reporters/dot.js +56 -0
  60. package/dist/lib/reporters/files.d.ts +9 -0
  61. package/dist/lib/reporters/files.d.ts.map +1 -0
  62. package/dist/lib/reporters/files.js +71 -0
  63. package/dist/lib/reporters/index.d.ts +13 -0
  64. package/dist/lib/reporters/index.d.ts.map +1 -0
  65. package/dist/lib/reporters/index.js +18 -0
  66. package/dist/lib/reporters/results.d.ts +30 -0
  67. package/dist/lib/reporters/results.d.ts.map +1 -0
  68. package/dist/lib/reporters/results.js +1 -0
  69. package/dist/lib/reporters/spec.d.ts +9 -0
  70. package/dist/lib/reporters/spec.d.ts.map +1 -0
  71. package/dist/lib/reporters/spec.js +153 -0
  72. package/dist/lib/reporters/tap.d.ts +9 -0
  73. package/dist/lib/reporters/tap.d.ts.map +1 -0
  74. package/dist/lib/reporters/tap.js +54 -0
  75. package/dist/lib/runner-browser.d.ts +21 -0
  76. package/dist/lib/runner-browser.d.ts.map +1 -0
  77. package/dist/lib/runner-browser.js +117 -0
  78. package/dist/lib/runner.d.ts +14 -0
  79. package/dist/lib/runner.d.ts.map +1 -0
  80. package/dist/lib/runner.js +118 -0
  81. package/dist/lib/runtime.d.ts +2 -0
  82. package/dist/lib/runtime.d.ts.map +1 -0
  83. package/dist/lib/runtime.js +2 -0
  84. package/dist/lib/ts-transform.d.ts +4 -0
  85. package/dist/lib/ts-transform.d.ts.map +1 -0
  86. package/dist/lib/ts-transform.js +29 -0
  87. package/dist/lib/watcher.d.ts +5 -0
  88. package/dist/lib/watcher.d.ts.map +1 -0
  89. package/dist/lib/watcher.js +39 -0
  90. package/dist/lib/worker-e2e.d.ts +2 -0
  91. package/dist/lib/worker-e2e.d.ts.map +1 -0
  92. package/dist/lib/worker-e2e.js +49 -0
  93. package/dist/lib/worker.d.ts +2 -0
  94. package/dist/lib/worker.d.ts.map +1 -0
  95. package/dist/lib/worker.js +57 -0
  96. package/dist/test/coverage/fixture.d.ts +5 -0
  97. package/dist/test/coverage/fixture.d.ts.map +1 -0
  98. package/dist/test/coverage/fixture.js +32 -0
  99. package/dist/test/coverage/test-browser.d.ts +2 -0
  100. package/dist/test/coverage/test-browser.d.ts.map +1 -0
  101. package/dist/test/coverage/test-browser.js +24 -0
  102. package/dist/test/coverage/test-e2e.d.ts +2 -0
  103. package/dist/test/coverage/test-e2e.d.ts.map +1 -0
  104. package/dist/test/coverage/test-e2e.js +60 -0
  105. package/dist/test/coverage/test-unit.d.ts +2 -0
  106. package/dist/test/coverage/test-unit.d.ts.map +1 -0
  107. package/dist/test/coverage/test-unit.js +27 -0
  108. package/dist/test/framework.test.browser.d.ts +2 -0
  109. package/dist/test/framework.test.browser.d.ts.map +1 -0
  110. package/dist/test/framework.test.browser.js +107 -0
  111. package/dist/test/framework.test.e2e.d.ts +2 -0
  112. package/dist/test/framework.test.e2e.d.ts.map +1 -0
  113. package/dist/test/framework.test.e2e.js +34 -0
  114. package/package.json +79 -5
  115. package/src/app/client/entry.ts +353 -0
  116. package/src/app/client/iframe.ts +18 -0
  117. package/src/app/server.ts +336 -0
  118. package/src/cli-entry.ts +15 -0
  119. package/src/cli.ts +384 -0
  120. package/src/index.ts +16 -0
  121. package/src/lib/colors.ts +3 -0
  122. package/src/lib/config.ts +377 -0
  123. package/src/lib/context.ts +168 -0
  124. package/src/lib/coverage-loader.ts +31 -0
  125. package/src/lib/coverage.ts +320 -0
  126. package/src/lib/executor.ts +145 -0
  127. package/src/lib/fake-timers.ts +64 -0
  128. package/src/lib/framework.ts +251 -0
  129. package/src/lib/import-module.ts +29 -0
  130. package/src/lib/mock.ts +89 -0
  131. package/src/lib/normalize.ts +22 -0
  132. package/src/lib/playwright.ts +100 -0
  133. package/src/lib/reporters/dot.ts +58 -0
  134. package/src/lib/reporters/files.ts +77 -0
  135. package/src/lib/reporters/index.ts +27 -0
  136. package/src/lib/reporters/results.ts +29 -0
  137. package/src/lib/reporters/spec.ts +174 -0
  138. package/src/lib/reporters/tap.ts +58 -0
  139. package/src/lib/runner-browser.ts +165 -0
  140. package/src/lib/runner.ts +189 -0
  141. package/src/lib/runtime.ts +2 -0
  142. package/src/lib/ts-transform.ts +36 -0
  143. package/src/lib/watcher.ts +46 -0
  144. package/src/lib/worker-e2e.ts +54 -0
  145. package/src/lib/worker.ts +50 -0
  146. package/src/test/coverage/fixture.ts +34 -0
  147. package/src/test/coverage/test-browser.ts +29 -0
  148. package/src/test/coverage/test-e2e.ts +70 -0
  149. package/src/test/coverage/test-unit.ts +32 -0
  150. package/tsconfig.json +16 -0
@@ -0,0 +1,255 @@
1
+ import * as fsp from 'node:fs/promises';
2
+ import * as os from 'node:os';
3
+ import * as path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import * as util from 'node:util';
6
+ import { importModule } from "./import-module.js";
7
+ export const IS_RUNNING_FROM_SRC = path.extname(new URL(import.meta.url).pathname) === '.ts';
8
+ /*
9
+ * The root directory for the test code. Coverage URLs are emitted as
10
+ * `/scripts/<rel-from-rootDir>` and resolved back via the same anchor.
11
+ *
12
+ * - In a published install: `process.cwd()`, since deps and user source all
13
+ * live under it.
14
+ * - In monorepo src mode: the monorepo root, computed by walking back from
15
+ * the resolved `@remix-run/test` source path. `process.cwd()` doesn't work
16
+ * here because workspace deps and node_modules live above the per-package
17
+ * cwd.
18
+ */
19
+ export function getBrowserTestRootDir() {
20
+ return IS_RUNNING_FROM_SRC
21
+ ? // Resolve to packages/test/src/index.ts and the pop 3 directories off to the repo root
22
+ path
23
+ .dirname(fileURLToPath(import.meta.resolve('@remix-run/test')))
24
+ .split(path.sep)
25
+ .slice(0, -3)
26
+ .join(path.sep)
27
+ : process.cwd();
28
+ }
29
+ // prettier-ignore
30
+ // Note: `description` is not a field used by parseArgs(), it's an additional field
31
+ // we use for `--help`
32
+ const cliOptions = {
33
+ 'browser.echo': {
34
+ type: 'boolean',
35
+ description: 'Echo browser console output to stdout',
36
+ },
37
+ 'browser.open': {
38
+ type: 'boolean',
39
+ description: 'Open browser window and keep open after tests finish',
40
+ },
41
+ 'glob.browser': {
42
+ type: 'string',
43
+ description: 'Glob pattern for browser test files',
44
+ },
45
+ 'glob.e2e': {
46
+ type: 'string',
47
+ description: 'Glob pattern for E2E test files',
48
+ },
49
+ 'glob.exclude': {
50
+ type: 'string',
51
+ description: 'Glob pattern for paths to exclude from discovery',
52
+ },
53
+ 'glob.test': {
54
+ type: 'string',
55
+ description: 'Glob pattern for all test files',
56
+ },
57
+ concurrency: {
58
+ type: 'string',
59
+ short: 'c',
60
+ description: 'Max number of concurrent test workers (default: os.availableParallelism())',
61
+ },
62
+ config: {
63
+ type: 'string',
64
+ description: 'Path to config file (default: remix-test.config.ts)',
65
+ },
66
+ coverage: {
67
+ type: 'boolean',
68
+ description: 'Enable or disable coverage collection (default: false)',
69
+ },
70
+ 'coverage.dir': {
71
+ type: 'string',
72
+ description: 'Directory to output coverage reports (default: .coverage)',
73
+ },
74
+ 'coverage.include': {
75
+ type: 'string',
76
+ multiple: true,
77
+ description: 'Glob pattern(s) for files to include in coverage',
78
+ },
79
+ 'coverage.exclude': {
80
+ type: 'string',
81
+ multiple: true,
82
+ description: 'Glob pattern(s) for files to exclude from coverage',
83
+ },
84
+ 'coverage.branches': {
85
+ type: 'string',
86
+ description: 'Branches coverage threshold percentage',
87
+ },
88
+ 'coverage.functions': {
89
+ type: 'string',
90
+ description: 'Functions coverage threshold percentage',
91
+ },
92
+ 'coverage.lines': {
93
+ type: 'string',
94
+ description: 'Lines coverage threshold percentage',
95
+ },
96
+ 'coverage.statements': {
97
+ type: 'string',
98
+ description: 'Statements coverage threshold percentage',
99
+ },
100
+ setup: {
101
+ type: 'string',
102
+ short: 's',
103
+ description: 'Path to a setup module exporting globalSetup/globalTeardown',
104
+ },
105
+ playwrightConfig: {
106
+ type: 'string',
107
+ description: 'Path to a Playwright config file',
108
+ },
109
+ project: {
110
+ type: 'string',
111
+ short: 'p',
112
+ description: 'Filter to a specific Playwright project (comma-separated)',
113
+ },
114
+ reporter: {
115
+ type: 'string',
116
+ short: 'r',
117
+ description: 'Test reporter: spec, files, tap, dot (default: spec)',
118
+ },
119
+ type: {
120
+ type: 'string',
121
+ short: 't',
122
+ description: 'Comma-separated test types to run (default: server,browser,e2e)',
123
+ },
124
+ watch: {
125
+ type: 'boolean',
126
+ short: 'w',
127
+ description: 'Re-run tests on file changes',
128
+ },
129
+ };
130
+ const defaultValues = {
131
+ browser: {
132
+ echo: false,
133
+ open: false,
134
+ },
135
+ concurrency: os.availableParallelism(),
136
+ coverage: {
137
+ dir: '.coverage',
138
+ include: undefined,
139
+ exclude: undefined,
140
+ statements: undefined,
141
+ lines: undefined,
142
+ branches: undefined,
143
+ functions: undefined,
144
+ },
145
+ glob: {
146
+ test: '**/*.test{,.e2e,.browser}.{ts,tsx}',
147
+ browser: '**/*.test.browser.{ts,tsx}',
148
+ e2e: '**/*.test.e2e.{ts,tsx}',
149
+ exclude: 'node_modules/**',
150
+ },
151
+ reporter: process.env.CI === 'true' ? 'files' : 'spec',
152
+ type: 'server,browser,e2e',
153
+ setup: undefined,
154
+ playwrightConfig: undefined,
155
+ project: undefined,
156
+ watch: false,
157
+ };
158
+ export async function loadConfig(args = process.argv.slice(2), cwd = process.cwd()) {
159
+ let parsed = parseCliArgs(args);
160
+ let fileConfig = await loadConfigFile(parsed.values.config, cwd);
161
+ let config = resolveConfig(fileConfig, parsed);
162
+ return config;
163
+ }
164
+ export function getRemixTestHelpText(_target = process.stdout) {
165
+ let lines = [
166
+ 'Usage: remix-test [glob] [options]',
167
+ '',
168
+ 'Arguments:',
169
+ ` glob Glob pattern for test files (default: "${defaultValues.glob.test}")`,
170
+ '',
171
+ 'Options:',
172
+ ];
173
+ for (let [long, opt] of Object.entries(cliOptions)) {
174
+ let short = 'short' in opt ? `/-${opt.short}` : '';
175
+ let label = opt.type === 'string' ? `--${long}${short} <value>` : `--${long}${short}`;
176
+ lines.push(` ${label.padEnd(30)} ${opt.description}`);
177
+ }
178
+ lines.push(` ${'-h, --help'.padEnd(30)} Show this help message`);
179
+ return lines.join('\n');
180
+ }
181
+ function parseCliArgs(args) {
182
+ return util.parseArgs({ args, options: cliOptions, allowPositionals: true });
183
+ }
184
+ function resolveConfig(fileConfig, { values: cliValues, positionals }) {
185
+ let fileCoverage = typeof fileConfig.coverage === 'boolean' ? {} : fileConfig.coverage || {};
186
+ return {
187
+ 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,
195
+ },
196
+ browser: {
197
+ echo: cliValues['browser.echo'] ?? fileConfig.browser?.echo ?? defaultValues.browser.echo,
198
+ open: cliValues['browser.open'] ?? fileConfig.browser?.open ?? defaultValues.browser.open,
199
+ },
200
+ concurrency: Number(cliValues.concurrency ?? fileConfig.concurrency ?? defaultValues.concurrency),
201
+ coverage: cliValues.coverage === true || !!fileConfig.coverage
202
+ ? {
203
+ 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,
210
+ statements: cliValues['coverage.statements'] !== undefined
211
+ ? Number(cliValues['coverage.statements'])
212
+ : fileCoverage.statements !== undefined
213
+ ? Number(fileCoverage.statements)
214
+ : undefined,
215
+ lines: cliValues['coverage.lines'] !== undefined
216
+ ? Number(cliValues['coverage.lines'])
217
+ : fileCoverage.lines !== undefined
218
+ ? Number(fileCoverage.lines)
219
+ : undefined,
220
+ branches: cliValues['coverage.branches'] !== undefined
221
+ ? Number(cliValues['coverage.branches'])
222
+ : fileCoverage.branches !== undefined
223
+ ? Number(fileCoverage.branches)
224
+ : undefined,
225
+ functions: cliValues['coverage.functions'] !== undefined
226
+ ? Number(cliValues['coverage.functions'])
227
+ : fileCoverage.functions !== undefined
228
+ ? Number(fileCoverage.functions)
229
+ : undefined,
230
+ }
231
+ : undefined,
232
+ setup: cliValues.setup ?? fileConfig.setup ?? defaultValues.setup,
233
+ playwrightConfig: cliValues.playwrightConfig ?? fileConfig.playwrightConfig ?? defaultValues.playwrightConfig,
234
+ project: cliValues.project ?? fileConfig.project ?? defaultValues.project,
235
+ reporter: cliValues.reporter ?? fileConfig.reporter ?? defaultValues.reporter,
236
+ type: cliValues.type ?? fileConfig.type ?? defaultValues.type,
237
+ watch: cliValues.watch ?? fileConfig.watch ?? defaultValues.watch,
238
+ };
239
+ }
240
+ async function loadConfigFile(configPath, cwd) {
241
+ let candidates = configPath
242
+ ? [path.resolve(cwd, configPath)]
243
+ : [path.join(cwd, 'remix-test.config.ts'), path.join(cwd, 'remix-test.config.js')];
244
+ for (let candidate of candidates) {
245
+ try {
246
+ await fsp.access(candidate);
247
+ let mod = await importModule(candidate, import.meta);
248
+ return mod.default ?? mod;
249
+ }
250
+ catch {
251
+ // not found or failed to load — try next
252
+ }
253
+ }
254
+ return {};
255
+ }
@@ -0,0 +1,93 @@
1
+ import type { Browser, Page } from 'playwright';
2
+ import type { V8CoverageEntry } from './coverage.ts';
3
+ import { type FakeTimers } from './fake-timers.ts';
4
+ import { type MockCall, type MockContext, type MockFunction } from './mock.ts';
5
+ import type { getPlaywrightPageOptions } from './playwright.ts';
6
+ /**
7
+ * The shape `t.serve()` consumes. Matches the result of `createTestServer`
8
+ * from `@remix-run/node-fetch-server/test`, but any object with a `baseUrl`
9
+ * and async `close()` works.
10
+ */
11
+ export interface TestServer {
12
+ baseUrl: string;
13
+ close(): Promise<void>;
14
+ }
15
+ /**
16
+ * Test Context providing utilities for testing via remix-test. The context is
17
+ * passed as the first argument to the {@link test}/{@link it} functions.
18
+ *
19
+ * @example
20
+ * describe('my test suite', () => {
21
+ * it('my test case', async (t) => {
22
+ * let mockFn = t.mock.fn(() => 'mocked value')
23
+ * // ...
24
+ * })
25
+ * })
26
+ */
27
+ export interface TestContext {
28
+ /**
29
+ * Registers a cleanup function to be called after the test completes.
30
+ *
31
+ * @param {() => void} fn - The cleanup function to execute
32
+ * @returns {void}
33
+ */
34
+ after(fn: () => void): void;
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.
38
+ */
39
+ mock: {
40
+ /**
41
+ * Creates a mock function with an optional implementation.
42
+ *
43
+ * @template T - The function type to be mocked
44
+ * @param {T} [impl] - Optional custom implementation for the mock
45
+ * @returns {MockFunction<T>} A mock function instance
46
+ */
47
+ fn<T extends (...args: any[]) => any>(impl?: T): MockFunction<T>;
48
+ /**
49
+ * Replaces `obj[methodName]` with a mock and records every call. The
50
+ * original method is restored automatically after the test completes.
51
+ *
52
+ * @template T - The object type
53
+ * @template K - The method key of the object
54
+ * @param {T} obj - The object to mock
55
+ * @param {K} methodName - The method name to mock
56
+ * @param {Function} [impl] - Optional implementation override (must be a function)
57
+ * @returns {MockFunction} A mock function instance for the mocked method
58
+ */
59
+ method<T extends object, K extends keyof T>(obj: T, methodName: K, impl?: Function): MockFunction;
60
+ };
61
+ /**
62
+ * Activates fake timers for testing time-dependent code.
63
+ *
64
+ * @returns {FakeTimers} A fake timers instance for controlling time
65
+ */
66
+ useFakeTimers(): FakeTimers;
67
+ /**
68
+ * Wires a running test server up to a Playwright page so the test can drive
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.
72
+ *
73
+ * @param server - The running server the page should target
74
+ * @returns A `Page` whose `baseURL` is set to `server.baseUrl`.
75
+ */
76
+ serve(server: TestServer): Promise<Page>;
77
+ }
78
+ export interface CreateTestContextOptions {
79
+ addE2ECoverageEntries: (value: {
80
+ entries: V8CoverageEntry[];
81
+ baseUrl: string;
82
+ }) => void;
83
+ browser: Browser;
84
+ coverage: boolean;
85
+ open: boolean;
86
+ playwrightPageOptions: ReturnType<typeof getPlaywrightPageOptions>;
87
+ }
88
+ export declare function createTestContext(options?: CreateTestContextOptions): {
89
+ testContext: TestContext;
90
+ cleanup(): Promise<void>;
91
+ };
92
+ export type { MockCall, MockContext, MockFunction };
93
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/lib/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AACpD,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAA;AACpE,OAAO,EAAQ,KAAK,QAAQ,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,WAAW,CAAA;AACpF,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAA;AAE/D;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,WAAW;IAC1B;;;;;OAKG;IACH,KAAK,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAAA;IAE3B;;;OAGG;IACH,IAAI,EAAE;QACJ;;;;;;WAMG;QACH,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAAA;QAEhE;;;;;;;;;;WAUG;QACH,MAAM,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,CAAC,EACxC,GAAG,EAAE,CAAC,EACN,UAAU,EAAE,CAAC,EACb,IAAI,CAAC,EAAE,QAAQ,GACd,YAAY,CAAA;KAChB,CAAA;IAED;;;;OAIG;IACH,aAAa,IAAI,UAAU,CAAA;IAE3B;;;;;;;;OAQG;IACH,KAAK,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACzC;AAED,MAAM,WAAW,wBAAwB;IACvC,qBAAqB,EAAE,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,eAAe,EAAE,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAA;IACvF,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,OAAO,CAAA;IACjB,IAAI,EAAE,OAAO,CAAA;IACb,qBAAqB,EAAE,UAAU,CAAC,OAAO,wBAAwB,CAAC,CAAA;CACnE;AAED,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,wBAAwB,GAAG;IACrE,WAAW,EAAE,WAAW,CAAA;IACxB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACzB,CAkEA;AAED,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,CAAA"}
@@ -0,0 +1,65 @@
1
+ import { createFakeTimers } from "./fake-timers.js";
2
+ import { mock } from "./mock.js";
3
+ export function createTestContext(options) {
4
+ let cleanups = [];
5
+ let testContext = {
6
+ mock: {
7
+ fn: mock.fn,
8
+ method(obj, methodName, impl) {
9
+ let mockFn = mock.method(obj, methodName, impl);
10
+ if (mockFn.mock.restore)
11
+ cleanups.push(mockFn.mock.restore);
12
+ return mockFn;
13
+ },
14
+ },
15
+ after(fn) {
16
+ cleanups.push(fn);
17
+ },
18
+ useFakeTimers() {
19
+ let timers = createFakeTimers();
20
+ cleanups.push(timers.restore);
21
+ return timers;
22
+ },
23
+ async serve(server) {
24
+ if (!options || !options.browser) {
25
+ throw new Error('t.serve() is only available in E2E test suites');
26
+ }
27
+ let page = await options.browser.newPage({
28
+ ...options.playwrightPageOptions,
29
+ baseURL: server.baseUrl,
30
+ });
31
+ if (options.playwrightPageOptions?.navigationTimeout != null) {
32
+ page.setDefaultNavigationTimeout(options.playwrightPageOptions.navigationTimeout);
33
+ }
34
+ if (options.playwrightPageOptions?.actionTimeout != null) {
35
+ page.setDefaultTimeout(options.playwrightPageOptions.actionTimeout);
36
+ }
37
+ let coverageEnabled = options.coverage && options.browser.browserType().name() === 'chromium';
38
+ if (coverageEnabled) {
39
+ await page.coverage.startJSCoverage({ resetOnNavigation: false });
40
+ cleanups.push(async () => {
41
+ let entries = await page.coverage.stopJSCoverage();
42
+ options.addE2ECoverageEntries?.({
43
+ entries: entries,
44
+ baseUrl: server.baseUrl,
45
+ });
46
+ });
47
+ }
48
+ cleanups.push(async () => {
49
+ if (!options.open) {
50
+ await page.close();
51
+ }
52
+ await server.close();
53
+ });
54
+ return page;
55
+ },
56
+ };
57
+ return {
58
+ testContext,
59
+ async cleanup() {
60
+ for (let fn of cleanups)
61
+ await fn();
62
+ cleanups.length = 0;
63
+ },
64
+ };
65
+ }
@@ -0,0 +1,16 @@
1
+ export declare function load(url: string, context: {
2
+ format?: string;
3
+ }, nextLoad: (url: string, context: {
4
+ format?: string;
5
+ }) => Promise<{
6
+ format: string;
7
+ source: string;
8
+ }>): Promise<{
9
+ format: string;
10
+ source: string;
11
+ } | {
12
+ format: string;
13
+ source: string;
14
+ shortCircuit: boolean;
15
+ }>;
16
+ //# sourceMappingURL=coverage-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage-loader.d.ts","sourceRoot":"","sources":["../../src/lib/coverage-loader.ts"],"names":[],"mappings":"AAYA,wBAAsB,IAAI,CACxB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,EAC5B,QAAQ,EAAE,CACR,GAAG,EAAE,MAAM,EACX,OAAO,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,KACzB,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;;;;;;;GAYjD"}
@@ -0,0 +1,20 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { transformTypeScript } from "./ts-transform.js";
4
+ // Custom ESM loader hook for TypeScript files.
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
8
+ // cleanly to TypeScript source lines via the inline source map, giving
9
+ // accurate per-line coverage rather than collapsing multiple statements onto
10
+ // a single minified line.
11
+ export async function load(url, context, nextLoad) {
12
+ let cleanUrl = url.includes('?') ? url.slice(0, url.indexOf('?')) : url;
13
+ if (!cleanUrl.endsWith('.ts') && !cleanUrl.endsWith('.tsx')) {
14
+ return nextLoad(url, context);
15
+ }
16
+ let filePath = fileURLToPath(cleanUrl);
17
+ let source = await readFile(filePath, 'utf-8');
18
+ let { code } = await transformTypeScript(source, filePath);
19
+ return { format: 'module', source: code, shortCircuit: true };
20
+ }
@@ -0,0 +1,28 @@
1
+ import type { createCoverageMap as CreateCoverageMap } from 'istanbul-lib-coverage';
2
+ export interface CoverageConfig {
3
+ dir: string;
4
+ include?: string[];
5
+ exclude?: string[];
6
+ statements?: number;
7
+ lines?: number;
8
+ branches?: number;
9
+ functions?: number;
10
+ }
11
+ export interface V8CoverageEntry {
12
+ url: string;
13
+ source?: string;
14
+ functions: Array<{
15
+ functionName: string;
16
+ isBlockCoverage: boolean;
17
+ ranges: Array<{
18
+ startOffset: number;
19
+ endOffset: number;
20
+ count: number;
21
+ }>;
22
+ }>;
23
+ }
24
+ export type CoverageMap = ReturnType<typeof CreateCoverageMap>;
25
+ export declare function collectServerCoverageMap(coverageDataDir: string, cwd: string, testFiles: Set<string>): Promise<CoverageMap | null>;
26
+ export declare function collectCoverageMapFromPlaywright(entries: V8CoverageEntry[], rootDir: string, testFiles: Set<string>, resolveRelativePath: (url: string) => Promise<string | null>): Promise<CoverageMap | null>;
27
+ export declare function generateCombinedCoverageReport(maps: (CoverageMap | null | undefined)[], cwd: string, config: CoverageConfig): Promise<boolean>;
28
+ //# sourceMappingURL=coverage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage.d.ts","sourceRoot":"","sources":["../../src/lib/coverage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,IAAI,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAsCnF,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,KAAK,CAAC;QACf,YAAY,EAAE,MAAM,CAAA;QACpB,eAAe,EAAE,OAAO,CAAA;QACxB,MAAM,EAAE,KAAK,CAAC;YAAE,WAAW,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KACzE,CAAC,CAAA;CACH;AAED,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAA;AAmI9D,wBAAsB,wBAAwB,CAC5C,eAAe,EAAE,MAAM,EACvB,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,GACrB,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAuD7B;AAED,wBAAsB,gCAAgC,CACpD,OAAO,EAAE,eAAe,EAAE,EAC1B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,EACtB,mBAAmB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,GAC3D,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CA2C7B;AAED,wBAAsB,8BAA8B,CAClD,IAAI,EAAE,CAAC,WAAW,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,EACxC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC,OAAO,CAAC,CAelB"}