@remix-run/test 0.2.0 → 0.3.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 (60) hide show
  1. package/README.md +39 -33
  2. package/dist/app/client/entry.js +4 -0
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +68 -23
  5. package/dist/index.d.ts +1 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/lib/config.d.ts +35 -21
  8. package/dist/lib/config.d.ts.map +1 -1
  9. package/dist/lib/config.js +73 -33
  10. package/dist/lib/fake-timers.d.ts +7 -0
  11. package/dist/lib/fake-timers.d.ts.map +1 -1
  12. package/dist/lib/fake-timers.js +27 -8
  13. package/dist/lib/import-module.d.ts.map +1 -1
  14. package/dist/lib/import-module.js +11 -2
  15. package/dist/lib/reporters/dot.d.ts.map +1 -1
  16. package/dist/lib/reporters/dot.js +10 -0
  17. package/dist/lib/reporters/files.d.ts.map +1 -1
  18. package/dist/lib/reporters/files.js +10 -0
  19. package/dist/lib/reporters/spec.d.ts.map +1 -1
  20. package/dist/lib/reporters/spec.js +10 -0
  21. package/dist/lib/reporters/tap.d.ts.map +1 -1
  22. package/dist/lib/reporters/tap.js +10 -0
  23. package/dist/lib/runner-browser.d.ts.map +1 -1
  24. package/dist/lib/runner-browser.js +6 -0
  25. package/dist/lib/runner.d.ts +18 -1
  26. package/dist/lib/runner.d.ts.map +1 -1
  27. package/dist/lib/runner.js +187 -38
  28. package/dist/lib/worker-e2e-file.d.ts +11 -0
  29. package/dist/lib/worker-e2e-file.d.ts.map +1 -0
  30. package/dist/lib/worker-e2e-file.js +69 -0
  31. package/dist/lib/worker-e2e.js +11 -47
  32. package/dist/lib/worker-process.d.ts +2 -0
  33. package/dist/lib/worker-process.d.ts.map +1 -0
  34. package/dist/lib/worker-process.js +55 -0
  35. package/dist/lib/worker-results.d.ts +3 -0
  36. package/dist/lib/worker-results.d.ts.map +1 -0
  37. package/dist/lib/worker-results.js +20 -0
  38. package/dist/lib/worker-server.d.ts +10 -0
  39. package/dist/lib/worker-server.d.ts.map +1 -0
  40. package/dist/lib/worker-server.js +113 -0
  41. package/dist/lib/worker.js +6 -55
  42. package/package.json +4 -4
  43. package/src/app/client/entry.ts +4 -0
  44. package/src/cli.ts +91 -28
  45. package/src/index.ts +1 -1
  46. package/src/lib/config.ts +124 -58
  47. package/src/lib/fake-timers.ts +33 -8
  48. package/src/lib/import-module.ts +12 -2
  49. package/src/lib/reporters/dot.ts +9 -0
  50. package/src/lib/reporters/files.ts +9 -0
  51. package/src/lib/reporters/spec.ts +9 -0
  52. package/src/lib/reporters/tap.ts +9 -0
  53. package/src/lib/runner-browser.ts +6 -0
  54. package/src/lib/runner.ts +253 -50
  55. package/src/lib/worker-e2e-file.ts +98 -0
  56. package/src/lib/worker-e2e.ts +14 -51
  57. package/src/lib/worker-process.ts +69 -0
  58. package/src/lib/worker-results.ts +22 -0
  59. package/src/lib/worker-server.ts +123 -0
  60. package/src/lib/worker.ts +7 -47
