cosmonarchy-bw-prerelease/tools/tbl/test/TblText.test.ts

120 lines
4.3 KiB
TypeScript
Raw Normal View History

import { describe, expect, it } from "bun:test"
import * as TblText from "../src/TblText.ts"
import { entry, failWith, show, succeed } from "./helpers.ts"
describe("encodeEntry", () => {
it("leaves backslashes and ordinary ASCII alone", () => {
expect(TblText.encodeEntry(entry("zerg\\avenger.grp"))).toBe("zerg\\avenger.grp")
})
it("joins the parts of a multi-part entry with <0>", () => {
expect(TblText.encodeEntry(entry("Terran Maverick", "*", "Ground Units")))
.toBe("Terran Maverick<0>*<0>Ground Units")
})
it("escapes colour codes, newlines and high bytes as <N>", () => {
const bytes = Uint8Array.from([0x04, 0x45, 0x01, 0x0a, 0xa4])
expect(TblText.encodeEntry([bytes])).toBe("<4>E<1><10><164>")
})
it("escapes a literal < as <<", () => {
expect(TblText.encodeEntry(entry("a<b"))).toBe("a<<b")
})
it("marks a wholly empty entry with <>", () => {
expect(TblText.encodeEntry(entry(""))).toBe("<>")
expect(TblText.encodeEntry(entry("", ""))).toBe("<0>")
})
it("protects a leading # from being read back as a comment", () => {
expect(TblText.encodeEntry(entry("#1 unit"))).toBe("<35>1 unit")
})
it("protects leading and trailing spaces from editors that trim them", () => {
expect(TblText.encodeEntry(entry(" pad "))).toBe("<32>pad<32>")
expect(TblText.encodeEntry(entry(" "))).toBe("<32>")
})
})
describe("decodeEntry", () => {
const parse = (line: string) => succeed(TblText.decodeEntry(line, 1))
it("round-trips every construct the encoder emits", async () => {
const cases = [
entry("zerg\\avenger.grp"),
entry("Terran Maverick", "*", "Ground Units"),
entry(""),
entry("", ""),
entry("#1 unit"),
entry(" pad "),
entry("a<b"),
[Uint8Array.from([0x04, 0x45, 0x01, 0x0a, 0xa4])]
]
for (const original of cases) {
expect(show([await parse(TblText.encodeEntry(original))])).toEqual(show([original]))
}
})
it("treats <0> at the edges as empty parts", async () => {
expect(show([await parse("<0>a<0>")])).toEqual([["", "a", ""]])
})
it("reads <> as nothing anywhere in the line", async () => {
expect(show([await parse("a<>b")])).toEqual([["ab"]])
})
it("rejects an unterminated escape", async () => {
const error = await failWith(TblText.decodeEntry("abc<12", 7))
expect(error.message).toContain("unterminated escape")
expect(error.line).toBe(7)
expect(error.column).toBe(4)
})
it("rejects a non-numeric escape", async () => {
const error = await failWith(TblText.decodeEntry("a<nope>", 1))
expect(error.message).toContain("invalid escape `<nope>`")
expect(error.column).toBe(2)
})
it("rejects a byte above 255", async () => {
const error = await failWith(TblText.decodeEntry("<256>", 1))
expect(error.message).toContain("out of range")
})
it("rejects non-ASCII characters, which would be ambiguous bytes", async () => {
const error = await failWith(TblText.decodeEntry("café", 3))
expect(error.message).toContain("non-ASCII")
expect(error.message).toContain("U+00E9")
expect(error.column).toBe(4)
})
})
describe("decode", () => {
it("skips comments and blank lines", async () => {
const table = await succeed(TblText.decode("# a comment\n\nfirst\n\n# another\nsecond\n"))
expect(show(table)).toEqual([["first"], ["second"]])
})
it("accepts CRLF line endings and a UTF-8 BOM", async () => {
const table = await succeed(TblText.decode("# c\r\nfirst\r\nsecond\r\n"))
expect(show(table)).toEqual([["first"], ["second"]])
})
it("reports the real line number of a bad escape", async () => {
const error = await failWith(TblText.decode("# banner\nfine\nbroken<9z>\n"))
expect(error.line).toBe(3)
})
it("emits a banner by default and omits it when bare", () => {
const table = [entry("a")]
expect(TblText.encode(table)).toStartWith("# tbl-text v1")
expect(TblText.encode(table, { header: false })).toBe("a\n")
expect(TblText.encode(table, { header: false, annotate: true })).toBe("# [0]\na\n")
})
it("round-trips a whole table through the text form", async () => {
const table = [entry("first"), entry("a", "b"), entry(""), entry("# hash")]
expect(show(await succeed(TblText.decode(TblText.encode(table))))).toEqual(show(table))
})
})