mirror of
https://github.com/oven-sh/bun
synced 2026-02-17 06:12:08 +00:00
Compare commits
13 Commits
jarred/bar
...
claude/vir
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7bf7786ba9 | ||
|
|
18322da1e9 | ||
|
|
c04ee28892 | ||
|
|
8bac1dd4f3 | ||
|
|
8695e45f59 | ||
|
|
44340bf7e7 | ||
|
|
85ca1a67cf | ||
|
|
19606f0c12 | ||
|
|
6dcd16657e | ||
|
|
07bdea0d03 | ||
|
|
32cdb2cfd4 | ||
|
|
a22f005365 | ||
|
|
fce146899e |
@@ -3,16 +3,15 @@ import { itBundled } from "../expectBundled";
|
||||
describe("css", () => {
|
||||
itBundled("css-module/GlobalPseudoFunction", {
|
||||
files: {
|
||||
"index.module.css": /* css */ `
|
||||
"/index.module.css": /* css */ `
|
||||
:global(.foo) {
|
||||
color: red;
|
||||
}
|
||||
`,
|
||||
},
|
||||
outdir: "/out",
|
||||
entryPoints: ["/index.module.css"],
|
||||
outfile: "/out.css",
|
||||
onAfterBundle(api) {
|
||||
api.expectFile("/out/index.module.css").toEqualIgnoringWhitespace(`
|
||||
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
|
||||
/* index.module.css */
|
||||
.foo {
|
||||
color: red;
|
||||
|
||||
@@ -3,16 +3,15 @@ import { itBundled } from "../expectBundled";
|
||||
describe("css", () => {
|
||||
itBundled("css/is-selector", {
|
||||
files: {
|
||||
"index.css": /* css */ `
|
||||
"/index.css": /* css */ `
|
||||
.foo:is(input:checked) {
|
||||
color: red;
|
||||
}
|
||||
`,
|
||||
},
|
||||
outdir: "/out",
|
||||
entryPoints: ["/index.css"],
|
||||
outfile: "/out.css",
|
||||
onAfterBundle(api) {
|
||||
api.expectFile("/out/index.css").toMatchInlineSnapshot(`
|
||||
api.expectFile("/out.css").toMatchInlineSnapshot(`
|
||||
"/* index.css */
|
||||
.foo:-webkit-any(input:checked) {
|
||||
color: red;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { itBundled } from "../expectBundled";
|
||||
describe("css", () => {
|
||||
itBundled("css/view-transition-class-selector-23600", {
|
||||
files: {
|
||||
"index.css": /* css */ `
|
||||
"/index.css": /* css */ `
|
||||
@keyframes slide-out {
|
||||
from {
|
||||
opacity: 1;
|
||||
@@ -33,10 +33,9 @@ describe("css", () => {
|
||||
}
|
||||
`,
|
||||
},
|
||||
outdir: "/out",
|
||||
entryPoints: ["/index.css"],
|
||||
outfile: "/out.css",
|
||||
onAfterBundle(api) {
|
||||
api.expectFile("/out/index.css").toMatchInlineSnapshot(`
|
||||
api.expectFile("/out.css").toMatchInlineSnapshot(`
|
||||
"/* index.css */
|
||||
@keyframes slide-out {
|
||||
from {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { itBundled } from "../../expectBundled";
|
||||
const runTest = (property: string, input: string, expected: string) => {
|
||||
const testTitle = `${property}: ${input}`;
|
||||
itBundled(testTitle, {
|
||||
virtual: true,
|
||||
files: {
|
||||
"/a.css": /* css */ `
|
||||
h1 {
|
||||
@@ -11,7 +12,7 @@ h1 {
|
||||
}
|
||||
`,
|
||||
},
|
||||
outfile: "out.css",
|
||||
outfile: "/out.css",
|
||||
|
||||
onAfterBundle(api) {
|
||||
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
|
||||
|
||||
@@ -4,6 +4,7 @@ import { itBundled } from "../../expectBundled";
|
||||
const runTest = (testTitle: string, input: string, expected: string) => {
|
||||
testTitle = testTitle.length === 0 ? input : testTitle;
|
||||
itBundled(testTitle, {
|
||||
virtual: true,
|
||||
files: {
|
||||
"/a.css": /* css */ `
|
||||
h1 {
|
||||
@@ -11,7 +12,7 @@ h1 {
|
||||
}
|
||||
`,
|
||||
},
|
||||
outfile: "out.css",
|
||||
outfile: "/out.css",
|
||||
|
||||
onAfterBundle(api) {
|
||||
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
|
||||
|
||||
@@ -3,6 +3,7 @@ import { itBundled } from "../../expectBundled";
|
||||
|
||||
const runTest = (input: string, expected: string) => {
|
||||
itBundled(input, {
|
||||
virtual: true,
|
||||
files: {
|
||||
"/a.css": /* css */ `
|
||||
h1 {
|
||||
@@ -10,7 +11,7 @@ h1 {
|
||||
}
|
||||
`,
|
||||
},
|
||||
outfile: "out.css",
|
||||
outfile: "/out.css",
|
||||
|
||||
onAfterBundle(api) {
|
||||
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
|
||||
|
||||
@@ -5,6 +5,7 @@ let i = 0;
|
||||
const testname = () => `test-${i++}`;
|
||||
describe("relative_color_out_of_gamut", () => {
|
||||
itBundled(testname(), {
|
||||
virtual: true,
|
||||
files: {
|
||||
"/a.css": /* css */ `
|
||||
h1 {
|
||||
@@ -12,7 +13,7 @@ h1 {
|
||||
}
|
||||
`,
|
||||
},
|
||||
outfile: "out.css",
|
||||
outfile: "/out.css",
|
||||
|
||||
onAfterBundle(api) {
|
||||
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
|
||||
@@ -25,6 +26,7 @@ h1 {
|
||||
});
|
||||
|
||||
itBundled(testname(), {
|
||||
virtual: true,
|
||||
files: {
|
||||
"/a.css": /* css */ `
|
||||
h1 {
|
||||
@@ -45,6 +47,7 @@ h1 {
|
||||
});
|
||||
|
||||
itBundled(testname(), {
|
||||
virtual: true,
|
||||
files: {
|
||||
"/a.css": /* css */ `
|
||||
h1 {
|
||||
@@ -65,6 +68,7 @@ h1 {
|
||||
});
|
||||
|
||||
itBundled(testname(), {
|
||||
virtual: true,
|
||||
files: {
|
||||
"/a.css": /* css */ `
|
||||
h1 {
|
||||
@@ -85,6 +89,7 @@ h1 {
|
||||
});
|
||||
|
||||
itBundled(testname(), {
|
||||
virtual: true,
|
||||
files: {
|
||||
"/a.css": /* css */ `
|
||||
h1 {
|
||||
@@ -105,6 +110,7 @@ h1 {
|
||||
});
|
||||
|
||||
itBundled(testname(), {
|
||||
virtual: true,
|
||||
files: {
|
||||
"/a.css": /* css */ `
|
||||
h1 {
|
||||
@@ -125,6 +131,7 @@ h1 {
|
||||
});
|
||||
|
||||
itBundled(testname(), {
|
||||
virtual: true,
|
||||
files: {
|
||||
"/a.css": /* css */ `
|
||||
h1 {
|
||||
@@ -145,6 +152,7 @@ h1 {
|
||||
});
|
||||
|
||||
itBundled(testname(), {
|
||||
virtual: true,
|
||||
files: {
|
||||
"/a.css": /* css */ `
|
||||
h1 {
|
||||
@@ -165,6 +173,7 @@ h1 {
|
||||
});
|
||||
|
||||
itBundled(testname(), {
|
||||
virtual: true,
|
||||
files: {
|
||||
"/a.css": /* css */ `
|
||||
h1 {
|
||||
@@ -185,6 +194,7 @@ h1 {
|
||||
});
|
||||
|
||||
itBundled(testname(), {
|
||||
virtual: true,
|
||||
files: {
|
||||
"/a.css": /* css */ `
|
||||
h1 {
|
||||
@@ -205,6 +215,7 @@ h1 {
|
||||
});
|
||||
|
||||
itBundled(testname(), {
|
||||
virtual: true,
|
||||
files: {
|
||||
"/a.css": /* css */ `
|
||||
h1 {
|
||||
@@ -225,6 +236,7 @@ h1 {
|
||||
});
|
||||
|
||||
itBundled(testname(), {
|
||||
virtual: true,
|
||||
files: {
|
||||
"/a.css": /* css */ `
|
||||
h1 {
|
||||
@@ -245,6 +257,7 @@ h1 {
|
||||
});
|
||||
|
||||
itBundled(testname(), {
|
||||
virtual: true,
|
||||
files: {
|
||||
"/a.css": /* css */ `
|
||||
h1 {
|
||||
@@ -265,6 +278,7 @@ h1 {
|
||||
});
|
||||
|
||||
itBundled(testname(), {
|
||||
virtual: true,
|
||||
files: {
|
||||
"/a.css": /* css */ `
|
||||
h1 {
|
||||
@@ -285,6 +299,7 @@ h1 {
|
||||
});
|
||||
|
||||
itBundled(testname(), {
|
||||
virtual: true,
|
||||
files: {
|
||||
"/a.css": /* css */ `
|
||||
h1 {
|
||||
@@ -305,6 +320,7 @@ h1 {
|
||||
});
|
||||
|
||||
itBundled(testname(), {
|
||||
virtual: true,
|
||||
files: {
|
||||
"/a.css": /* css */ `
|
||||
h1 {
|
||||
@@ -325,6 +341,7 @@ h1 {
|
||||
});
|
||||
|
||||
itBundled(testname(), {
|
||||
virtual: true,
|
||||
files: {
|
||||
"/a.css": /* css */ `
|
||||
h1 {
|
||||
@@ -345,6 +362,7 @@ h1 {
|
||||
});
|
||||
|
||||
itBundled(testname(), {
|
||||
virtual: true,
|
||||
files: {
|
||||
"/a.css": /* css */ `
|
||||
h1 {
|
||||
@@ -365,6 +383,7 @@ h1 {
|
||||
});
|
||||
|
||||
itBundled(testname(), {
|
||||
virtual: true,
|
||||
files: {
|
||||
"/a.css": /* css */ `
|
||||
h1 {
|
||||
@@ -385,6 +404,7 @@ h1 {
|
||||
});
|
||||
|
||||
itBundled(testname(), {
|
||||
virtual: true,
|
||||
files: {
|
||||
"/a.css": /* css */ `
|
||||
h1 {
|
||||
@@ -405,6 +425,7 @@ h1 {
|
||||
});
|
||||
|
||||
itBundled(testname(), {
|
||||
virtual: true,
|
||||
files: {
|
||||
"/a.css": /* css */ `
|
||||
h1 {
|
||||
@@ -425,6 +446,7 @@ h1 {
|
||||
});
|
||||
|
||||
itBundled(testname(), {
|
||||
virtual: true,
|
||||
files: {
|
||||
"/a.css": /* css */ `
|
||||
h1 {
|
||||
@@ -445,6 +467,7 @@ h1 {
|
||||
});
|
||||
|
||||
itBundled(testname(), {
|
||||
virtual: true,
|
||||
files: {
|
||||
"/a.css": /* css */ `
|
||||
h1 {
|
||||
@@ -465,6 +488,7 @@ h1 {
|
||||
});
|
||||
|
||||
itBundled(testname(), {
|
||||
virtual: true,
|
||||
files: {
|
||||
"/a.css": /* css */ `
|
||||
h1 {
|
||||
@@ -485,6 +509,7 @@ h1 {
|
||||
});
|
||||
|
||||
itBundled(testname(), {
|
||||
virtual: true,
|
||||
files: {
|
||||
"/a.css": /* css */ `
|
||||
h1 {
|
||||
@@ -505,6 +530,7 @@ h1 {
|
||||
});
|
||||
|
||||
itBundled(testname(), {
|
||||
virtual: true,
|
||||
files: {
|
||||
"/a.css": /* css */ `
|
||||
h1 {
|
||||
@@ -525,6 +551,7 @@ h1 {
|
||||
});
|
||||
|
||||
itBundled(testname(), {
|
||||
virtual: true,
|
||||
files: {
|
||||
"/a.css": /* css */ `
|
||||
h1 {
|
||||
|
||||
@@ -16,9 +16,9 @@ describe("bundler", () => {
|
||||
color: black }
|
||||
`,
|
||||
},
|
||||
outfile: "/out.js",
|
||||
outfile: "/out.css",
|
||||
onAfterBundle(api) {
|
||||
api.expectFile("/out.js").toEqualIgnoringWhitespace(`
|
||||
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
|
||||
/* entry.css */
|
||||
body {
|
||||
color: #000;
|
||||
@@ -31,9 +31,9 @@ describe("bundler", () => {
|
||||
files: {
|
||||
"/entry.css": /* css */ `\n`,
|
||||
},
|
||||
outfile: "/out.js",
|
||||
outfile: "/out.css",
|
||||
onAfterBundle(api) {
|
||||
api.expectFile("/out.js").toEqualIgnoringWhitespace(`
|
||||
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
|
||||
/* entry.css */`);
|
||||
},
|
||||
});
|
||||
@@ -48,12 +48,12 @@ describe("bundler", () => {
|
||||
}
|
||||
}`,
|
||||
},
|
||||
outfile: "/out.js",
|
||||
outfile: "/out.css",
|
||||
onAfterBundle(api) {
|
||||
api.expectFile("/out.js").toEqualIgnoringWhitespace(`
|
||||
api.expectFile("/out.css").toEqualIgnoringWhitespace(`
|
||||
/* entry.css */
|
||||
body {
|
||||
&h1 {
|
||||
& h1 {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,6 +299,13 @@ export interface BundlerTestInput {
|
||||
|
||||
/** Run after the bun.build function is called with its output */
|
||||
onAfterApiBundle?(build: BuildOutput): Promise<void> | void;
|
||||
|
||||
/**
|
||||
* Run the build entirely in memory using Bun.build's `files` API.
|
||||
* No temp directories or files are created. Outputs are read from BuildArtifact.text().
|
||||
* The `onAfterBundle` callback still works with the same API.
|
||||
*/
|
||||
virtual?: boolean;
|
||||
}
|
||||
|
||||
export interface SourceMapTests {
|
||||
@@ -408,6 +415,40 @@ function testRef(id: string, options: BundlerTestInput): BundlerTestRef {
|
||||
return { id, options };
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract capture function calls from file contents.
|
||||
* Finds all occurrences of fnName(...) and returns the argument contents.
|
||||
*/
|
||||
function extractCaptures(fileContents: string, file: string, fnName: string): string[] {
|
||||
let i = 0;
|
||||
const length = fileContents.length;
|
||||
const matches: string[] = [];
|
||||
while (i < length) {
|
||||
i = fileContents.indexOf(fnName, i);
|
||||
if (i === -1) break;
|
||||
const start = i;
|
||||
let depth = 0;
|
||||
while (i < length) {
|
||||
const char = fileContents[i];
|
||||
if (char === "(") depth++;
|
||||
else if (char === ")") {
|
||||
depth--;
|
||||
if (depth === 0) break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (depth !== 0) {
|
||||
throw new Error(`Could not find closing paren for ${fnName} call in ${file}`);
|
||||
}
|
||||
matches.push(fileContents.slice(start + fnName.length + 1, i));
|
||||
i++;
|
||||
}
|
||||
if (matches.length === 0) {
|
||||
throw new Error(`No ${fnName} calls found in ${file}`);
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
function expectBundled(
|
||||
id: string,
|
||||
opts: BundlerTestInput,
|
||||
@@ -494,6 +535,7 @@ function expectBundled(
|
||||
generateOutput = true,
|
||||
onAfterApiBundle,
|
||||
throw: _throw = false,
|
||||
virtual = false,
|
||||
...unknownProps
|
||||
} = opts;
|
||||
|
||||
@@ -580,6 +622,198 @@ function expectBundled(
|
||||
return testRef(id, opts);
|
||||
}
|
||||
|
||||
// Virtual mode: run entirely in memory without disk I/O
|
||||
if (virtual) {
|
||||
// Validate that unsupported options are not set
|
||||
const unsupportedOptions: string[] = [];
|
||||
if (runtimeFiles && Object.keys(runtimeFiles).length > 0) unsupportedOptions.push("runtimeFiles");
|
||||
if (run) unsupportedOptions.push("run");
|
||||
if (dce) unsupportedOptions.push("dce");
|
||||
if (cjs2esm) unsupportedOptions.push("cjs2esm");
|
||||
if (matchesReference) unsupportedOptions.push("matchesReference");
|
||||
if (snapshotSourceMap) unsupportedOptions.push("snapshotSourceMap");
|
||||
if (expectExactFilesize) unsupportedOptions.push("expectExactFilesize");
|
||||
if (onAfterApiBundle) unsupportedOptions.push("onAfterApiBundle");
|
||||
if (bundleWarnings) unsupportedOptions.push("bundleWarnings");
|
||||
if (keepNames) unsupportedOptions.push("keepNames");
|
||||
if (emitDCEAnnotations) unsupportedOptions.push("emitDCEAnnotations");
|
||||
if (ignoreDCEAnnotations) unsupportedOptions.push("ignoreDCEAnnotations");
|
||||
if (bytecode) unsupportedOptions.push("bytecode");
|
||||
if (compile) unsupportedOptions.push("compile");
|
||||
if (features && features.length > 0) unsupportedOptions.push("features");
|
||||
if (outdir) unsupportedOptions.push("outdir (use outfile instead)");
|
||||
|
||||
if (unsupportedOptions.length > 0) {
|
||||
throw new Error(`Virtual mode does not support the following options: ${unsupportedOptions.join(", ")}`);
|
||||
}
|
||||
|
||||
return (async () => {
|
||||
// Prepare virtual files with dedent applied for strings, preserve binary content as-is
|
||||
// Use relative paths (strip leading /) to get consistent path comments in CSS output
|
||||
const virtualFiles: Record<string, string | Buffer | Uint8Array | Blob> = {};
|
||||
for (const [file, contents] of Object.entries(files)) {
|
||||
const relativePath = file.startsWith("/") ? file.slice(1) : file;
|
||||
virtualFiles[relativePath] = typeof contents === "string" ? dedent(contents) : contents;
|
||||
}
|
||||
|
||||
// Convert entrypoints to relative paths too
|
||||
const relativeEntryPoints = entryPoints.map(ep => (ep.startsWith("/") ? ep.slice(1) : ep));
|
||||
|
||||
const build = await Bun.build({
|
||||
entrypoints: relativeEntryPoints,
|
||||
files: virtualFiles,
|
||||
target,
|
||||
format,
|
||||
minify: {
|
||||
whitespace: minifyWhitespace,
|
||||
syntax: minifySyntax,
|
||||
identifiers: minifyIdentifiers,
|
||||
},
|
||||
external,
|
||||
plugins: typeof plugins === "function" ? [{ name: "plugin", setup: plugins }] : plugins,
|
||||
splitting,
|
||||
treeShaking,
|
||||
sourcemap: sourceMap,
|
||||
publicPath,
|
||||
banner,
|
||||
footer,
|
||||
packages,
|
||||
loader,
|
||||
jsx: jsx
|
||||
? {
|
||||
runtime: jsx.runtime,
|
||||
importSource: jsx.importSource,
|
||||
factory: jsx.factory,
|
||||
fragment: jsx.fragment,
|
||||
sideEffects: jsx.sideEffects,
|
||||
development: jsx.development,
|
||||
}
|
||||
: undefined,
|
||||
define,
|
||||
drop,
|
||||
conditions,
|
||||
});
|
||||
|
||||
const expectedErrors = bundleErrors
|
||||
? Object.entries(bundleErrors).flatMap(([file, v]) => v.map(error => ({ file, error })))
|
||||
: null;
|
||||
|
||||
if (!build.success) {
|
||||
// Collect actual errors from build logs
|
||||
const actualErrors = build.logs
|
||||
.filter(x => x.level === "error")
|
||||
.map(x => ({
|
||||
file: x.position?.file || "",
|
||||
error: x.message,
|
||||
}));
|
||||
|
||||
// Check if errors were expected
|
||||
if (expectedErrors && expectedErrors.length > 0) {
|
||||
const errorsLeft = [...expectedErrors];
|
||||
const unexpectedErrors: typeof actualErrors = [];
|
||||
|
||||
for (const error of actualErrors) {
|
||||
const i = errorsLeft.findIndex(item => error.file.endsWith(item.file) && error.error.includes(item.error));
|
||||
if (i === -1) {
|
||||
unexpectedErrors.push(error);
|
||||
} else {
|
||||
errorsLeft.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (unexpectedErrors.length > 0) {
|
||||
throw new Error(
|
||||
"Unexpected errors reported while bundling:\n" +
|
||||
unexpectedErrors.map(e => `${e.file}: ${e.error}`).join("\n") +
|
||||
"\n\nExpected errors:\n" +
|
||||
expectedErrors.map(e => `${e.file}: ${e.error}`).join("\n"),
|
||||
);
|
||||
}
|
||||
|
||||
if (errorsLeft.length > 0) {
|
||||
throw new Error(
|
||||
"Expected errors were not found while bundling:\n" +
|
||||
errorsLeft.map(e => `${e.file}: ${e.error}`).join("\n") +
|
||||
"\n\nActual errors:\n" +
|
||||
actualErrors.map(e => `${e.file}: ${e.error}`).join("\n"),
|
||||
);
|
||||
}
|
||||
|
||||
return testRef(id, opts);
|
||||
}
|
||||
|
||||
throw new Error(`Bundle failed:\n${actualErrors.map(e => `${e.file}: ${e.error}`).join("\n")}`);
|
||||
} else if (expectedErrors && expectedErrors.length > 0) {
|
||||
throw new Error(
|
||||
"Errors were expected while bundling:\n" + expectedErrors.map(e => `${e.file}: ${e.error}`).join("\n"),
|
||||
);
|
||||
}
|
||||
|
||||
// Build in-memory file cache from BuildArtifact outputs
|
||||
const outputCache: Record<string, string> = {};
|
||||
for (const output of build.outputs) {
|
||||
// Normalize path: "./a.css" -> "/a.css"
|
||||
let outputPath = output.path;
|
||||
if (outputPath.startsWith("./")) outputPath = outputPath.slice(1);
|
||||
if (!outputPath.startsWith("/")) outputPath = "/" + outputPath;
|
||||
outputCache[outputPath] = await output.text();
|
||||
}
|
||||
|
||||
// Determine the main output file path
|
||||
const mainOutputPath = Object.keys(outputCache)[0] || "/out.js";
|
||||
const outfileVirtual = outfile ? (outfile.startsWith("/") ? outfile : "/" + outfile) : mainOutputPath;
|
||||
|
||||
// Create API object that reads from in-memory cache
|
||||
const readFile = (file: string): string => {
|
||||
// Normalize the file path
|
||||
let normalizedFile = file;
|
||||
if (normalizedFile.startsWith("./")) normalizedFile = normalizedFile.slice(1);
|
||||
if (!normalizedFile.startsWith("/")) normalizedFile = "/" + normalizedFile;
|
||||
|
||||
// Try exact match first
|
||||
if (normalizedFile in outputCache) return outputCache[normalizedFile];
|
||||
|
||||
// For single-output builds, allow accessing the output by the configured outfile path
|
||||
const outputs = Object.keys(outputCache);
|
||||
if (outputs.length === 1 && normalizedFile === outfileVirtual) {
|
||||
return outputCache[outputs[0]];
|
||||
}
|
||||
|
||||
throw new Error(`Virtual file not found: ${file}. Available: ${Object.keys(outputCache).join(", ")}`);
|
||||
};
|
||||
|
||||
const api = {
|
||||
root: "/virtual",
|
||||
outfile: outfileVirtual,
|
||||
outdir: "/virtual/out",
|
||||
join: (...paths: string[]) => "/" + paths.join("/").replace(/^\/+/, ""),
|
||||
readFile,
|
||||
writeFile: (_file: string, _contents: string) => {
|
||||
throw new Error("writeFile not supported in virtual mode");
|
||||
},
|
||||
expectFile: (file: string) => expect(readFile(file)),
|
||||
prependFile: (_file: string, _contents: string) => {
|
||||
throw new Error("prependFile not supported in virtual mode");
|
||||
},
|
||||
appendFile: (_file: string, _contents: string) => {
|
||||
throw new Error("appendFile not supported in virtual mode");
|
||||
},
|
||||
assertFileExists: (file: string) => {
|
||||
readFile(file); // Will throw if not found
|
||||
},
|
||||
warnings: {} as Record<string, ErrorMeta[]>,
|
||||
options: opts,
|
||||
captureFile: (file: string, fnName = "capture") => extractCaptures(readFile(file), file, fnName),
|
||||
} satisfies BundlerTestBundleAPI;
|
||||
|
||||
if (onAfterBundle) {
|
||||
onAfterBundle(api);
|
||||
}
|
||||
|
||||
return testRef(id, opts);
|
||||
})();
|
||||
}
|
||||
|
||||
return (async () => {
|
||||
if (!backend) {
|
||||
backend =
|
||||
@@ -1321,42 +1555,7 @@ for (const [key, blob] of build.outputs) {
|
||||
},
|
||||
warnings: warningReference,
|
||||
options: opts,
|
||||
captureFile: (file, fnName = "capture") => {
|
||||
const fileContents = readFile(file);
|
||||
let i = 0;
|
||||
const length = fileContents.length;
|
||||
const matches = [];
|
||||
while (i < length) {
|
||||
i = fileContents.indexOf(fnName, i);
|
||||
if (i === -1) {
|
||||
break;
|
||||
}
|
||||
const start = i;
|
||||
let depth = 0;
|
||||
while (i < length) {
|
||||
const char = fileContents[i];
|
||||
if (char === "(") {
|
||||
depth++;
|
||||
} else if (char === ")") {
|
||||
depth--;
|
||||
if (depth === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (depth !== 0) {
|
||||
throw new Error(`Could not find closing paren for ${fnName} call in ${file}`);
|
||||
}
|
||||
matches.push(fileContents.slice(start + fnName.length + 1, i));
|
||||
i++;
|
||||
}
|
||||
|
||||
if (matches.length === 0) {
|
||||
throw new Error(`No ${fnName} calls found in ${file}`);
|
||||
}
|
||||
return matches;
|
||||
},
|
||||
captureFile: (file, fnName = "capture") => extractCaptures(readFile(file), file, fnName),
|
||||
} satisfies BundlerTestBundleAPI;
|
||||
|
||||
// DCE keep scan
|
||||
|
||||
Reference in New Issue
Block a user