@@ -0,0 +1,69 @@
1
+ import { runE2ETestFile } from './worker-e2e-file.ts'
2
+ import type { TestResults } from './reporters/results.ts'
3
+ import { runServerTestFile } from './worker-server.ts'
4
+ import { createFailedResults } from './worker-results.ts'
5
+
6
+ const workerData = await readWorkerData()
7
+
8
+ const results = await runWorkerProcessFile(workerData)
9
+
10
+ if (results) {
11
+ await sendResults(results)
12
+ }
13
+
14
+ if (process.connected) {
15
+ process.disconnect()
16
+ }
17
+
18
+ process.exitCode = 0
19
+
20
+ function readWorkerData(): Promise<unknown> {
21
+ return new Promise((resolve, reject) => {
22
+ function cleanup() {
23
+ process.off('message', onMessage)
24
+ process.off('disconnect', onDisconnect)
25
+ }
26
+
27
+ function onMessage(value: unknown) {
28
+ cleanup()
29
+ resolve(value)
30
+ }
31
+
32
+ function onDisconnect() {
33
+ cleanup()
34
+ reject(new Error('Test worker process disconnected'))
35
+ }
36
+
37
+ process.once('message', onMessage)
38
+ process.once('disconnect', onDisconnect)
39
+ })
40
+ }
41
+
42
+ async function runWorkerProcessFile(value: unknown): Promise<TestResults | undefined> {
43
+ try {
44
+ if (!isRecord(value) || (value.type !== 'server' && value.type !== 'e2e')) {
45
+ throw new Error('Invalid test worker process data')
46
+ }
47
+
48
+ return value.type === 'e2e'
49
+ ? await runE2ETestFile(value, sendResults)
50
+ : await runServerTestFile(value)
51
+ } catch (error) {
52
+ return createFailedResults(error)
53
+ }
54
+ }
55
+
56
+ async function sendResults(results: TestResults): Promise<void> {
57
+ if (!process.send) {
58
+ throw new Error('Test worker process is missing an IPC channel')
59
+ }
60
+
61
+ let send = process.send.bind(process)
62
+ await new Promise<void>((resolve, reject) => {
63
+ send(results, undefined, undefined, (error) => (error ? reject(error) : resolve()))
64
+ })
65
+ }
66
+
67
+ function isRecord(value: unknown): value is Record<string, unknown> {
68
+ return typeof value === 'object' && value !== null
69
+ }
@@ -0,0 +1,22 @@
1
+ import type { TestResults } from './reporters/results.ts'
2
+
3
+ export function createFailedResults(error: unknown): TestResults {
4
+ return {
5
+ passed: 0,
6
+ failed: 1,
7
+ skipped: 0,
8
+ todo: 0,
9
+ tests: [
10
+ {
11
+ name: '',
12
+ suiteName: '',
13
+ status: 'failed',
14
+ duration: 0,
15
+ error: {
16
+ message: error instanceof Error ? error.message : String(error),
17
+ stack: error instanceof Error ? error.stack : undefined,
18
+ },
19
+ },
20
+ ],
21
+ }
22
+ }
@@ -0,0 +1,123 @@
1
+ import * as mod from 'node:module'
2
+ import { IS_RUNNING_FROM_SRC } from './config.ts'
3
+ import { importModule } from './import-module.ts'
4
+ import type { CoverageConfig } from './coverage.ts'
5
+ import type { TestResults } from './reporters/results.ts'
6
+ import { runTests } from './executor.ts'
7
+ import { IS_BUN } from './runtime.ts'
8
+ import { createFailedResults } from './worker-results.ts'
9
+
10
+ export interface ServerTestWorkerData {
11
+ file: string
12
+ coverage?: CoverageConfig
13
+ }
14
+
15
+ export async function runServerTestFile(value: unknown): Promise<TestResults> {
16
+ let workerData: ServerTestWorkerData | undefined
17
+
18
+ try {
19
+ workerData = parseServerTestWorkerData(value)
20
+
21
+ // When coverage is enabled in Node, we use a coverage-friendly TypeScript loader which
22
+ // replaces tsx's minified transformation with a non-minified esbuild transform
23
+ // so V8 coverage byte offsets align with readable source lines. This hook runs
24
+ // before the inherited tsx hook (hooks are LIFO), so it intercepts .ts imports and
25
+ // short-circuits before tsx transforms them.
26
+ if (workerData.coverage && !IS_BUN) {
27
+ // Ensure we load the right file whether we're running in the monorepo (TS) or
28
+ // from a published package (JS)
29
+ let ext = IS_RUNNING_FROM_SRC ? '.ts' : '.js'
30
+ mod.register(new URL(`./coverage-loader${ext}`, import.meta.url), import.meta.url)
31
+ await import(workerData.file)
32
+ } else {
33
+ await importModule(workerData.file, import.meta)
34
+ }
35
+
36
+ let results = await runTests()
37
+ await takeCoverage(workerData.coverage)
38
+ return results
39
+ } catch (e) {
40
+ try {
41
+ await takeCoverage(workerData?.coverage)
42
+ } catch (coverageError) {
43
+ e = coverageError
44
+ }
45
+
46
+ return createFailedResults(e)
47
+ }
48
+ }
49
+
50
+ function parseServerTestWorkerData(value: unknown): ServerTestWorkerData {
51
+ if (!isRecord(value) || typeof value.file !== 'string') {
52
+ throw new Error('Invalid server test worker data')
53
+ }
54
+
55
+ return {
56
+ file: value.file,
57
+ coverage: parseCoverageConfig(value.coverage),
58
+ }
59
+ }
60
+
61
+ export function isRecord(value: unknown): value is Record<string, unknown> {
62
+ return typeof value === 'object' && value !== null
63
+ }
64
+
65
+ export function parseCoverageConfig(value: unknown): CoverageConfig | undefined {
66
+ if (value === undefined) {
67
+ return undefined
68
+ }
69
+
70
+ if (!isRecord(value) || typeof value.dir !== 'string') {
71
+ throw new Error('Invalid server test worker coverage config')
72
+ }
73
+
74
+ let coverage: CoverageConfig = {
75
+ dir: value.dir,
76
+ }
77
+ let include = parseStringArray(value.include, 'include')
78
+ let exclude = parseStringArray(value.exclude, 'exclude')
79
+ let statements = parseNumber(value.statements, 'statements')
80
+ let lines = parseNumber(value.lines, 'lines')
81
+ let branches = parseNumber(value.branches, 'branches')
82
+ let functions = parseNumber(value.functions, 'functions')
83
+
84
+ if (include) coverage.include = include
85
+ if (exclude) coverage.exclude = exclude
86
+ if (statements !== undefined) coverage.statements = statements
87
+ if (lines !== undefined) coverage.lines = lines
88
+ if (branches !== undefined) coverage.branches = branches
89
+ if (functions !== undefined) coverage.functions = functions
90
+
91
+ return coverage
92
+ }
93
+
94
+ function parseStringArray(value: unknown, name: string): string[] | undefined {
95
+ if (value === undefined) {
96
+ return undefined
97
+ }
98
+
99
+ if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {
100
+ throw new Error(`Invalid server test worker coverage ${name}`)
101
+ }
102
+
103
+ return value
104
+ }
105
+
106
+ function parseNumber(value: unknown, name: string): number | undefined {
107
+ if (value === undefined) {
108
+ return undefined
109
+ }
110
+
111
+ if (typeof value !== 'number') {
112
+ throw new Error(`Invalid server test worker coverage ${name}`)
113
+ }
114
+
115
+ return value
116
+ }
117
+
118
+ async function takeCoverage(coverage: CoverageConfig | undefined): Promise<void> {
119
+ if (coverage && !IS_BUN) {
120
+ let v8 = await import('node:v8')
121
+ v8.takeCoverage()
122
+ }
123
+ }
package/src/lib/worker.ts CHANGED
@@ -1,50 +1,10 @@
1
- import * as mod from 'node:module'
2
- import * as path from 'node:path'
3
1
  import { parentPort, workerData } from 'node:worker_threads'
