86 lines
4.2 KiB
JavaScript
86 lines
4.2 KiB
JavaScript
import { readFile } from "node:fs/promises";
|
|
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { encodeSource, hasExactRoundTrip, parseJpwabc, serializeBytes, serializeText } from "../dist/domain/jpwabc.js";
|
|
|
|
const readRaw = (path) => readFile(path);
|
|
|
|
test("M1 decodes UTF-16LE/BOM and preserves the 康定情歌 source", async () => {
|
|
const result = parseJpwabc(await readRaw("research/jpw7/demo/JP-Word练习曲-歌曲-康定情歌.jpwabc"));
|
|
assert.equal(result.source.encoding, "utf-16le");
|
|
assert.equal(result.source.bom, "utf-16le");
|
|
assert.equal(result.header.formatVersion, "1.0");
|
|
assert.equal(result.header.productVersion, "7.30");
|
|
assert.deepEqual(result.sections.map((section) => section.name), ["<preamble>", ".Options", ".Fonts", ".Title", ".Voice", ".Words", ".Attachments", ".Page"]);
|
|
assert.ok(result.options.document.length >= 0);
|
|
assert.ok(result.fonts.length > 0);
|
|
assert.ok(result.titles.some((entry) => entry.field === "Title" && entry.value === "康定情歌"));
|
|
assert.ok(result.voices.some((line) => line.tokens.some((token) => token.kind === "note")));
|
|
assert.ok(result.voices.some((line) => line.tokens.some((token) => token.kind === "barline")));
|
|
assert.ok(result.words.length >= 4);
|
|
assert.ok(result.attachments.length > 0);
|
|
assert.equal(hasExactRoundTrip(result), true);
|
|
assert.equal(serializeText(result), result.source.text);
|
|
assert.deepEqual([...serializeBytes(result)], [...result.source.bytes]);
|
|
const reparsed = parseJpwabc(serializeBytes(result));
|
|
assert.equal(serializeText(reparsed), serializeText(result));
|
|
assert.deepEqual([...serializeBytes(reparsed)], [...result.source.bytes]);
|
|
});
|
|
|
|
test("M1 detects UTF-16BE and UTF-8 BOM variants", () => {
|
|
const source = ".Title\r\nTitle = 编码探针\r\n";
|
|
const bigEndian = parseJpwabc(encodeSource(source, "utf-16be", "utf-16be"));
|
|
const utf8 = parseJpwabc(encodeSource(source, "utf-8", "utf-8"));
|
|
assert.equal(bigEndian.source.encoding, "utf-16be");
|
|
assert.equal(bigEndian.source.bom, "utf-16be");
|
|
assert.equal(utf8.source.encoding, "utf-8");
|
|
assert.equal(utf8.source.bom, "utf-8");
|
|
assert.equal(hasExactRoundTrip(bigEndian), true);
|
|
assert.equal(hasExactRoundTrip(utf8), true);
|
|
});
|
|
|
|
test("M1 retains unknown sections, unknown options and source ranges", () => {
|
|
const source = [
|
|
"// ******** JPW-ABC File Ver 9.9 ********",
|
|
"",
|
|
".Options",
|
|
"KnownLater = {opaque:value}",
|
|
"",
|
|
".MysteryBlock",
|
|
"Payload {vendor:private} ",
|
|
"",
|
|
".Voice",
|
|
"1_ {vendor:private} | 0__",
|
|
""
|
|
].join("\r\n");
|
|
const result = parseJpwabc(encodeSource(source, "utf-16le", "utf-16le"));
|
|
assert.equal(result.source.encoding, "utf-16le");
|
|
assert.equal(result.sections.find((section) => section.name === ".MysteryBlock")?.known, false);
|
|
assert.ok(result.options.unknownDocument.some((entry) => entry.key === "KnownLater"));
|
|
assert.ok(result.unknownBlocks.some((block) => block.reason === "unknown-section"));
|
|
assert.ok(result.unknownTokens.some((token) => token.head === "vendor"));
|
|
assert.ok(result.diagnostics.some((item) => item.code === "UNKNOWN_SECTION"));
|
|
assert.equal(result.compatibility.originalExport, "blocked");
|
|
assert.equal(hasExactRoundTrip(result), true);
|
|
assert.ok(result.sections.every((section) => section.range.byteEnd >= section.range.byteStart));
|
|
});
|
|
|
|
test("M1 reports malformed structures without dropping their bytes", () => {
|
|
const source = ".Voice\n1_ {unclosed\n";
|
|
const result = parseJpwabc(encodeSource(source, "utf-8", "none"));
|
|
assert.ok(result.diagnostics.some((item) => item.code === "UNCLOSED_BRACE"));
|
|
assert.equal(result.compatibility.projectSave, "blocked");
|
|
assert.equal(result.compatibility.originalExport, "blocked");
|
|
assert.equal(hasExactRoundTrip(result), true);
|
|
});
|
|
|
|
test("M1 classification report covers all 33 fixtures", async () => {
|
|
const report = JSON.parse(await readFile("acceptance/m1/sample-classification.json", "utf8"));
|
|
assert.equal(report.sampleCountExpected, 33);
|
|
assert.equal(report.sampleCountActual, 33);
|
|
assert.equal(report.allSourceBytesPreserved, true);
|
|
assert.equal(report.failures.length, 0);
|
|
assert.equal(report.samples.length, 33);
|
|
assert.ok(report.samples.every((sample) => sample.status && sample.sourcePreserved === true));
|
|
});
|