@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.
- package/README.md +43 -44
- package/dist/app/client/entry.js +4 -0
- package/dist/app/server.d.ts.map +1 -1
- package/dist/app/server.js +10 -10
- package/dist/cli.d.ts +30 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +87 -23
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/lib/config.d.ts +55 -21
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +82 -33
- package/dist/lib/context.d.ts +5 -5
- package/dist/lib/coverage-loader.js +2 -2
- package/dist/lib/coverage.js +1 -1
- package/dist/lib/fake-timers.d.ts +39 -0
- package/dist/lib/fake-timers.d.ts.map +1 -1
- package/dist/lib/fake-timers.js +27 -8
- package/dist/lib/framework.d.ts +12 -6
- package/dist/lib/framework.d.ts.map +1 -1
- package/dist/lib/framework.js +24 -12
- package/dist/lib/import-module.d.ts.map +1 -1
- package/dist/lib/import-module.js +13 -3
- package/dist/lib/reporters/dot.d.ts.map +1 -1
- package/dist/lib/reporters/dot.js +10 -0
- package/dist/lib/reporters/files.d.ts.map +1 -1
- package/dist/lib/reporters/files.js +10 -0
- package/dist/lib/reporters/results.d.ts +1 -1
- package/dist/lib/reporters/results.d.ts.map +1 -1
- package/dist/lib/reporters/spec.d.ts.map +1 -1
- package/dist/lib/reporters/spec.js +10 -0
- package/dist/lib/reporters/tap.d.ts.map +1 -1
- package/dist/lib/reporters/tap.js +10 -0
- package/dist/lib/runner-browser.d.ts.map +1 -1
- package/dist/lib/runner-browser.js +40 -2
- package/dist/lib/runner.d.ts +18 -1
- package/dist/lib/runner.d.ts.map +1 -1
- package/dist/lib/runner.js +187 -38
- package/dist/lib/worker-e2e-file.d.ts +11 -0
- package/dist/lib/worker-e2e-file.d.ts.map +1 -0
- package/dist/lib/worker-e2e-file.js +69 -0
- package/dist/lib/worker-e2e.js +11 -47
- package/dist/lib/worker-process.d.ts +2 -0
- package/dist/lib/worker-process.d.ts.map +1 -0
- package/dist/lib/worker-process.js +55 -0
- package/dist/lib/worker-results.d.ts +3 -0
- package/dist/lib/worker-results.d.ts.map +1 -0
- package/dist/lib/worker-results.js +20 -0
- package/dist/lib/worker-server.d.ts +10 -0
- package/dist/lib/worker-server.d.ts.map +1 -0
- package/dist/lib/worker-server.js +112 -0
- package/dist/lib/worker.js +6 -55
- package/package.json +5 -5
- package/src/app/client/entry.ts +4 -0
- package/src/app/server.ts +11 -10
- package/src/cli.ts +121 -28
- package/src/index.ts +1 -1
- package/src/lib/config.ts +144 -58
- package/src/lib/context.ts +5 -5
- package/src/lib/coverage-loader.ts +2 -2
- package/src/lib/coverage.ts +1 -1
- package/src/lib/fake-timers.ts +65 -8
- package/src/lib/framework.ts +53 -36
- package/src/lib/import-module.ts +14 -3
- package/src/lib/reporters/dot.ts +9 -0
- package/src/lib/reporters/files.ts +9 -0
- package/src/lib/reporters/results.ts +1 -1
- package/src/lib/reporters/spec.ts +9 -0
- package/src/lib/reporters/tap.ts +9 -0
- package/src/lib/runner-browser.ts +46 -2
- package/src/lib/runner.ts +253 -50
- package/src/lib/ts-transform.ts +1 -1
- package/src/lib/worker-e2e-file.ts +98 -0
- package/src/lib/worker-e2e.ts +14 -51
- package/src/lib/worker-process.ts +69 -0
- package/src/lib/worker-results.ts +22 -0
- package/src/lib/worker-server.ts +123 -0
- package/src/lib/worker.ts +7 -47
- package/tsconfig.json +6 -3
package/src/lib/worker-e2e.ts
CHANGED
|
@@ -1,54 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { importModule } from './import-module.ts'
|
|
4
|
-
import {
|
|
5
|
-
getBrowserLauncher,
|
|
6
|
-
getPlaywrightLaunchOptions,
|
|
7
|
-
getPlaywrightPageOptions,
|
|
8
|
-
} from './playwright.ts'
|
|
9
|
-
import type { TestResults } from './reporters/results.ts'
|
|
1
|
+
import { parentPort, workerData } from 'node:worker_threads'
|
|
2
|
+
import { runE2ETestFile } from './worker-e2e-file.ts'
|
|
10
3
|
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
if (!parentPort) {
|
|
5
|
+
throw new Error('E2E test worker is missing a parent port')
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const port = parentPort
|
|
9
|
+
const results = await runE2ETestFile(workerData, (openResults) => {
|
|
10
|
+
port.postMessage(openResults)
|
|
11
|
+
})
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
let browser = await launcher.launch(opts)
|
|
17
|
-
try {
|
|
18
|
-
let results = await runTests({
|
|
19
|
-
browser,
|
|
20
|
-
open: workerData.open,
|
|
21
|
-
playwrightPageOptions: getPlaywrightPageOptions(workerData.playwrightUseOpts),
|
|
22
|
-
coverage: workerData.coverage,
|
|
23
|
-
})
|
|
24
|
-
parentPort!.postMessage(results)
|
|
25
|
-
if (workerData.open) {
|
|
26
|
-
console.log('\nBrowser is open. Press Ctrl+C to close.')
|
|
27
|
-
await new Promise<void>((resolve) => browser.on('disconnected', () => resolve()))
|
|
28
|
-
}
|
|
29
|
-
} finally {
|
|
30
|
-
await browser.close()
|
|
31
|
-
}
|
|
32
|
-
process.exit(0)
|
|
33
|
-
} catch (e) {
|
|
34
|
-
let results: TestResults = {
|
|
35
|
-
passed: 0,
|
|
36
|
-
failed: 1,
|
|
37
|
-
skipped: 0,
|
|
38
|
-
todo: 0,
|
|
39
|
-
tests: [
|
|
40
|
-
{
|
|
41
|
-
name: '',
|
|
42
|
-
suiteName: '',
|
|
43
|
-
status: 'failed',
|
|
44
|
-
duration: 0,
|
|
45
|
-
error: {
|
|
46
|
-
message: e instanceof Error ? e.message : String(e),
|
|
47
|
-
stack: e instanceof Error ? e.stack : undefined,
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
],
|
|
51
|
-
}
|
|
52
|
-
parentPort!.postMessage(results)
|
|
53
|
-
process.exit(0)
|
|
13
|
+
if (results) {
|
|
14
|
+
port.postMessage(results)
|
|
54
15
|
}
|
|
16
|
+
|
|
17
|
+
process.exit(0)
|
|
@@ -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 with
|
|
22
|
+
// an un-minified esbuild transform so V8 coverage byte offsets align with readable
|
|
23
|
+
// source lines.
|
|
24
|
+
if (workerData.coverage && !IS_BUN) {
|
|
25
|
+
// Ensure we load the right file whether we're running in the monorepo (TS) or
|
|
26
|
+
// from a published package (JS)
|
|
27
|
+
let ext = IS_RUNNING_FROM_SRC ? '.ts' : '.js'
|
|
28
|
+
mod.register(new URL(`./coverage-loader${ext}`, import.meta.url), import.meta.url)
|
|
29
|
+
await import(workerData.file)
|
|
30
|
+
} else {
|
|
31
|
+
await importModule(workerData.file, import.meta)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let results = await runTests()
|
|
35
|
+
await takeCoverage(workerData.coverage)
|
|
36
|
+
return results
|
|
37
|
+
} catch (error) {
|
|
38
|
+
let failure = error
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
await takeCoverage(workerData?.coverage)
|
|
42
|
+
} catch (coverageError) {
|
|
43
|
+
failure = coverageError
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return createFailedResults(failure)
|
|
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 {
|
|
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
|
-
|
|
11
|
-
|
|
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)
|
package/tsconfig.json
CHANGED
|
@@ -3,14 +3,17 @@
|
|
|
3
3
|
"strict": true,
|
|
4
4
|
"lib": ["ES2024", "DOM", "DOM.Iterable"],
|
|
5
5
|
"types": ["node", "dom-navigation"],
|
|
6
|
-
"module": "
|
|
7
|
-
"moduleResolution": "
|
|
6
|
+
"module": "NodeNext",
|
|
7
|
+
"moduleResolution": "NodeNext",
|
|
8
8
|
"target": "ESNext",
|
|
9
9
|
"allowImportingTsExtensions": true,
|
|
10
10
|
"rewriteRelativeImportExtensions": true,
|
|
11
11
|
"verbatimModuleSyntax": true,
|
|
12
|
+
"erasableSyntaxOnly": true,
|
|
13
|
+
"isolatedModules": true,
|
|
12
14
|
"skipLibCheck": true,
|
|
13
15
|
"jsx": "react-jsx",
|
|
14
16
|
"jsxImportSource": "@remix-run/ui"
|
|
15
|
-
}
|
|
17
|
+
},
|
|
18
|
+
"exclude": ["dist"]
|
|
16
19
|
}
|