import { test } from "node:test"; import assert from "node:assert/strict"; import { readdirSync, readFileSync } from "node:fs"; import { spawnSync } from "node:child_process"; import { run, fixtures, toolsDir } from "./helpers.mjs"; import { join } from "node:path"; const env = { COURSE_ROOT: join(fixtures, "course") }; const audit = (ws) => run("coupling-audit.mjs", [join(fixtures, ws)], env); test("audit_flags_card_name_in_engine", () => { const r = audit("ws-coupled"); assert.equal(r.code, 1); assert.match(r.out, /core\.rs:2/); assert.match(r.out, /core\.rs:3/); assert.match(r.out, /Baron Rivendare/); assert.doesNotMatch(r.out, /cards\.rs/); }); test("audit_ignores_content_dir", () => { const r = audit("ws-content-only"); assert.equal(r.code, 0, r.out); }); test("audit_ignores_grammar_words", () => { const r = audit("ws-grammar"); assert.equal(r.code, 0, r.out); }); test("audit_ignores_mechanic_words_in_real_catalog", () => { // Uses the real course/catalog.json (no COURSE_ROOT override). const r = run("coupling-audit.mjs", [join(fixtures, "ws-mechanics")]); assert.equal(r.code, 0, r.out); }); test("audit_allows_ordinary_lowercase_identifiers", () => { // `key`, `flag`, `wall`, `water` are noun words in the real catalog, but as lowercase // identifiers they are ordinary code, not content coupling. const r = run("coupling-audit.mjs", [join(fixtures, "ws-ordinary-words")]); assert.equal(r.code, 0, r.out); }); test("audit_flags_noun_words_as_names_strings_and_variants", () => { // Uses the real catalog: string literal "flag", KEY, Kind::Door and "text:WALL" are coupling; // the lowercase identifiers on line 1 are not. const r = run("coupling-audit.mjs", [join(fixtures, "ws-noun-coupled")]); assert.equal(r.code, 1); assert.doesNotMatch(r.out, /core\.rs:1:/); assert.match(r.out, /core\.rs:2: names "FLAG"/); assert.match(r.out, /core\.rs:3: names "KEY"/); assert.match(r.out, /core\.rs:4: names "DOOR"/); assert.match(r.out, /core\.rs:5: names "WALL"/); }); test("tools_do_not_rely_on_import_meta_main", () => { // `import.meta.main` is undefined before Node 24.2, so a tool guarded by it exits 0 // having checked nothing. Tools use isMain() from lib.mjs (an argv comparison). for (const f of readdirSync(toolsDir).filter((f) => f.endsWith(".mjs"))) { assert.doesNotMatch(readFileSync(join(toolsDir, f), "utf8"), /import\.meta\.main/, f); } }); test("audit_runs_from_a_relative_path", () => { const root = join(toolsDir, ".."); const r = spawnSync("node", ["tools/coupling-audit.mjs", join(fixtures, "ws-coupled")], { cwd: root, encoding: "utf8", env: { ...process.env, COURSE_ROOT: join(fixtures, "course") }, }); assert.equal(r.status, 1, r.stdout + r.stderr); assert.match(r.stderr, /Baron Rivendare/); });