import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import url from "node:url"; const repo = path.resolve(path.dirname(url.fileURLToPath(import.meta.url)), "../../.."); const gate = path.join(repo, "apps/site/tools/design-gate.mjs"); const packagedGate = path.join(repo, "packages/skill/tools/design-gate.mjs"); const work = fs.mkdtempSync(path.join(os.tmpdir(), "design-gate-contract-")); const normalizedSource = (file) => fs.readFileSync(file, "utf8").replace(/\r\n/g, "\n"); assert.equal( normalizedSource(gate), normalizedSource(packagedGate), "apps/site/tools/design-gate.mjs must match packages/skill/tools/design-gate.mjs", ); const configFor = (page, extra = {}) => ({ pages: [{ name: "fixture", path: page, h1: "h1", ...extra }], widths: [375, 390], fontScales: [1, 1.3], checks: { meta: false, contrast: false, stack: false, rhythm: false, scaleMatrix: true, tapTargets: false, leftInset: false, deadCss: false, cssHygiene: false, }, }); const run = (name, html, extra = {}) => { const page = path.join(work, `${name}.html`); const config = path.join(work, `${name}.json`); fs.writeFileSync(page, html.replace("", "")); fs.writeFileSync(config, JSON.stringify(configFor(page, extra))); return spawnSync(process.execPath, [gate, `--config=${config}`], { cwd: work, encoding: "utf8" }); }; const outputOf = (result) => result.stdout + result.stderr; try { const flexible = run("flexible", `

A flexible display heading

`); assert.equal(flexible.status, 0, outputOf(flexible)); assert.match(outputOf(flexible), /h1 보임 — [2-9]줄\(줄수 계약 없음\)/); const capped = run("capped", `

Heading that must wrap

`, { h1MaxLines: 1 }); assert.equal(capped.status, 1, outputOf(capped)); assert.match(outputOf(capped), /h1 — \d+줄\(최대 1줄\)/); const expanded = run("expanded", `

Scale tolerant design

`, { h1MaxLines: 1 }); assert.equal(expanded.status, 0, outputOf(expanded)); assert.match(outputOf(expanded), /375@1x:1줄/); assert.match(outputOf(expanded), /375@1\.3x:[2-9]줄/); const clipped = run("clipped", `

Heading that is clipped when enlarged

`); assert.equal(clipped.status, 1, outputOf(clipped)); assert.match(outputOf(clipped), /제목 텍스트 클리핑/); const verticallyClipped = run("vertically-clipped", `

Title that becomes vertically clipped

`); assert.equal(verticallyClipped.status, 1, outputOf(verticallyClipped)); assert.match(outputOf(verticallyClipped), /제목 텍스트 클리핑 .+\(y\)/); const missing = run("missing", `

Existing heading

`, { h1: ".missing" }); assert.equal(missing.status, 1, outputOf(missing)); assert.match(outputOf(missing), /h1 선택자 없음 \(\.missing\)/); const empty = run("empty", `

`); assert.equal(empty.status, 1, outputOf(empty)); assert.match(outputOf(empty), /h1 — 텍스트 없음/); console.log("design-gate-contract: 7 passed"); } finally { fs.rmSync(work, { recursive: true, force: true }); }