@remix-run/test 0.1.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.
- package/README.md +140 -35
- package/dist/app/client/entry.d.ts +2 -0
- package/dist/app/client/entry.d.ts.map +1 -0
- package/dist/app/client/entry.js +324 -0
- package/dist/app/client/iframe.d.ts +2 -0
- package/dist/app/client/iframe.d.ts.map +1 -0
- package/dist/app/client/iframe.js +22 -0
- package/dist/app/server.d.ts +6 -0
- package/dist/app/server.d.ts.map +1 -0
- package/dist/app/server.js +303 -0
- package/dist/cli-entry.d.ts +3 -0
- package/dist/cli-entry.d.ts.map +1 -0
- package/dist/cli-entry.js +14 -0
- package/dist/cli.d.ts +7 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +273 -139
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/lib/colors.d.ts +2 -0
- package/dist/lib/colors.d.ts.map +1 -0
- package/dist/lib/colors.js +2 -0
- package/dist/lib/config.d.ts +32 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +125 -22
- package/dist/lib/context.d.ts +37 -13
- package/dist/lib/context.d.ts.map +1 -1
- package/dist/lib/context.js +19 -3
- package/dist/lib/coverage-loader.d.ts +16 -0
- package/dist/lib/coverage-loader.d.ts.map +1 -0
- package/dist/lib/coverage-loader.js +20 -0
- package/dist/lib/coverage.d.ts +28 -0
- package/dist/lib/coverage.d.ts.map +1 -0
- package/dist/lib/coverage.js +212 -0
- package/dist/lib/executor.d.ts +3 -26
- package/dist/lib/executor.d.ts.map +1 -1
- package/dist/lib/executor.js +11 -6
- package/dist/lib/fake-timers.d.ts +6 -0
- package/dist/lib/fake-timers.d.ts.map +1 -0
- package/dist/lib/fake-timers.js +45 -0
- package/dist/lib/import-module.d.ts +2 -0
- package/dist/lib/import-module.d.ts.map +1 -0
- package/dist/lib/import-module.js +29 -0
- package/dist/lib/normalize.d.ts +2 -0
- package/dist/lib/normalize.d.ts.map +1 -0
- package/dist/lib/{utils.js → normalize.js} +0 -9
- package/dist/lib/playwright.d.ts +1 -1
- package/dist/lib/playwright.d.ts.map +1 -1
- package/dist/lib/playwright.js +5 -8
- package/dist/lib/reporters/dot.d.ts +1 -2
- package/dist/lib/reporters/dot.d.ts.map +1 -1
- package/dist/lib/reporters/dot.js +2 -1
- package/dist/lib/reporters/files.d.ts +1 -2
- package/dist/lib/reporters/files.d.ts.map +1 -1
- package/dist/lib/reporters/files.js +2 -1
- package/dist/lib/reporters/index.d.ts +4 -5
- package/dist/lib/reporters/index.d.ts.map +1 -1
- package/dist/lib/reporters/index.js +3 -3
- package/dist/lib/reporters/results.d.ts +30 -0
- package/dist/lib/reporters/results.d.ts.map +1 -0
- package/dist/lib/reporters/results.js +1 -0
- package/dist/lib/reporters/spec.d.ts +1 -2
- package/dist/lib/reporters/spec.d.ts.map +1 -1
- package/dist/lib/reporters/spec.js +2 -1
- package/dist/lib/reporters/tap.d.ts +1 -2
- package/dist/lib/reporters/tap.d.ts.map +1 -1
- package/dist/lib/reporters/tap.js +1 -1
- package/dist/lib/runner-browser.d.ts +21 -0
- package/dist/lib/runner-browser.d.ts.map +1 -0
- package/dist/lib/runner-browser.js +117 -0
- package/dist/lib/runner.d.ts +7 -2
- package/dist/lib/runner.d.ts.map +1 -1
- package/dist/lib/runner.js +33 -4
- package/dist/lib/runtime.d.ts +2 -0
- package/dist/lib/runtime.d.ts.map +1 -0
- package/dist/lib/runtime.js +2 -0
- package/dist/lib/ts-transform.d.ts +4 -0
- package/dist/lib/ts-transform.d.ts.map +1 -0
- package/dist/lib/ts-transform.js +29 -0
- package/dist/lib/worker-e2e.js +5 -4
- package/dist/lib/worker.js +31 -3
- package/dist/test/coverage/fixture.d.ts +5 -0
- package/dist/test/coverage/fixture.d.ts.map +1 -0
- package/dist/test/coverage/fixture.js +32 -0
- package/dist/test/coverage/test-browser.d.ts +2 -0
- package/dist/test/coverage/test-browser.d.ts.map +1 -0
- package/dist/test/coverage/test-browser.js +24 -0
- package/dist/test/coverage/test-e2e.d.ts +2 -0
- package/dist/test/coverage/test-e2e.d.ts.map +1 -0
- package/dist/test/coverage/test-e2e.js +60 -0
- package/dist/test/coverage/test-unit.d.ts +2 -0
- package/dist/test/coverage/test-unit.d.ts.map +1 -0
- package/dist/test/coverage/test-unit.js +27 -0
- package/dist/test/framework.test.browser.d.ts +2 -0
- package/dist/test/framework.test.browser.d.ts.map +1 -0
- package/dist/test/framework.test.browser.js +107 -0
- package/dist/test/framework.test.e2e.d.ts.map +1 -0
- package/dist/test/framework.test.e2e.js +34 -0
- package/package.json +30 -9
- package/src/app/client/entry.ts +353 -0
- package/src/app/client/iframe.ts +18 -0
- package/src/app/server.ts +336 -0
- package/src/cli-entry.ts +15 -0
- package/src/cli.ts +322 -148
- package/src/index.ts +1 -0
- package/src/lib/colors.ts +3 -0
- package/src/lib/config.ts +169 -23
- package/src/lib/context.ts +59 -17
- package/src/lib/coverage-loader.ts +31 -0
- package/src/lib/coverage.ts +320 -0
- package/src/lib/executor.ts +18 -35
- package/src/lib/fake-timers.ts +64 -0
- package/src/lib/import-module.ts +29 -0
- package/src/lib/{utils.ts → normalize.ts} +0 -18
- package/src/lib/playwright.ts +5 -7
- package/src/lib/reporters/dot.ts +3 -2
- package/src/lib/reporters/files.ts +3 -2
- package/src/lib/reporters/index.ts +4 -5
- package/src/lib/reporters/results.ts +29 -0
- package/src/lib/reporters/spec.ts +3 -2
- package/src/lib/reporters/tap.ts +2 -2
- package/src/lib/runner-browser.ts +165 -0
- package/src/lib/runner.ts +62 -10
- package/src/lib/runtime.ts +2 -0
- package/src/lib/ts-transform.ts +36 -0
- package/src/lib/worker-e2e.ts +7 -5
- package/src/lib/worker.ts +24 -4
- package/src/test/coverage/fixture.ts +34 -0
- package/src/test/coverage/test-browser.ts +29 -0
- package/src/test/coverage/test-e2e.ts +70 -0
- package/src/test/coverage/test-unit.ts +32 -0
- package/tsconfig.json +3 -1
- package/dist/lib/e2e-server.d.ts +0 -11
- package/dist/lib/e2e-server.d.ts.map +0 -1
- package/dist/lib/e2e-server.js +0 -15
- package/dist/lib/framework.test.d.ts +0 -2
- package/dist/lib/framework.test.d.ts.map +0 -1
- package/dist/lib/framework.test.e2e.d.ts.map +0 -1
- package/dist/lib/framework.test.e2e.js +0 -29
- package/dist/lib/framework.test.js +0 -283
- package/dist/lib/utils.d.ts +0 -16
- package/dist/lib/utils.d.ts.map +0 -1
- package/src/lib/e2e-server.ts +0 -28
- /package/dist/{lib → test}/framework.test.e2e.d.ts +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ts-transform.d.ts","sourceRoot":"","sources":["../../src/lib/ts-transform.ts"],"names":[],"mappings":"AAmBA,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAa3B"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { transform } from 'esbuild';
|
|
2
|
+
import { getTsconfig } from 'get-tsconfig';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
const tsconfigCache = new Map();
|
|
5
|
+
/*
|
|
6
|
+
* Transform a TypeScript file to JavaScript using esbuild with an inline
|
|
7
|
+
* source map and no minification. Used by the coverage ESM loader hook (so V8
|
|
8
|
+
* instruments readable JS), the coverage collector (so byte offsets can be
|
|
9
|
+
* re-derived and mapped back to TypeScript lines), and the browser harness
|
|
10
|
+
* server (so the bytes V8 sees in the browser match what the collector
|
|
11
|
+
* re-derives). Identical inputs must produce identical outputs across all
|
|
12
|
+
* call sites or coverage offsets won't line up.
|
|
13
|
+
*
|
|
14
|
+
* Compiler options (notably JSX) are taken from the nearest `tsconfig.json`
|
|
15
|
+
* walking up from the file's directory, so each project picks up its own
|
|
16
|
+
* `jsxImportSource` etc. Discovery results are cached by directory.
|
|
17
|
+
*/
|
|
18
|
+
export async function transformTypeScript(source, filePath) {
|
|
19
|
+
let loader = filePath.endsWith('.tsx') ? 'tsx' : 'ts';
|
|
20
|
+
let tsConfig = getTsconfig(path.dirname(filePath), 'tsconfig.json', tsconfigCache);
|
|
21
|
+
let result = await transform(source, {
|
|
22
|
+
loader,
|
|
23
|
+
sourcemap: 'inline',
|
|
24
|
+
sourcesContent: true,
|
|
25
|
+
sourcefile: filePath,
|
|
26
|
+
tsconfigRaw: { compilerOptions: tsConfig?.config.compilerOptions ?? {} },
|
|
27
|
+
});
|
|
28
|
+
return { code: result.code };
|
|
29
|
+
}
|
package/dist/lib/worker-e2e.js
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import { workerData, parentPort } from 'node:worker_threads';
|
|
2
|
-
import { tsImport } from 'tsx/esm/api';
|
|
3
|
-
import { createServer } from "./e2e-server.js";
|
|
4
2
|
import { runTests } from "./executor.js";
|
|
3
|
+
import { importModule } from "./import-module.js";
|
|
5
4
|
import { getBrowserLauncher, getPlaywrightLaunchOptions, getPlaywrightPageOptions, } from "./playwright.js";
|
|
6
5
|
try {
|
|
7
|
-
await
|
|
6
|
+
await importModule(workerData.file, import.meta);
|
|
8
7
|
let launcher = await getBrowserLauncher(workerData.playwrightUseOpts);
|
|
9
8
|
let opts = getPlaywrightLaunchOptions(workerData.playwrightUseOpts);
|
|
10
9
|
let browser = await launcher.launch(opts);
|
|
11
10
|
try {
|
|
12
11
|
let results = await runTests({
|
|
13
12
|
browser,
|
|
14
|
-
createServer,
|
|
15
13
|
open: workerData.open,
|
|
16
14
|
playwrightPageOptions: getPlaywrightPageOptions(workerData.playwrightUseOpts),
|
|
15
|
+
coverage: workerData.coverage,
|
|
17
16
|
});
|
|
18
17
|
parentPort.postMessage(results);
|
|
19
18
|
if (workerData.open) {
|
|
@@ -24,6 +23,7 @@ try {
|
|
|
24
23
|
finally {
|
|
25
24
|
await browser.close();
|
|
26
25
|
}
|
|
26
|
+
process.exit(0);
|
|
27
27
|
}
|
|
28
28
|
catch (e) {
|
|
29
29
|
let results = {
|
|
@@ -45,4 +45,5 @@ catch (e) {
|
|
|
45
45
|
],
|
|
46
46
|
};
|
|
47
47
|
parentPort.postMessage(results);
|
|
48
|
+
process.exit(0);
|
|
48
49
|
}
|
package/dist/lib/worker.js
CHANGED
|
@@ -1,10 +1,37 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
import * as mod from 'node:module';
|
|
10
|
+
import * as path from 'node:path';
|
|
11
|
+
import { parentPort, workerData } from 'node:worker_threads';
|
|
3
12
|
import { runTests } from "./executor.js";
|
|
13
|
+
import { importModule } from "./import-module.js";
|
|
14
|
+
import { IS_BUN } from "./runtime.js";
|
|
15
|
+
import { IS_RUNNING_FROM_SRC } from "./config.js";
|
|
4
16
|
try {
|
|
5
|
-
|
|
17
|
+
// When coverage is enabled in Node, we use a coverage-friendly TypeScript loader which
|
|
18
|
+
// replaces tsx's minified transformation with a non-minified esbuild transform
|
|
19
|
+
// so V8 coverage byte offsets align with readable source lines. This hook runs
|
|
20
|
+
// before the inherited tsx hook (hooks are LIFO), so it intercepts .ts imports and
|
|
21
|
+
// short-circuits before tsx transforms them.
|
|
22
|
+
if (workerData.coverage && !IS_BUN) {
|
|
23
|
+
// Ensure we load the right file whether we're running in the monorepo (TS) or
|
|
24
|
+
// from a published package (JS)
|
|
25
|
+
let ext = IS_RUNNING_FROM_SRC ? '.ts' : '.js';
|
|
26
|
+
mod.register(new URL(`./coverage-loader${ext}`, import.meta.url), import.meta.url);
|
|
27
|
+
await import(__rewriteRelativeImportExtension(workerData.file));
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
await importModule(workerData.file, import.meta);
|
|
31
|
+
}
|
|
6
32
|
let results = await runTests();
|
|
7
33
|
parentPort.postMessage(results);
|
|
34
|
+
process.exit(0);
|
|
8
35
|
}
|
|
9
36
|
catch (e) {
|
|
10
37
|
let results = {
|
|
@@ -26,4 +53,5 @@ catch (e) {
|
|
|
26
53
|
],
|
|
27
54
|
};
|
|
28
55
|
parentPort.postMessage(results);
|
|
56
|
+
process.exit(0);
|
|
29
57
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixture.d.ts","sourceRoot":"","sources":["../../../src/test/coverage/fixture.ts"],"names":[],"mappings":"AAKA,wBAAgB,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAEhD;AAGD,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAQ1C;AAGD,wBAAgB,gBAAgB,IAAI,MAAM,CAIzC;AAGD,wBAAgB,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAK3C"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// This file exists solely to validate coverage accuracy.
|
|
2
|
+
// Each function has a known expected coverage profile based on
|
|
3
|
+
// which paths the associated tests exercise.
|
|
4
|
+
// Fully covered — both statements and the single branch
|
|
5
|
+
export function add(a, b) {
|
|
6
|
+
return a + b;
|
|
7
|
+
}
|
|
8
|
+
// Partially covered — only the `n > 0` branch is tested
|
|
9
|
+
export function classify(n) {
|
|
10
|
+
if (n > 0) {
|
|
11
|
+
return 'positive';
|
|
12
|
+
}
|
|
13
|
+
else if (n < 0) {
|
|
14
|
+
return 'negative';
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
return 'zero';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
// Never called — 0% across the board
|
|
21
|
+
export function uncalledFunction() {
|
|
22
|
+
let result = 'never';
|
|
23
|
+
result += ' reached';
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
// Partially covered — only the truthy `name` branch is tested
|
|
27
|
+
export function greet(name) {
|
|
28
|
+
if (name) {
|
|
29
|
+
return `Hello, ${name}!`;
|
|
30
|
+
}
|
|
31
|
+
return 'Hello, stranger!';
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-browser.d.ts","sourceRoot":"","sources":["../../../src/test/coverage/test-browser.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as assert from '@remix-run/assert';
|
|
2
|
+
import { describe, it } from "../../lib/framework.js";
|
|
3
|
+
import { add, classify, greet } from "./fixture.js";
|
|
4
|
+
// Expected coverage for coverage-fixture.ts (same as the server/e2e fixture
|
|
5
|
+
// tests):
|
|
6
|
+
//
|
|
7
|
+
// add — 100% functions, statements, lines, branches
|
|
8
|
+
// classify — function covered, but only the `n > 0` branch is hit
|
|
9
|
+
// (the `n < 0` and `else` branches are uncovered)
|
|
10
|
+
// uncalledFunction — 0% across the board (never imported)
|
|
11
|
+
// greet — function covered, but only the truthy `name` branch is hit
|
|
12
|
+
// (the fallback `Hello, stranger!` line is uncovered)
|
|
13
|
+
describe('browser coverage fixture', () => {
|
|
14
|
+
it('exercises some but not all code paths in the browser', () => {
|
|
15
|
+
assert.equal(add(2, 3), 5);
|
|
16
|
+
assert.equal(add(-1, 1), 0);
|
|
17
|
+
assert.equal(classify(42), 'positive');
|
|
18
|
+
assert.equal(classify(1), 'positive');
|
|
19
|
+
// deliberately NOT testing classify(-1) or classify(0)
|
|
20
|
+
assert.equal(greet('World'), 'Hello, World!');
|
|
21
|
+
// deliberately NOT testing greet() without an argument
|
|
22
|
+
// deliberately NOT importing or calling uncalledFunction
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-e2e.d.ts","sourceRoot":"","sources":["../../../src/test/coverage/test-e2e.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import * as assert from '@remix-run/assert';
|
|
2
|
+
import { createTestServer } from '@remix-run/node-fetch-server/test';
|
|
3
|
+
import * as fsp from 'node:fs/promises';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { describe, it } from "../../lib/framework.js";
|
|
6
|
+
import { transformTypeScript } from "../../lib/ts-transform.js";
|
|
7
|
+
// Expected coverage for coverage/fixture.ts (same as the server fixture test):
|
|
8
|
+
//
|
|
9
|
+
// add — 100% functions, statements, lines, branches
|
|
10
|
+
// classify — function covered, but only the `n > 0` branch is hit
|
|
11
|
+
// uncalledFunction — 0% across the board (never called)
|
|
12
|
+
// greet — function covered, but only the truthy `name` branch is hit
|
|
13
|
+
describe('e2e coverage fixture', () => {
|
|
14
|
+
it('exercises some but not all code paths in the browser', async (t) => {
|
|
15
|
+
// Compile the fixture TypeScript to browser-ready JS
|
|
16
|
+
let fixturePath = path.resolve(import.meta.dirname, './fixture.ts');
|
|
17
|
+
let fixtureSource = await fsp.readFile(fixturePath, 'utf-8');
|
|
18
|
+
let { code: fixtureJs } = await transformTypeScript(fixtureSource, fixturePath);
|
|
19
|
+
let handler = (req) => {
|
|
20
|
+
let url = new URL(req.url);
|
|
21
|
+
if (url.pathname === '/') {
|
|
22
|
+
return new Response([
|
|
23
|
+
`<!doctype html>`,
|
|
24
|
+
`<html>`,
|
|
25
|
+
`<body>`,
|
|
26
|
+
` <div id="result"></div>`,
|
|
27
|
+
` <script type="module">`,
|
|
28
|
+
` import { add, classify, greet } from '/src/test/coverage/fixture.ts'`,
|
|
29
|
+
` // Exercise the same paths as the server fixture test:`,
|
|
30
|
+
` // - add: fully covered`,
|
|
31
|
+
` // - classify: only positive branch`,
|
|
32
|
+
` // - greet: only with a name`,
|
|
33
|
+
` // - uncalledFunction: never imported`,
|
|
34
|
+
` let results = [`,
|
|
35
|
+
` add(2, 3),`,
|
|
36
|
+
` add(-1, 1),`,
|
|
37
|
+
` classify(42),`,
|
|
38
|
+
` classify(1),`,
|
|
39
|
+
` greet('World'),`,
|
|
40
|
+
` ]`,
|
|
41
|
+
` document.getElementById('result').textContent = results.join(',')`,
|
|
42
|
+
` </script>`,
|
|
43
|
+
`</body>`,
|
|
44
|
+
`</html>`,
|
|
45
|
+
].join('\n'), { headers: { 'Content-Type': 'text/html' } });
|
|
46
|
+
}
|
|
47
|
+
// Serve the compiled fixture at the path the import expects
|
|
48
|
+
if (url.pathname === '/src/test/coverage/fixture.ts') {
|
|
49
|
+
return new Response(fixtureJs, {
|
|
50
|
+
headers: { 'Content-Type': 'application/javascript' },
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return new Response('Not found', { status: 404 });
|
|
54
|
+
};
|
|
55
|
+
let page = await t.serve(await createTestServer(handler));
|
|
56
|
+
await page.goto('/');
|
|
57
|
+
let result = await page.locator('#result').textContent();
|
|
58
|
+
assert.equal(result, '5,0,positive,positive,Hello, World!');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-unit.d.ts","sourceRoot":"","sources":["../../../src/test/coverage/test-unit.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as assert from '@remix-run/assert';
|
|
2
|
+
import { describe, it } from "../../lib/framework.js";
|
|
3
|
+
import { add, classify, greet } from "./fixture.js";
|
|
4
|
+
// Expected coverage for coverage-fixture.ts:
|
|
5
|
+
//
|
|
6
|
+
// add — 100% functions, statements, lines, branches
|
|
7
|
+
// classify — function covered, but only the `n > 0` branch is hit
|
|
8
|
+
// (the `n < 0` and `else` branches are uncovered)
|
|
9
|
+
// uncalledFunction — 0% across the board (never imported)
|
|
10
|
+
// greet — function covered, but only the truthy `name` branch is hit
|
|
11
|
+
// (the fallback `Hello, stranger!` line is uncovered)
|
|
12
|
+
describe('coverage/fixture.ts', () => {
|
|
13
|
+
it('add returns the sum', () => {
|
|
14
|
+
assert.equal(add(2, 3), 5);
|
|
15
|
+
assert.equal(add(-1, 1), 0);
|
|
16
|
+
});
|
|
17
|
+
it('classify identifies positive numbers only', () => {
|
|
18
|
+
assert.equal(classify(42), 'positive');
|
|
19
|
+
assert.equal(classify(1), 'positive');
|
|
20
|
+
// deliberately NOT testing classify(-1) or classify(0)
|
|
21
|
+
});
|
|
22
|
+
// deliberately NOT importing or calling uncalledFunction
|
|
23
|
+
it('greet with a name only', () => {
|
|
24
|
+
assert.equal(greet('World'), 'Hello, World!');
|
|
25
|
+
// deliberately NOT testing greet() without an argument
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"framework.test.browser.d.ts","sourceRoot":"","sources":["../../src/test/framework.test.browser.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "@remix-run/ui/jsx-runtime";
|
|
2
|
+
import decamelize from 'decamelize';
|
|
3
|
+
import * as assert from '@remix-run/assert';
|
|
4
|
+
import { describe, it } from '@remix-run/test';
|
|
5
|
+
import { on } from '@remix-run/ui';
|
|
6
|
+
import { render } from '@remix-run/ui/test';
|
|
7
|
+
describe('Counter', () => {
|
|
8
|
+
function Counter(handle) {
|
|
9
|
+
let count = handle.props.count ?? 0;
|
|
10
|
+
return () => (_jsxs("div", { children: [
|
|
11
|
+
_jsx("h3", { children: "Counter" }), _jsxs("div", { children: [
|
|
12
|
+
_jsx("button", { "data-action": "decrement", mix: [
|
|
13
|
+
on('click', () => {
|
|
14
|
+
count--;
|
|
15
|
+
handle.update();
|
|
16
|
+
}),
|
|
17
|
+
], children: "-" }), _jsx("span", { "data-testid": "count", style: { fontSize: '24px', minWidth: '2ch', textAlign: 'center' }, children: count }), _jsx("button", { "data-action": "increment", mix: [
|
|
18
|
+
on('click', () => {
|
|
19
|
+
count++;
|
|
20
|
+
handle.update();
|
|
21
|
+
}),
|
|
22
|
+
], children: "+" })
|
|
23
|
+
] })
|
|
24
|
+
] }));
|
|
25
|
+
}
|
|
26
|
+
it('renders with initial count of 0 when not specified', (t) => {
|
|
27
|
+
let { $, cleanup } = render(_jsx(Counter, {}));
|
|
28
|
+
t.after(cleanup);
|
|
29
|
+
assert.equal(Number($('[data-testid="count"]').textContent), 0);
|
|
30
|
+
});
|
|
31
|
+
it('renders with a provided initial count', (t) => {
|
|
32
|
+
let { $, cleanup } = render(_jsx(Counter, { count: 5 }));
|
|
33
|
+
t.after(cleanup);
|
|
34
|
+
assert.equal(Number($('[data-testid="count"]').textContent), 5);
|
|
35
|
+
});
|
|
36
|
+
it('increments the count', async (t) => {
|
|
37
|
+
let { $, act, cleanup } = render(_jsx(Counter, {}));
|
|
38
|
+
t.after(cleanup);
|
|
39
|
+
await act(() => $('[data-action="increment"]')?.click());
|
|
40
|
+
assert.equal(Number($('[data-testid="count"]').textContent), 1);
|
|
41
|
+
await act(() => $('[data-action="increment"]')?.click());
|
|
42
|
+
assert.equal(Number($('[data-testid="count"]').textContent), 2);
|
|
43
|
+
await act(() => $('[data-action="increment"]')?.click());
|
|
44
|
+
assert.equal(Number($('[data-testid="count"]').textContent), 3);
|
|
45
|
+
});
|
|
46
|
+
it('decrements the count', async (t) => {
|
|
47
|
+
let { $, act, cleanup } = render(_jsx(Counter, { count: 3 }));
|
|
48
|
+
t.after(cleanup);
|
|
49
|
+
await act(() => $('[data-action="decrement"]')?.click());
|
|
50
|
+
assert.equal(Number($('[data-testid="count"]').textContent), 2);
|
|
51
|
+
await act(() => $('[data-action="decrement"]')?.click());
|
|
52
|
+
assert.equal(Number($('[data-testid="count"]').textContent), 1);
|
|
53
|
+
await act(() => $('[data-action="decrement"]')?.click());
|
|
54
|
+
assert.equal(Number($('[data-testid="count"]').textContent), 0);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
describe('FieldLabel (using decamelize)', () => {
|
|
58
|
+
// Demonstrates that ESM third-party libraries are importable from test modules
|
|
59
|
+
function FieldLabel(_handle) {
|
|
60
|
+
return (props) => (_jsx("span", { "data-testid": "label", children: decamelize(props.name, { separator: ' ' }) }));
|
|
61
|
+
}
|
|
62
|
+
it('renders a single word unchanged', (t) => {
|
|
63
|
+
let { $, cleanup } = render(_jsx(FieldLabel, { name: "name" }));
|
|
64
|
+
t.after(cleanup);
|
|
65
|
+
assert.equal($('[data-testid="label"]')?.textContent, 'name');
|
|
66
|
+
});
|
|
67
|
+
it('converts camelCase to spaced words', (t) => {
|
|
68
|
+
let { $, cleanup } = render(_jsx(FieldLabel, { name: "firstName" }));
|
|
69
|
+
t.after(cleanup);
|
|
70
|
+
assert.equal($('[data-testid="label"]')?.textContent, 'first name');
|
|
71
|
+
});
|
|
72
|
+
it('handles multiple humps', (t) => {
|
|
73
|
+
let { $, cleanup } = render(_jsx(FieldLabel, { name: "dateOfBirth" }));
|
|
74
|
+
t.after(cleanup);
|
|
75
|
+
assert.equal($('[data-testid="label"]')?.textContent, 'date of birth');
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
describe('DOM Tests', () => {
|
|
79
|
+
it('can interact with DOM', async () => {
|
|
80
|
+
let div = document.createElement('div');
|
|
81
|
+
div.textContent = 'Hello';
|
|
82
|
+
assert.equal(div.textContent, 'Hello');
|
|
83
|
+
});
|
|
84
|
+
it('can test fetch API', async () => {
|
|
85
|
+
let response = await fetch('data:text/plain,hello');
|
|
86
|
+
let text = await response.text();
|
|
87
|
+
assert.equal(text, 'hello');
|
|
88
|
+
});
|
|
89
|
+
it.skip('skip: can skip tests', () => {
|
|
90
|
+
assert.equal(true, false);
|
|
91
|
+
});
|
|
92
|
+
it.todo('todo: can mark tests as todo');
|
|
93
|
+
});
|
|
94
|
+
describe('render/cleanup', () => {
|
|
95
|
+
it('cleanup removes the container from the DOM', () => {
|
|
96
|
+
let { container, cleanup } = render(_jsx("div", { "data-testid": "manual", children: "hello" }));
|
|
97
|
+
assert.equal(document.body.contains(container), true);
|
|
98
|
+
cleanup();
|
|
99
|
+
assert.equal(document.body.contains(container), false);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
describe.skip('skip: Skipped Test Suite', () => {
|
|
103
|
+
it('would fail', () => {
|
|
104
|
+
assert.equal(true, false);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
describe.todo('todo: Test Suite');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"framework.test.e2e.d.ts","sourceRoot":"","sources":["../../src/test/framework.test.e2e.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "@remix-run/ui/jsx-runtime";
|
|
2
|
+
import * as assert from 'node:assert/strict';
|
|
3
|
+
import { renderToString } from '@remix-run/ui/server';
|
|
4
|
+
import { createTestServer } from '@remix-run/node-fetch-server/test';
|
|
5
|
+
import { describe, it } from "../lib/framework.js";
|
|
6
|
+
const html = async (n) => new Response(await renderToString(n), {
|
|
7
|
+
headers: { 'Content-Type': 'text/html' },
|
|
8
|
+
});
|
|
9
|
+
describe('e2e tests', () => {
|
|
10
|
+
it('runs playwright against a fetch handler', async (t) => {
|
|
11
|
+
function Doc(handle) {
|
|
12
|
+
return () => (_jsxs("html", { children: [
|
|
13
|
+
_jsx("head", { children: _jsx("title", { children: "Test" }) }), _jsx("body", { children: handle.props.children })
|
|
14
|
+
] }));
|
|
15
|
+
}
|
|
16
|
+
let handler = (request) => {
|
|
17
|
+
let url = new URL(request.url);
|
|
18
|
+
if (url.pathname === '/') {
|
|
19
|
+
return html(_jsxs(Doc, { children: [
|
|
20
|
+
_jsx("h1", { children: "Hello Remix" }), _jsx("a", { href: "/about", children: "About" })
|
|
21
|
+
] }));
|
|
22
|
+
}
|
|
23
|
+
if (url.pathname === '/about') {
|
|
24
|
+
return html(_jsx(Doc, { children: _jsx("h1", { children: "About Remix" }) }));
|
|
25
|
+
}
|
|
26
|
+
return new Response('Not found', { status: 404 });
|
|
27
|
+
};
|
|
28
|
+
let page = await t.serve(await createTestServer(handler));
|
|
29
|
+
await page.goto('/');
|
|
30
|
+
assert.equal(await page.locator('h1').textContent(), 'Hello Remix');
|
|
31
|
+
await page.click('[href="/about"]');
|
|
32
|
+
assert.equal(await page.locator('h1').textContent(), 'About Remix');
|
|
33
|
+
});
|
|
34
|
+
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remix-run/test",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "A
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "A test framework for JavaScript and TypeScript projects",
|
|
5
5
|
"author": "Shopify Inc.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -19,8 +19,11 @@
|
|
|
19
19
|
"!src/**/*.test.*"
|
|
20
20
|
],
|
|
21
21
|
"type": "module",
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=24.3.0"
|
|
24
|
+
},
|
|
22
25
|
"bin": {
|
|
23
|
-
"remix-test": "./dist/cli.js"
|
|
26
|
+
"remix-test": "./dist/cli-entry.js"
|
|
24
27
|
},
|
|
25
28
|
"exports": {
|
|
26
29
|
".": {
|
|
@@ -28,13 +31,23 @@
|
|
|
28
31
|
"default": "./dist/index.js"
|
|
29
32
|
},
|
|
30
33
|
"./package.json": "./package.json",
|
|
31
|
-
"./cli":
|
|
34
|
+
"./cli": {
|
|
35
|
+
"types": "./dist/cli.d.ts",
|
|
36
|
+
"default": "./dist/cli.js"
|
|
37
|
+
}
|
|
32
38
|
},
|
|
33
39
|
"dependencies": {
|
|
40
|
+
"es-module-lexer": "^2.0.0",
|
|
41
|
+
"esbuild": "^0.27.1",
|
|
42
|
+
"get-tsconfig": "^4.13.6",
|
|
43
|
+
"istanbul-lib-coverage": "^3.2.2",
|
|
44
|
+
"istanbul-lib-report": "^3.0.1",
|
|
45
|
+
"istanbul-reports": "^3.2.0",
|
|
46
|
+
"magic-string": "^0.30.21",
|
|
47
|
+
"source-map-js": "^1.2.1",
|
|
34
48
|
"tsx": "^4.21.0",
|
|
35
|
-
"
|
|
36
|
-
"@remix-run/
|
|
37
|
-
"@remix-run/node-fetch-server": "0.13.0"
|
|
49
|
+
"v8-to-istanbul": "^9.3.0",
|
|
50
|
+
"@remix-run/terminal": "^0.1.0"
|
|
38
51
|
},
|
|
39
52
|
"peerDependencies": {
|
|
40
53
|
"playwright": "^1.59.0"
|
|
@@ -45,10 +58,17 @@
|
|
|
45
58
|
}
|
|
46
59
|
},
|
|
47
60
|
"devDependencies": {
|
|
61
|
+
"@types/dom-navigation": "1.0.6",
|
|
62
|
+
"@types/istanbul-lib-coverage": "^2.0.6",
|
|
63
|
+
"@types/istanbul-lib-report": "^3.0.3",
|
|
64
|
+
"@types/istanbul-reports": "^3.0.4",
|
|
48
65
|
"@types/node": "^24.6.0",
|
|
49
66
|
"@typescript/native-preview": "7.0.0-dev.20251125.1",
|
|
67
|
+
"decamelize": "^6.0.1",
|
|
50
68
|
"playwright": "^1.59.0",
|
|
51
|
-
"@remix-run/assert": "0.1.0"
|
|
69
|
+
"@remix-run/assert": "^0.1.0",
|
|
70
|
+
"@remix-run/node-fetch-server": "^0.13.0",
|
|
71
|
+
"@remix-run/ui": "^0.1.0"
|
|
52
72
|
},
|
|
53
73
|
"keywords": [
|
|
54
74
|
"testing",
|
|
@@ -61,7 +81,8 @@
|
|
|
61
81
|
"scripts": {
|
|
62
82
|
"build": "tsgo -p tsconfig.build.json",
|
|
63
83
|
"clean": "git clean -fdX",
|
|
64
|
-
"test": "node src/cli.ts",
|
|
84
|
+
"test": "node src/cli-entry.ts",
|
|
85
|
+
"test:bun": "bun --bun src/cli-entry.ts --type server",
|
|
65
86
|
"typecheck": "tsgo --noEmit"
|
|
66
87
|
}
|
|
67
88
|
}
|