4
- import { runTests } from './executor.ts'
5
- import { importModule } from './import-module.ts'
6
- import type { TestResults } from './reporters/results.ts'
7
- import { IS_BUN } from './runtime.ts'
8
- import { IS_RUNNING_FROM_SRC } from './config.ts'
2
+ import { runServerTestFile } from './worker-server.ts'
9
3
 
10
- try {
11
- // When coverage is enabled in Node, we use a coverage-friendly TypeScript loader which
12
- // replaces tsx's minified transformation with a non-minified esbuild transform
13
- // so V8 coverage byte offsets align with readable source lines. This hook runs
14
- // before the inherited tsx hook (hooks are LIFO), so it intercepts .ts imports and
15
- // short-circuits before tsx transforms them.
16
- if (workerData.coverage && !IS_BUN) {
17
- // Ensure we load the right file whether we're running in the monorepo (TS) or
18
- // from a published package (JS)
19
- let ext = IS_RUNNING_FROM_SRC ? '.ts' : '.js'
20
- mod.register(new URL(`./coverage-loader${ext}`, import.meta.url), import.meta.url)
21
- await import(workerData.file)
22
- } else {
23
- await importModule(workerData.file, import.meta)
24
- }
25
-
26
- let results = await runTests()
27
- parentPort!.postMessage(results)
28
- process.exit(0)
29
- } catch (e) {
30
- let results: TestResults = {
31
- passed: 0,
32
- failed: 1,
33
- skipped: 0,
34
- todo: 0,
35
- tests: [
36
- {
37
- name: '',
38
- suiteName: '',
39
- status: 'failed',
40
- duration: 0,
41
- error: {
42
- message: e instanceof Error ? e.message : String(e),
43
- stack: e instanceof Error ? e.stack : undefined,
44
- },
45
- },
46
- ],
47
- }
48
- parentPort!.postMessage(results)
49
- process.exit(0)
4
+ if (!parentPort) {
5
+ throw new Error('Server test worker is missing a parent port')
50
6
  }
7
+
8
+ const results = await runServerTestFile(workerData)
9
+ parentPort.postMessage(results)
10
+ process.exit(0)