// node --test javascript/src/compose/build.test.mjs (deno for the type checks; skipped without it) import assert from "node:assert/strict"; import { execFileSync, spawnSync } from "node:child_process"; import { readFileSync } from "node:fs"; import { test } from "node:test"; import { compose } from "../../../examples/rust/compose/atlibs.modules.js"; import { composition } from "./build.js"; const example = new URL("../../../examples/rust/compose/", import.meta.url); test("compose.ts writes the composition.json beside it", () => { const out = execFileSync(process.execPath, ["compose.ts"], { cwd: example, encoding: "utf8" }); assert.equal(out, readFileSync(new URL("composition.json", example), "utf8")); }); test("build() runs the hosts' load checks and points at the line that made the instance or route", async () => { const lineOf = (re) => readFileSync(new URL(import.meta.url), "utf8").split("\n").findIndex((l) => re.test(l)) + 1; const refused = async (c, msg, marker) => assert.rejects(c.build(), (e) => e.message.startsWith(`composition: ${msg}`) && e.message.endsWith(`build.test.mjs:${lineOf(marker)}:${e.message.split(":").at(-1)}`), msg); const a = composition({ name: "a" }); a.route(a.instance("ui", compose), "com.example.compose.confirm", a.instance("x", compose)); // confirm-route await refused(a, "edge ui com.example.compose.confirm: x (compose) does not implement com.example.compose.confirm", /confirm-route$/); const b = composition({ name: "b" }); b.instance("x", compose, { input: { n: "0" } }); // string-n await refused(b, "instance x: input n must be of type integer", /string-n$/); const c = composition({ name: "c" }); const ui = c.instance("ui", compose); const x = c.instance("x", compose); c.route(ui, "com.example.compose.lookup", x); c.route(ui, "com.example.compose.lookup", x); // twice await refused(c, "edge ui com.example.compose.lookup: routed twice", /twice$/); const ok = composition({ name: "ok" }); ok.route(ok.instance("ui", compose), "com.example.compose.lookup", ok.instance("x", compose, { input: { n: 1 } })); assert.deepEqual((await ok.build()).edges, [{ from: "ui", nsid: "com.example.compose.lookup", to: "x", reason: "the example's lookup: another instance doubles n" }]); }); const deno = spawnSync("deno", ["--version"]).status === 0; test("types: wrong.ts's marked lines are type errors, compose.ts has none", { skip: !deno && "no deno" }, () => { const check = (file) => spawnSync("deno", ["check", file], { cwd: example, encoding: "utf8", env: { ...process.env, NO_COLOR: "1" } }); assert.equal(check("compose.ts").status, 0); const out = check("wrong.ts").stderr; const marked = readFileSync(new URL("wrong.ts", example), "utf8").split("\n").flatMap((l, i) => (l.includes("// ✗") ? [i + 1] : [])); const errors = [...new Set([...out.matchAll(/wrong\.ts:(\d+):\d+/g)].map((m) => Number(m[1])))]; assert.deepEqual(errors, marked, out); });