// ============================================================================= // library.test.ts — bun test suite for MonoDisplay library // // run: bun test // ============================================================================= import { test, expect, describe } from "bun:test"; import { BinaryReader, MonoDisplayParser, MonoDisplayFile, ElementType, SectionType, buildBinBuffer, type Image2DElement, type MonoFormatElementsAlways, type MonoFormatImage2D, } from "../src/index"; // --------------------------------------------------------------------------- // Low-level buffer helpers // --------------------------------------------------------------------------- // Real magic: 0xAF 0x7E 0x2B 0x63 const MAGIC_BYTES = new Uint8Array([0xAF, 0x7E, 0x2B, 0x63]); function concat(...parts: (Uint8Array | ArrayBuffer)[]): ArrayBuffer { const arrays = parts.map((p: Uint8Array | ArrayBuffer) => p instanceof ArrayBuffer ? new Uint8Array(p) : p ); const total = arrays.reduce((s, a) => s + a.byteLength, 0); const out = new Uint8Array(total); let off = 0; for (const a of arrays) { out.set(a, off); off += a.byteLength; } return out.buffer; } function u8(n: number) { return new Uint8Array([n]); } function u16le(n: number) { const b = new Uint8Array(2); new DataView(b.buffer).setUint16(0, n, true); return b; } function u24le(n: number) { return new Uint8Array([n & 0xFF, (n >> 8) & 0xFF, (n >> 16) & 0xFF]); } function u32le(n: number) { const b = new Uint8Array(4); new DataView(b.buffer).setUint32(0, n, true); return b; } // --------------------------------------------------------------------------- // BinaryReader // --------------------------------------------------------------------------- describe("BinaryReader", () => { test("readByte", () => { const r = new BinaryReader(new Uint8Array([0xAB]).buffer); expect(r.readByte()).toBe(0xAB); expect(r.remaining).toBe(0); }); test("readUint16 LE", () => { const r = new BinaryReader(new Uint8Array([0x34, 0x12]).buffer); expect(r.readUint16()).toBe(0x1234); }); test("readShort alias", () => { const r = new BinaryReader(new Uint8Array([0x01, 0x00]).buffer); expect(r.readShort()).toBe(1); }); test("readUint24 LE", () => { // 0x010203 LE → bytes [0x03, 0x02, 0x01] const r = new BinaryReader(new Uint8Array([0x03, 0x02, 0x01]).buffer); expect(r.readUint24()).toBe(0x010203); }); test("readUint32 LE", () => { const r = new BinaryReader(new Uint8Array([0x78, 0x56, 0x34, 0x12]).buffer); expect(r.readUint32()).toBe(0x12345678); }); test("readUint64 LE", () => { const bytes = new Uint8Array(8); new DataView(bytes.buffer).setBigUint64(0, 0x0102030405060708n, true); const r = new BinaryReader(bytes.buffer); expect(r.readUint64()).toBe(0x0102030405060708n); }); test("offset advances through mixed reads", () => { const r = new BinaryReader(new Uint8Array([1, 2, 3, 4, 5, 6]).buffer); r.readByte(); expect(r.offset).toBe(1); r.readUint16(); expect(r.offset).toBe(3); expect(() => r.readUint32()).toThrow(RangeError); // only 3 remain }); test("RangeError on exhaustion", () => { const r = new BinaryReader(new Uint8Array([0x01]).buffer); r.readByte(); expect(() => r.readByte()).toThrow(RangeError); }); test("seek", () => { const r = new BinaryReader(new Uint8Array([10, 20, 30]).buffer); r.seek(2); expect(r.readByte()).toBe(30); }); test("seek out of bounds throws", () => { const r = new BinaryReader(new Uint8Array([1, 2]).buffer); expect(() => r.seek(5)).toThrow(RangeError); }); test("readUtf8 ASCII", () => { const enc = new TextEncoder().encode("hello"); const r = new BinaryReader(enc.buffer); expect(r.readUtf8(5)).toBe("hello"); }); test("readUtf8 multi-byte codepoints", () => { const str = "日本語🚀"; const enc = new TextEncoder().encode(str); const r = new BinaryReader(enc.buffer); expect(r.readUtf8(enc.byteLength)).toBe(str); }); }); // --------------------------------------------------------------------------- // MonoDisplayParser — header validation // --------------------------------------------------------------------------- describe("MonoDisplayParser — header", () => { const parser = new MonoDisplayParser(); test("rejects wrong magic", () => { const bad = new Uint8Array(16).fill(0); bad[0] = 0xDE; bad[1] = 0xAD; bad[2] = 0xBE; bad[3] = 0xEF; expect(() => parser.parse(bad.buffer)).toThrow(/magic/i); }); test("rejects version 0 (illegal)", () => { const buf = concat(MAGIC_BYTES, u32le(0), u16le(0), u16le(0)); expect(() => parser.parse(buf)).toThrow(/version/i); }); test("rejects version > 1 (reserved)", () => { const buf = concat(MAGIC_BYTES, u32le(2), u16le(0), u16le(0)); expect(() => parser.parse(buf)).toThrow(/version/i); }); }); // --------------------------------------------------------------------------- // MonoDisplayParser — Image2D round-trip // --------------------------------------------------------------------------- describe("MonoDisplayParser — Image2D round-trip", () => { const parser = new MonoDisplayParser(); /** * Build a minimal valid file buffer containing one ElementsAlways section * with one Image2D element. * * File header (12 bytes): * magic u32le + version u32le + numSections u16le + reserved u16le * * Section header (4 bytes): * type u8 + size u24le (INCLUDES 4-byte header) * * ElementsAlways section data: * flags u16le + numElements u16le * * Image2D element (12 fixed bytes + packed pixel data): * type u16le + xOffset u16le + yOffset u16le + width u16le + height u16le + reserved u16le * + packed 1bpp pixel data + padding to 4-byte boundary */ function makeImage2DFile( width: number, height: number, pixels: Uint8Array, xOffset: number = 0, yOffset: number = 0, ): ArrayBuffer { // Pack pixels to 1bpp const total = width * height; const packedLen = (total + 7) >> 3; const packed = new Uint8Array(packedLen); for (let i = 0; i < total; i++) { if (pixels[i]) packed[i >> 3] = (packed[i >> 3] ?? 0) | (1 << (i & 7)); } // Padding to 4-byte boundary for element (12 fixed bytes + packed data) const elementDataLen = 12 + packedLen; const padLen = (4 - (elementDataLen & 3)) & 3; const padding = new Uint8Array(padLen); // Image2D element bytes const element = new Uint8Array(concat( u16le(1), // type = Image2D u16le(xOffset), u16le(yOffset), u16le(width), u16le(height), u16le(0), // reserved packed, padding, )); // Section data: flags + numElements + element const sectionData = new Uint8Array(concat( u16le(0), // flags = 0 u16le(1), // numElements = 1 element, )); // Section header: type u8 + size u24le (4 header bytes + data) const sectionSize = 4 + sectionData.byteLength; const sectionHeader = new Uint8Array(concat( u8(1), // SectionType.ElementsAlways = 1 u24le(sectionSize), )); // File header const fileHeader = new Uint8Array(concat( MAGIC_BYTES, u32le(1), // version = 1 u16le(1), // numSections = 1 u16le(0), // reserved )); return concat(fileHeader, sectionHeader, sectionData); } test("sections[0].sectionType === SectionType.ElementsAlways", () => { const pixels = new Uint8Array([1, 0, 0, 1]); const buf = makeImage2DFile(2, 2, pixels); const result = parser.parse(buf); expect(result.sections.length).toBe(1); expect(result.sections[0]!.sectionType).toBe(SectionType.ElementsAlways); }); test("elements[0].type === ElementType.Image2D", () => { const pixels = new Uint8Array([1, 0, 0, 1]); const buf = makeImage2DFile(2, 2, pixels); const result = parser.parse(buf); const section = result.sections[0] as MonoFormatElementsAlways; expect(section.elements.length).toBe(1); expect(section.elements[0]!.type).toBe(ElementType.Image2D); }); test("correct dimensions after parse", () => { const pixels = new Uint8Array([0, 1, 1, 0]); const buf = makeImage2DFile(2, 2, pixels); const result = parser.parse(buf); const section = result.sections[0] as MonoFormatElementsAlways; const el = section.elements[0] as MonoFormatImage2D; expect(el.image.width).toBe(2); expect(el.image.height).toBe(2); }); test("correct pixel data after round-trip", () => { // pixels: [1, 0, 0, 1] → top-left and bottom-right are on const pixels = new Uint8Array([1, 0, 0, 1]); const buf = makeImage2DFile(2, 2, pixels); const result = parser.parse(buf); const section = result.sections[0] as MonoFormatElementsAlways; const el = section.elements[0] as MonoFormatImage2D; expect(Array.from(el.image.pixels)).toEqual([1, 0, 0, 1]); }); test("xOffset and yOffset are preserved", () => { const pixels = new Uint8Array([1, 1, 1, 1]); const buf = makeImage2DFile(2, 2, pixels, 5, 3); const result = parser.parse(buf); const section = result.sections[0] as MonoFormatElementsAlways; const el = section.elements[0] as MonoFormatImage2D; expect(el.xOffset).toBe(5); expect(el.yOffset).toBe(3); }); }); // --------------------------------------------------------------------------- // MonoDisplayFile // --------------------------------------------------------------------------- describe("MonoDisplayFile", () => { const parser = new MonoDisplayParser(); test("toBuffer() produces parseable output for Image2D element", () => { const pixels = new Uint8Array([1, 0, 1, 0]); const el: Image2DElement = { type: ElementType.Image2D, pixels, width: 2, height: 2 }; const file = new MonoDisplayFile({ elements_always: [el] }); const buf = file.toBuffer(); const result = parser.parse(buf); expect(result.sections.length).toBeGreaterThan(0); const section = result.sections[0] as MonoFormatElementsAlways; expect(section.sectionType).toBe(SectionType.ElementsAlways); expect(section.elements.length).toBe(1); const parsed = section.elements[0] as MonoFormatImage2D; expect(parsed.type).toBe(ElementType.Image2D); expect(Array.from(parsed.image.pixels)).toEqual([1, 0, 1, 0]); }); test("xOffset/yOffset are round-tripped", () => { const pixels = new Uint8Array([1, 1, 1, 1]); const el: Image2DElement = { type: ElementType.Image2D, pixels, width: 2, height: 2, xOffset: 10, yOffset: 7, }; const file = new MonoDisplayFile({ elements_always: [el] }); const buf = file.toBuffer(); const result = parser.parse(buf); const section = result.sections[0] as MonoFormatElementsAlways; const parsed = section.elements[0] as MonoFormatImage2D; expect(parsed.xOffset).toBe(10); expect(parsed.yOffset).toBe(7); }); test("flags.drawFront default is applied", () => { const pixels = new Uint8Array([0]); const el: Image2DElement = { type: ElementType.Image2D, pixels, width: 1, height: 1 }; const file = new MonoDisplayFile({ elements_always: [el] }); const buf = file.toBuffer(); const result = parser.parse(buf); const section = result.sections[0] as MonoFormatElementsAlways; // Default flags should have drawFront = true (flags byte bit 0 = 1) expect(section.flags.drawFront).toBe(true); }); test("empty elements_always array is valid and produces parseable output", () => { const file = new MonoDisplayFile({ elements_always: [] }); const buf = file.toBuffer(); const result = parser.parse(buf); expect(result.sections.length).toBeGreaterThan(0); const section = result.sections[0] as MonoFormatElementsAlways; expect(section.sectionType).toBe(SectionType.ElementsAlways); expect(section.elements.length).toBe(0); }); }); // --------------------------------------------------------------------------- // buildBinBuffer // --------------------------------------------------------------------------- describe("buildBinBuffer", () => { const parser = new MonoDisplayParser(); test("builds a parseable Uint8Array from Image2DElement", () => { const pixels = new Uint8Array([0, 1, 1, 0]); const el: Image2DElement = { type: ElementType.Image2D, pixels, width: 2, height: 2 }; const buf = buildBinBuffer(el); expect(buf).toBeInstanceOf(Uint8Array); const result = parser.parse(buf); expect(result.sections.length).toBeGreaterThan(0); const section = result.sections[0] as MonoFormatElementsAlways; const parsed = section.elements[0] as MonoFormatImage2D; expect(Array.from(parsed.image.pixels)).toEqual([0, 1, 1, 0]); }); test("pixel data round-trips correctly", () => { const pixels = new Uint8Array([1, 0, 0, 0, 0, 0, 0, 1]); // 8 pixels, corners on const el: Image2DElement = { type: ElementType.Image2D, pixels, width: 8, height: 1 }; const buf = buildBinBuffer(el); const result = parser.parse(buf); const section = result.sections[0] as MonoFormatElementsAlways; const parsed = section.elements[0] as MonoFormatImage2D; expect(parsed.image.width).toBe(8); expect(parsed.image.height).toBe(1); expect(Array.from(parsed.image.pixels)).toEqual([1, 0, 0, 0, 0, 0, 0, 1]); }); });