import test from "node:test"; import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import { parseJpwabc } from "../dist/domain/jpwabc.js"; import { modelFromParse, createEmptyDocument, rational, createNoteEvent } from "../dist/domain/score-model.js"; import { DeterministicFontMeasurementProvider } from "../dist/layout/font-measure.js"; import { DEFAULT_PAPER_CONFIG, layoutDocument, layoutSummary } from "../dist/layout/layout-engine.js"; import { probeSvg, renderSnapshotSvg } from "../dist/layout/svg-renderer.js"; async function kangdingModel() { const bytes = await readFile("research/jpw7/demo/JP-Word练习曲-歌曲-康定情歌.jpwabc"); return modelFromParse(parseJpwabc(bytes), { fileName: "康定情歌.jpwabc" }); } test("M3 waits for fonts, produces fixed A4 geometry and the 16-measure/6-line baseline", async () => { const provider = new DeterministicFontMeasurementProvider(); const snapshot = await layoutDocument(await kangdingModel(), DEFAULT_PAPER_CONFIG, provider); const summary = layoutSummary(snapshot); assert.deepEqual(snapshot.pageSizeMm, { width: 210, height: 297 }); assert.deepEqual(snapshot.contentRectMm, { x: 20, y: 20, width: 170, height: 257 }); assert.equal(summary.pageCount, 1); assert.equal(summary.lineCount, 6); assert.equal(summary.measureCount, 16); assert.ok(snapshot.fontMeasurements.length > 0); assert.ok(snapshot.fontMeasurements.every((measurement) => measurement.loaded === true)); assert.equal(snapshot.pages[0].widthMm, 210); assert.equal(snapshot.pages[0].heightMm, 297); }); test("M3 layout is deterministic for identical model, config and measurement provider", async () => { const model = await kangdingModel(); const first = await layoutDocument(model, DEFAULT_PAPER_CONFIG, new DeterministicFontMeasurementProvider()); const second = await layoutDocument(model, DEFAULT_PAPER_CONFIG, new DeterministicFontMeasurementProvider()); assert.equal(first.deterministicKey, second.deterministicKey); assert.deepEqual(first.pages, second.pages); assert.deepEqual(first.warnings, second.warnings); }); test("M3 reports an unbreakable over-wide measure instead of clipping it", async () => { const document = createEmptyDocument("overflow-document"); const event = createNoteEvent("wide-event", 1, rational(1)); event.source = { raw: "X".repeat(800), range: null }; document.voices.push({ id: "wide-voice", name: "宽小节", sectionId: null, visible: true, measures: [{ id: "wide-measure", ordinal: 0, beats: rational(1), events: [event] }] }); const snapshot = await layoutDocument(document, DEFAULT_PAPER_CONFIG, new DeterministicFontMeasurementProvider()); const warning = snapshot.warnings.find((item) => item.code === "MEASURE_OVERFLOW"); assert.ok(warning); assert.equal(warning.severity, "error"); assert.equal(snapshot.measures["wide-measure"].overflow, true); }); test("M3 SVG renderer preserves A4 physical units, semantic IDs and layer separation", async () => { const snapshot = await layoutDocument(await kangdingModel(), DEFAULT_PAPER_CONFIG, new DeterministicFontMeasurementProvider()); const [svg] = renderSnapshotSvg(snapshot); const probe = probeSvg(svg); assert.equal(probe.width, "210mm"); assert.equal(probe.height, "297mm"); assert.equal(probe.viewBox, "0 0 21000 29700"); assert.equal(probe.pageNumber, 1); assert.equal(probe.hasScoreLayer, true); assert.equal(probe.hasAssistiveLayer, false); assert.ok(probe.dataIds.length >= Object.keys(snapshot.symbols).length); const titleFontSize = Number(svg.match(/data-role="document-id"[^>]*font-size="([0-9.]+)"/)?.[1] ?? 0); const noteFontSize = Number(svg.match(/data-kind="note"[^>]*>[\s\S]*?font-size="([0-9.]+)"/)?.[1] ?? 0); assert.ok(titleFontSize > 100); assert.ok(noteFontSize > 100); const accessibleProbe = probeSvg(renderSnapshotSvg(snapshot, true)[0]); assert.equal(accessibleProbe.hasAssistiveLayer, true); }); test("M3 overflow and orphan diagnostics are visible in the snapshot, not hidden by renderer", async () => { const snapshot = await layoutDocument(await kangdingModel(), DEFAULT_PAPER_CONFIG, new DeterministicFontMeasurementProvider()); assert.ok(snapshot.warnings.some((warning) => warning.code === "ORPHANED_ATTACHMENT")); assert.ok(snapshot.pages.every((page) => page.widthMm === 210 && page.heightMm === 297)); });