@remix-run/test 0.0.0 → 0.1.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/LICENSE +21 -0
- package/README.md +325 -2
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +171 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/lib/config.d.ts +60 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +152 -0
- package/dist/lib/context.d.ts +69 -0
- package/dist/lib/context.d.ts.map +1 -0
- package/dist/lib/context.js +49 -0
- package/dist/lib/e2e-server.d.ts +11 -0
- package/dist/lib/e2e-server.d.ts.map +1 -0
- package/dist/lib/e2e-server.js +15 -0
- package/dist/lib/executor.d.ts +27 -0
- package/dist/lib/executor.d.ts.map +1 -0
- package/dist/lib/executor.js +123 -0
- package/dist/lib/framework.d.ts +107 -0
- package/dist/lib/framework.d.ts.map +1 -0
- package/dist/lib/framework.js +198 -0
- package/dist/lib/framework.test.d.ts +2 -0
- package/dist/lib/framework.test.d.ts.map +1 -0
- package/dist/lib/framework.test.e2e.d.ts +2 -0
- package/dist/lib/framework.test.e2e.d.ts.map +1 -0
- package/dist/lib/framework.test.e2e.js +29 -0
- package/dist/lib/framework.test.js +283 -0
- package/dist/lib/mock.d.ts +52 -0
- package/dist/lib/mock.d.ts.map +1 -0
- package/dist/lib/mock.js +61 -0
- package/dist/lib/playwright.d.ts +15 -0
- package/dist/lib/playwright.d.ts.map +1 -0
- package/dist/lib/playwright.js +84 -0
- package/dist/lib/reporters/dot.d.ts +10 -0
- package/dist/lib/reporters/dot.d.ts.map +1 -0
- package/dist/lib/reporters/dot.js +55 -0
- package/dist/lib/reporters/files.d.ts +10 -0
- package/dist/lib/reporters/files.d.ts.map +1 -0
- package/dist/lib/reporters/files.js +70 -0
- package/dist/lib/reporters/index.d.ts +14 -0
- package/dist/lib/reporters/index.d.ts.map +1 -0
- package/dist/lib/reporters/index.js +18 -0
- package/dist/lib/reporters/spec.d.ts +10 -0
- package/dist/lib/reporters/spec.d.ts.map +1 -0
- package/dist/lib/reporters/spec.js +152 -0
- package/dist/lib/reporters/tap.d.ts +10 -0
- package/dist/lib/reporters/tap.d.ts.map +1 -0
- package/dist/lib/reporters/tap.js +54 -0
- package/dist/lib/runner.d.ts +9 -0
- package/dist/lib/runner.d.ts.map +1 -0
- package/dist/lib/runner.js +89 -0
- package/dist/lib/utils.d.ts +16 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +27 -0
- package/dist/lib/watcher.d.ts +5 -0
- package/dist/lib/watcher.d.ts.map +1 -0
- package/dist/lib/watcher.js +39 -0
- package/dist/lib/worker-e2e.d.ts +2 -0
- package/dist/lib/worker-e2e.d.ts.map +1 -0
- package/dist/lib/worker-e2e.js +48 -0
- package/dist/lib/worker.d.ts +2 -0
- package/dist/lib/worker.d.ts.map +1 -0
- package/dist/lib/worker.js +29 -0
- package/package.json +58 -5
- package/src/cli.ts +210 -0
- package/src/index.ts +15 -0
- package/src/lib/config.ts +231 -0
- package/src/lib/context.ts +126 -0
- package/src/lib/e2e-server.ts +28 -0
- package/src/lib/executor.ts +162 -0
- package/src/lib/framework.ts +251 -0
- package/src/lib/mock.ts +89 -0
- package/src/lib/playwright.ts +102 -0
- package/src/lib/reporters/dot.ts +57 -0
- package/src/lib/reporters/files.ts +76 -0
- package/src/lib/reporters/index.ts +28 -0
- package/src/lib/reporters/spec.ts +173 -0
- package/src/lib/reporters/tap.ts +58 -0
- package/src/lib/runner.ts +137 -0
- package/src/lib/utils.ts +40 -0
- package/src/lib/watcher.ts +46 -0
- package/src/lib/worker-e2e.ts +52 -0
- package/src/lib/worker.ts +30 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const noColor = process.env.CI === 'true' || !!process.env.NO_COLOR;
|
|
2
|
+
export const colors = {
|
|
3
|
+
reset: noColor ? '' : '\x1b[0m',
|
|
4
|
+
dim: noColor ? (s) => s : (s) => `\x1b[2m${s}\x1b[0m`,
|
|
5
|
+
green: noColor ? (s) => s : (s) => `\x1b[32m${s}\x1b[0m`,
|
|
6
|
+
red: noColor ? (s) => s : (s) => `\x1b[31m${s}\x1b[0m`,
|
|
7
|
+
cyan: noColor ? (s) => s : (s) => `\x1b[36m${s}\x1b[0m`,
|
|
8
|
+
yellow: noColor ? (s) => s : (s) => `\x1b[2m\x1b[33m${s}\x1b[0m`,
|
|
9
|
+
};
|
|
10
|
+
function normalizeFilePath(path) {
|
|
11
|
+
let locSuffix = path.match(/(:\d+:\d+)$/)?.[0] || '';
|
|
12
|
+
let normalized = path
|
|
13
|
+
.replace(/^\/scripts\/@pkg\/([^):]+)/g, (...args) => args[1])
|
|
14
|
+
.replace(/^\/scripts\/@test\/([^):]+)/g, (...args) => args[1])
|
|
15
|
+
.replace(/^\/scripts\/([^):]+)/g, (...args) => args[1])
|
|
16
|
+
.replace(/^\s+/, ' ') + locSuffix;
|
|
17
|
+
return path.includes('/@test/') ? `./${normalized}` : normalized;
|
|
18
|
+
}
|
|
19
|
+
export function normalizeLine(line) {
|
|
20
|
+
let match = line.match(/ \(.*\)$/);
|
|
21
|
+
if (match) {
|
|
22
|
+
let filepath = match[0].slice(2, -1);
|
|
23
|
+
filepath = filepath.replace(/https?:\/\/localhost:\d+\//g, '/');
|
|
24
|
+
return line.slice(0, match.index) + ' (' + normalizeFilePath(filepath) + ')';
|
|
25
|
+
}
|
|
26
|
+
return line;
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../../src/lib/watcher.ts"],"names":[],"mappings":"AAUA,wBAAgB,aAAa,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI;;;EAmC7D"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
function getFileModTime(file) {
|
|
3
|
+
try {
|
|
4
|
+
return fs.statSync(file).mtimeMs;
|
|
5
|
+
}
|
|
6
|
+
catch {
|
|
7
|
+
return 0;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function createWatcher(onChange) {
|
|
11
|
+
let watchers = new Set();
|
|
12
|
+
let fileModTimes = new Map();
|
|
13
|
+
function update(files) {
|
|
14
|
+
for (let watcher of watchers) {
|
|
15
|
+
watcher.close();
|
|
16
|
+
}
|
|
17
|
+
watchers.clear();
|
|
18
|
+
for (let file of files) {
|
|
19
|
+
fileModTimes.set(file, getFileModTime(file));
|
|
20
|
+
watchers.add(fs.watch(file, () => {
|
|
21
|
+
// macOS FSEvents can fire multiple callbacks per save (e.g. write +
|
|
22
|
+
// metadata flush). Guard with mtime so only a real content change
|
|
23
|
+
// triggers a rerun instead of every duplicate event.
|
|
24
|
+
let mtime = getFileModTime(file);
|
|
25
|
+
if (mtime !== fileModTimes.get(file)) {
|
|
26
|
+
fileModTimes.set(file, mtime);
|
|
27
|
+
onChange(file);
|
|
28
|
+
}
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function close() {
|
|
33
|
+
for (let watcher of watchers) {
|
|
34
|
+
watcher.close();
|
|
35
|
+
}
|
|
36
|
+
watchers.clear();
|
|
37
|
+
}
|
|
38
|
+
return { update, close };
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-e2e.d.ts","sourceRoot":"","sources":["../../src/lib/worker-e2e.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { workerData, parentPort } from 'node:worker_threads';
|
|
2
|
+
import { tsImport } from 'tsx/esm/api';
|
|
3
|
+
import { createServer } from "./e2e-server.js";
|
|
4
|
+
import { runTests } from "./executor.js";
|
|
5
|
+
import { getBrowserLauncher, getPlaywrightLaunchOptions, getPlaywrightPageOptions, } from "./playwright.js";
|
|
6
|
+
try {
|
|
7
|
+
await tsImport(workerData.file, import.meta.url);
|
|
8
|
+
let launcher = await getBrowserLauncher(workerData.playwrightUseOpts);
|
|
9
|
+
let opts = getPlaywrightLaunchOptions(workerData.playwrightUseOpts);
|
|
10
|
+
let browser = await launcher.launch(opts);
|
|
11
|
+
try {
|
|
12
|
+
let results = await runTests({
|
|
13
|
+
browser,
|
|
14
|
+
createServer,
|
|
15
|
+
open: workerData.open,
|
|
16
|
+
playwrightPageOptions: getPlaywrightPageOptions(workerData.playwrightUseOpts),
|
|
17
|
+
});
|
|
18
|
+
parentPort.postMessage(results);
|
|
19
|
+
if (workerData.open) {
|
|
20
|
+
console.log('\nBrowser is open. Press Ctrl+C to close.');
|
|
21
|
+
await new Promise((resolve) => browser.on('disconnected', () => resolve()));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
await browser.close();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch (e) {
|
|
29
|
+
let results = {
|
|
30
|
+
passed: 0,
|
|
31
|
+
failed: 1,
|
|
32
|
+
skipped: 0,
|
|
33
|
+
todo: 0,
|
|
34
|
+
tests: [
|
|
35
|
+
{
|
|
36
|
+
name: '',
|
|
37
|
+
suiteName: '',
|
|
38
|
+
status: 'failed',
|
|
39
|
+
duration: 0,
|
|
40
|
+
error: {
|
|
41
|
+
message: e instanceof Error ? e.message : String(e),
|
|
42
|
+
stack: e instanceof Error ? e.stack : undefined,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
parentPort.postMessage(results);
|
|
48
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/lib/worker.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { workerData, parentPort } from 'node:worker_threads';
|
|
2
|
+
import { tsImport } from 'tsx/esm/api';
|
|
3
|
+
import { runTests } from "./executor.js";
|
|
4
|
+
try {
|
|
5
|
+
await tsImport(workerData.file, import.meta.url);
|
|
6
|
+
let results = await runTests();
|
|
7
|
+
parentPort.postMessage(results);
|
|
8
|
+
}
|
|
9
|
+
catch (e) {
|
|
10
|
+
let results = {
|
|
11
|
+
passed: 0,
|
|
12
|
+
failed: 1,
|
|
13
|
+
skipped: 0,
|
|
14
|
+
todo: 0,
|
|
15
|
+
tests: [
|
|
16
|
+
{
|
|
17
|
+
name: '',
|
|
18
|
+
suiteName: '',
|
|
19
|
+
status: 'failed',
|
|
20
|
+
duration: 0,
|
|
21
|
+
error: {
|
|
22
|
+
message: e instanceof Error ? e.message : String(e),
|
|
23
|
+
stack: e instanceof Error ? e.stack : undefined,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
};
|
|
28
|
+
parentPort.postMessage(results);
|
|
29
|
+
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,67 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remix-run/test",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A browser-based test framework for Remix components",
|
|
5
|
+
"author": "Shopify Inc.",
|
|
5
6
|
"license": "MIT",
|
|
6
7
|
"repository": {
|
|
7
8
|
"type": "git",
|
|
8
9
|
"url": "git+https://github.com/remix-run/remix.git",
|
|
9
10
|
"directory": "packages/test"
|
|
10
11
|
},
|
|
11
|
-
"
|
|
12
|
-
|
|
12
|
+
"homepage": "https://github.com/remix-run/remix/tree/main/packages/test#readme",
|
|
13
|
+
"files": [
|
|
14
|
+
"tsconfig.json",
|
|
15
|
+
"LICENSE",
|
|
16
|
+
"README.md",
|
|
17
|
+
"dist",
|
|
18
|
+
"src",
|
|
19
|
+
"!src/**/*.test.*"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"bin": {
|
|
23
|
+
"remix-test": "./dist/cli.js"
|
|
24
|
+
},
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"default": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./package.json": "./package.json",
|
|
31
|
+
"./cli": "./dist/cli.js"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"tsx": "^4.21.0",
|
|
35
|
+
"@remix-run/component": "0.7.0",
|
|
36
|
+
"@remix-run/fetch-router": "0.18.1",
|
|
37
|
+
"@remix-run/node-fetch-server": "0.13.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"playwright": "^1.59.0"
|
|
41
|
+
},
|
|
42
|
+
"peerDependenciesMeta": {
|
|
43
|
+
"playwright": {
|
|
44
|
+
"optional": true
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^24.6.0",
|
|
49
|
+
"@typescript/native-preview": "7.0.0-dev.20251125.1",
|
|
50
|
+
"playwright": "^1.59.0",
|
|
51
|
+
"@remix-run/assert": "0.1.0"
|
|
52
|
+
},
|
|
53
|
+
"keywords": [
|
|
54
|
+
"testing",
|
|
55
|
+
"browser",
|
|
56
|
+
"test",
|
|
57
|
+
"remix",
|
|
58
|
+
"component",
|
|
59
|
+
"playwright"
|
|
60
|
+
],
|
|
61
|
+
"scripts": {
|
|
62
|
+
"build": "tsgo -p tsconfig.build.json",
|
|
63
|
+
"clean": "git clean -fdX",
|
|
64
|
+
"test": "node src/cli.ts",
|
|
65
|
+
"typecheck": "tsgo --noEmit"
|
|
13
66
|
}
|
|
14
|
-
}
|
|
67
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as fsp from 'node:fs/promises'
|
|
3
|
+
import * as path from 'node:path'
|
|
4
|
+
import { tsImport } from 'tsx/esm/api'
|
|
5
|
+
import { runServerTests } from './lib/runner.ts'
|
|
6
|
+
import { createReporter } from './lib/reporters/index.ts'
|
|
7
|
+
import { createWatcher } from './lib/watcher.ts'
|
|
8
|
+
import { loadPlaywrightConfig, resolveProjects } from './lib/playwright.ts'
|
|
9
|
+
import { loadConfig, type ResolvedRemixTestConfig } from './lib/config.ts'
|
|
10
|
+
import type { Counts } from './lib/utils.ts'
|
|
11
|
+
|
|
12
|
+
const config = await loadConfig()
|
|
13
|
+
|
|
14
|
+
let hasExited = false
|
|
15
|
+
let latestExitCode = 0
|
|
16
|
+
let watcher: ReturnType<typeof createWatcher> | undefined
|
|
17
|
+
let running = false
|
|
18
|
+
let queued = false
|
|
19
|
+
let rerunTimer: NodeJS.Timeout | undefined
|
|
20
|
+
|
|
21
|
+
process.on('SIGINT', () => cleanupAndExit(latestExitCode))
|
|
22
|
+
process.on('SIGTERM', () => cleanupAndExit(latestExitCode))
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
await executeRun()
|
|
26
|
+
|
|
27
|
+
if (config.watch) {
|
|
28
|
+
console.log('Watching for changes. Press Ctrl+C to stop.')
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
cleanupAndExit(1)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function executeRun() {
|
|
35
|
+
if (hasExited) return
|
|
36
|
+
|
|
37
|
+
running = true
|
|
38
|
+
|
|
39
|
+
let globalTeardown: (() => Promise<void> | void) | undefined
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
if (config.setup) {
|
|
43
|
+
let mod = await tsImport(path.resolve(process.cwd(), config.setup), {
|
|
44
|
+
parentURL: import.meta.url,
|
|
45
|
+
})
|
|
46
|
+
let globalSetup: (() => Promise<void> | void) | undefined = mod.globalSetup
|
|
47
|
+
globalTeardown = mod.globalTeardown
|
|
48
|
+
await globalSetup?.()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let { files, serverFiles, e2eFiles } = await discoverTests(config)
|
|
52
|
+
|
|
53
|
+
if (config.watch) {
|
|
54
|
+
watcher ??= createWatcher((file) => queueRerun(file))
|
|
55
|
+
watcher.update(files)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let playwrightConfig =
|
|
59
|
+
config.playwrightConfig == null || typeof config.playwrightConfig === 'string'
|
|
60
|
+
? await loadPlaywrightConfig(config.playwrightConfig)
|
|
61
|
+
: config.playwrightConfig
|
|
62
|
+
|
|
63
|
+
let reporter = createReporter(config.reporter)
|
|
64
|
+
let startTime = performance.now()
|
|
65
|
+
|
|
66
|
+
let counts: Counts = {
|
|
67
|
+
passed: 0,
|
|
68
|
+
failed: 0,
|
|
69
|
+
skipped: 0,
|
|
70
|
+
todo: 0,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Run server tests
|
|
74
|
+
if (serverFiles.length > 0) {
|
|
75
|
+
reporter.onSectionStart('\nRunning server tests:')
|
|
76
|
+
let serverResult = await runServerTests(serverFiles, reporter, config.concurrency, 'server')
|
|
77
|
+
counts.failed += serverResult.failed
|
|
78
|
+
counts.passed += serverResult.passed
|
|
79
|
+
counts.skipped += serverResult.skipped
|
|
80
|
+
counts.todo += serverResult.todo
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Run e2e tests for all browsers configured by the user
|
|
84
|
+
if (e2eFiles.length > 0) {
|
|
85
|
+
let projects = resolveProjects(playwrightConfig)
|
|
86
|
+
if (config.project) {
|
|
87
|
+
let projectNames = config.project.split(',').map((p) => p.trim())
|
|
88
|
+
projects = projects.filter((p) => p.name && projectNames.includes(p.name))
|
|
89
|
+
if (projects.length === 0) {
|
|
90
|
+
throw new Error(`No playwright projects found with name(s) "${config.project}"`)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (let project of projects) {
|
|
95
|
+
reporter.onSectionStart(`\nRunning tests for project \`${project.name}\`:`)
|
|
96
|
+
|
|
97
|
+
if (config.browser?.open) {
|
|
98
|
+
if (project.playwrightUseOpts?.headless === true) {
|
|
99
|
+
let label = project.name ? ` (project "${project.name}")` : ''
|
|
100
|
+
console.warn(
|
|
101
|
+
`Warning: browser.open is set but playwright headless is explicitly true${label} — ignoring browser.open`,
|
|
102
|
+
)
|
|
103
|
+
} else {
|
|
104
|
+
project.playwrightUseOpts = { ...project.playwrightUseOpts, headless: false }
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let e2eResult =
|
|
109
|
+
e2eFiles.length > 0
|
|
110
|
+
? await runServerTests(e2eFiles, reporter, config.concurrency, 'e2e', {
|
|
111
|
+
open: config.browser?.open,
|
|
112
|
+
playwrightUseOpts: project.playwrightUseOpts,
|
|
113
|
+
projectName: project.name,
|
|
114
|
+
})
|
|
115
|
+
: null
|
|
116
|
+
|
|
117
|
+
counts.passed += e2eResult?.passed ?? 0
|
|
118
|
+
counts.failed += e2eResult?.failed ?? 0
|
|
119
|
+
counts.skipped += e2eResult?.skipped ?? 0
|
|
120
|
+
counts.todo += e2eResult?.todo ?? 0
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
reporter.onSummary(counts, performance.now() - startTime)
|
|
125
|
+
|
|
126
|
+
latestExitCode = counts.failed > 0 ? 1 : 0
|
|
127
|
+
} catch (error) {
|
|
128
|
+
console.error('Error running tests:', error)
|
|
129
|
+
latestExitCode = 1
|
|
130
|
+
} finally {
|
|
131
|
+
await globalTeardown?.()
|
|
132
|
+
running = false
|
|
133
|
+
if (queued) {
|
|
134
|
+
queued = false
|
|
135
|
+
queueRerun('queued change')
|
|
136
|
+
} else if (!config.watch) {
|
|
137
|
+
cleanupAndExit(latestExitCode)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function discoverTests(config: ResolvedRemixTestConfig): Promise<{
|
|
143
|
+
files: string[]
|
|
144
|
+
serverFiles: string[]
|
|
145
|
+
e2eFiles: string[]
|
|
146
|
+
}> {
|
|
147
|
+
async function findFiles(pattern: string) {
|
|
148
|
+
let files: string[] = []
|
|
149
|
+
let exclude = ['node_modules/**', '.git/**']
|
|
150
|
+
|
|
151
|
+
for await (let file of fsp.glob(pattern, { cwd: process.cwd(), exclude })) {
|
|
152
|
+
files.push(path.resolve(process.cwd(), file))
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return files
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
let files = await findFiles(config.glob.test)
|
|
159
|
+
|
|
160
|
+
if (files.length === 0) {
|
|
161
|
+
console.log(`No test files found matching pattern: ${config.glob.test}`)
|
|
162
|
+
process.exit(1)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
let e2eSet = new Set(await findFiles(config.glob.e2e))
|
|
166
|
+
|
|
167
|
+
let types = new Set(config.type.split(','))
|
|
168
|
+
let e2eFiles = types.has('e2e') ? files.filter((f) => e2eSet.has(f)) : []
|
|
169
|
+
let serverFiles = types.has('server') ? files.filter((f) => !e2eSet.has(f)) : []
|
|
170
|
+
|
|
171
|
+
let totalFiles = serverFiles.length + e2eFiles.length
|
|
172
|
+
|
|
173
|
+
if (totalFiles === 0) {
|
|
174
|
+
console.log(`No test files remain after filtering for type ${config.type}`)
|
|
175
|
+
process.exit(1)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
console.log(
|
|
179
|
+
`Found ${totalFiles} test file(s) (${serverFiles.length} server, ${e2eFiles.length} e2e)`,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
files,
|
|
184
|
+
serverFiles,
|
|
185
|
+
e2eFiles,
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function queueRerun(reason: string) {
|
|
190
|
+
if (!config.watch || hasExited) return
|
|
191
|
+
|
|
192
|
+
clearTimeout(rerunTimer)
|
|
193
|
+
|
|
194
|
+
rerunTimer = setTimeout(() => {
|
|
195
|
+
rerunTimer = undefined
|
|
196
|
+
if (running) {
|
|
197
|
+
queued = true
|
|
198
|
+
} else {
|
|
199
|
+
console.log(`\n↻ Change detected (${reason}), re-running tests...\n`)
|
|
200
|
+
void executeRun()
|
|
201
|
+
}
|
|
202
|
+
}, 100)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function cleanupAndExit(code: number) {
|
|
206
|
+
if (hasExited) return
|
|
207
|
+
hasExited = true
|
|
208
|
+
watcher?.close()
|
|
209
|
+
process.exit(code)
|
|
210
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type { RemixTestConfig } from './lib/config.ts'
|
|
2
|
+
export {
|
|
3
|
+
describe,
|
|
4
|
+
it,
|
|
5
|
+
suite,
|
|
6
|
+
test,
|
|
7
|
+
before,
|
|
8
|
+
after,
|
|
9
|
+
beforeEach,
|
|
10
|
+
afterEach,
|
|
11
|
+
beforeAll,
|
|
12
|
+
afterAll,
|
|
13
|
+
} from './lib/framework.ts'
|
|
14
|
+
export { mock } from './lib/mock.ts'
|
|
15
|
+
export type { TestContext } from './lib/context.ts'
|