diff --git a/tests/boundary/filesystem/javascriptExportShapeComparison.test.ts b/tests/boundary/filesystem/javascriptExportShapeComparison.test.ts index 365cbcdf8..e4cbfcc30 100644 --- a/tests/boundary/filesystem/javascriptExportShapeComparison.test.ts +++ b/tests/boundary/filesystem/javascriptExportShapeComparison.test.ts @@ -113,18 +113,20 @@ describe("JavaScript export return-shape comparison", () => { }); describe("JavaScript export return-shape pairing limits", () => { - it.each([ - { presence: "absent", side: "left" }, - { presence: "unknown-coverage", side: "left" }, - { presence: "absent", side: "right" }, - { presence: "unknown-coverage", side: "right" }, - ] as const)( - "does not pair an inline $side discriminant whose presence is $presence", - async ({ presence, side }) => { - const [left, right] = await analyzeSources({ - left: 'export default () => ({ kind: "result", count: 1 });', - right: 'export default () => ({ kind: "result", total: 2 });', - }); + it("does not pair an inline discriminant with absent or unknown presence", async () => { + const rows = [ + { presence: "absent", side: "left" }, + { presence: "unknown-coverage", side: "left" }, + { presence: "absent", side: "right" }, + { presence: "unknown-coverage", side: "right" }, + ] as const; + const [left, right] = await analyzeSources({ + left: 'export default () => ({ kind: "result", count: 1 });', + right: 'export default () => ({ kind: "result", total: 2 });', + }); + + for (const { presence, side } of rows) { + const label = `${side} discriminant presence ${presence}`; const selected = side === "left" ? left : right; const graph = createJavaScriptApplicationGraph({ schema: "JavaScriptApplicationGraph", @@ -165,26 +167,28 @@ describe("JavaScript export return-shape pairing limits", () => { side === "left" ? compare({ ...left, graph }, right) : compare(left, { ...right, graph }); - expect(result.coverage).toMatchObject({ + expect(result.coverage, label).toMatchObject({ status: "partial", paired_variants: 0, unpaired_left_variants: 1, unpaired_right_variants: 1, }); - expect(result.changes).toHaveLength(2); + expect(result.changes, label).toHaveLength(2); expect( result.changes.every( ({ status, discriminant }) => status === "unknown" && discriminant === null, ), + label, ).toBe(true); expect( result.property_inventories.every( ({ paired, discriminant }) => !paired && discriminant === null, ), + label, ).toBe(true); - }, - ); + } + }); it("does not pair discriminants that an unknown trailing spread can overwrite", async () => { const [left, right] = await analyzeSources({ diff --git a/tests/boundary/filesystem/javascriptPackageExportTargets.test.ts b/tests/boundary/filesystem/javascriptPackageExportTargets.test.ts index 1c73bd894..9a8da4a52 100644 --- a/tests/boundary/filesystem/javascriptPackageExportTargets.test.ts +++ b/tests/boundary/filesystem/javascriptPackageExportTargets.test.ts @@ -169,27 +169,13 @@ const nativeResultSchema = z.object({ code: z.string().nullable(), }); -it.each( - (["import", "require"] as const).flatMap((moduleKind) => - cases.map((testCase) => ({ ...testCase, moduleKind })), - ), -)( - "matches Node $moduleKind target selection for $name", - async ({ - moduleKind, - name, - exports, - expected, - nodeError, - rejectedTarget, - }) => { +it.each(cases)( + "matches Node import and require target selection for $name", + async ({ name, exports, expected, nodeError, rejectedTarget }) => { const root = await createTestTempDirectory("rea-package-target-"); - const mainPath = moduleKind === "import" ? "main.mjs" : "main.cjs"; const files = { - [mainPath]: - moduleKind === "import" - ? 'import value from "fixture"; export default value;' - : 'module.exports = require("fixture");', + "main.mjs": 'import value from "fixture"; export default value;', + "main.cjs": 'module.exports = require("fixture");', "node_modules/fixture/package.json": JSON.stringify({ exports }), ...Object.fromEntries( [ @@ -209,57 +195,78 @@ it.each( await writeFile(join(root, path), text); }), ); - const runner = - moduleKind === "import" - ? 'const load = async () => (await import("fixture")).default;' - : 'const load = async () => require("fixture");'; - const native = await promisify(execFile)( - process.execPath, - [ - "--input-type", - moduleKind === "import" ? "module" : "commonjs", - "--eval", - `${runner} load().then(value => console.log(JSON.stringify({value,code:null})), error => console.log(JSON.stringify({value:null,code:error.code ?? error.name})));`, - ], - { cwd: root, timeout: 5_000 }, - ); - const oracle = nativeResultSchema.parse(JSON.parse(native.stdout)); - expect(oracle.value, name).toBe(expected); - if (nodeError !== undefined) - expect(oracle.code).toBe( - moduleKind === "import" && nodeError === "MODULE_NOT_FOUND" - ? "ERR_MODULE_NOT_FOUND" - : nodeError, + for (const moduleKind of ["import", "require"] as const) { + const runner = + moduleKind === "import" + ? 'const load = async () => (await import("fixture")).default;' + : 'const load = async () => require("fixture");'; + const native = await promisify(execFile)( + process.execPath, + [ + "--input-type", + moduleKind === "import" ? "module" : "commonjs", + "--eval", + `${runner} load().then(value => console.log(JSON.stringify({value,code:null})), error => console.log(JSON.stringify({value:null,code:error.code ?? error.name})));`, + ], + { cwd: root, timeout: 5_000 }, ); + const oracle = nativeResultSchema.parse(JSON.parse(native.stdout)); + expect(oracle.value, `${name} (${moduleKind})`).toBe(expected); + if (nodeError !== undefined) + expect(oracle.code, `${name} (${moduleKind})`).toBe( + moduleKind === "import" && nodeError === "MODULE_NOT_FOUND" + ? "ERR_MODULE_NOT_FOUND" + : nodeError, + ); + } + const result = await reconstructJavaScriptArtifact({ input_path: root, format: "directory", }); - const edge = result.graph.edges.find( - ({ properties }) => - properties.specifier === "fixture" && - properties.resolution_status !== undefined && - properties.resolution_status !== null, - ); - expect(edge).toBeDefined(); - expect(edge?.properties.resolved_path).toBe(expected); - if (expected !== null) { - expect(edge?.properties.resolution_status).toBe("resolved"); - } else { - expect(edge?.properties.resolution_status).not.toBe("resolved"); - } - if (rejectedTarget !== undefined) { - expect(edge?.properties.resolution_status).toBe("rejected"); - expect(edge?.evidence.limitations.join(" ")).toContain( - JSON.stringify(rejectedTarget), + for (const mainPath of ["main.mjs", "main.cjs"] as const) { + const edge = result.graph.edges.find( + ({ evidence, properties }) => + properties.specifier === "fixture" && + properties.resolution_status !== undefined && + properties.resolution_status !== null && + evidence.location.available && + evidence.location.value.kind === "source-range" && + evidence.location.value.source === mainPath, ); - expect(edge?.evidence.limitations.join(" ")).toContain("exports target"); - expect(edge?.evidence.limitations.join(" ")).not.toContain( - "not valid package JSON", + expect(edge, `${name} (${mainPath})`).toBeDefined(); + expect(edge?.properties.resolved_path, `${name} (${mainPath})`).toBe( + expected, ); - expect(edge?.evidence.location).toMatchObject({ - value: { kind: "source-range", source: mainPath }, - }); + if (expected !== null) { + expect( + edge?.properties.resolution_status, + `${name} (${mainPath})`, + ).toBe("resolved"); + } else { + expect( + edge?.properties.resolution_status, + `${name} (${mainPath})`, + ).not.toBe("resolved"); + } + if (rejectedTarget !== undefined) { + expect( + edge?.properties.resolution_status, + `${name} (${mainPath})`, + ).toBe("rejected"); + expect(edge?.evidence.limitations.join(" ")).toContain( + JSON.stringify(rejectedTarget), + ); + expect(edge?.evidence.limitations.join(" ")).toContain( + "exports target", + ); + expect(edge?.evidence.limitations.join(" ")).not.toContain( + "not valid package JSON", + ); + expect(edge?.evidence.location).toMatchObject({ + value: { kind: "source-range", source: mainPath }, + }); + } } }, );