mirror of
https://gitlab.com/the-no-frauds-club/cosmonarchy-bw-prerelease.git
synced 2026-09-17 01:01:12 +00:00
120 lines
4.3 KiB
TypeScript
120 lines
4.3 KiB
TypeScript
|
|
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))
|
|||
|
|
})
|
|||
|
|
})
|