/** * CJK encode/decode vectors (spec §10.1). */ import { describe, expect, it } from "vitest"; import { ALPHABET_END, ALPHABET_SIZE, ALPHABET_START, cjkDecode, cjkEncode, isValidPhraseCharset, remainingBits, } from "./cjk"; describe("cjkEncode", () => { it("encodes 0x00 to U+4E00 (first character of the alphabet)", () => { // 0x00 = 8 bits, padded left into a 14-bit slot → index 0 expect(cjkEncode(new Uint8Array([0x00]))).toBe("\u4E00"); }); it("encodes 0xFF to U+8DC0 (verified programmatically)", () => { // 0xFF = 8 bits = 11111111; left-padded to 14 = 11111111_000000 = 0x3FC0 // 0x4E00 + 0x3FC0 = 0x8DC0. Spec's Vector 1 second line cites U+6A3F, // which is incorrect — this test asserts the real value. expect(cjkEncode(new Uint8Array([0xFF]))).toBe("\u8DC0"); }); it("encodes 0xAB 0xCD to [U+78F3, U+5E00]", () => { // 0xAB=10101011, 0xCD=11001101 → 1010101111001101 (16 bits) // First 14 bits → 0x2AF3; first char = 0x4E00 + 0x2AF3 = 0x78F3 // Remaining 2 bits = 01, left-padded → 0x1000; second = 0x4E00 + 0x1000 = 0x5E00 expect(cjkEncode(new Uint8Array([0xAB, 0xCD]))).toBe("\u78F3\u5E00"); }); it("produces only characters within the 16384-entry alphabet", () => { const bytes = new Uint8Array(256); for (let i = 0; i < 256; i++) bytes[i] = i; const encoded = cjkEncode(bytes); for (const ch of encoded) { const cp = ch.codePointAt(0)!; expect(cp).toBeGreaterThanOrEqual(ALPHABET_START); expect(cp).toBeLessThanOrEqual(ALPHABET_END); } }); }); describe("cjkEncode / cjkDecode round-trip", () => { it("round-trips every single-byte value", () => { for (let b = 0; b <= 255; b++) { const original = new Uint8Array([b]); const encoded = cjkEncode(original); const decoded = cjkDecode(encoded, remainingBits(1)); expect(decoded).toEqual(original); } }); it("round-trips random buffers of length 1..200", () => { for (let len = 1; len <= 200; len++) { const original = new Uint8Array(len); crypto.getRandomValues(original); const encoded = cjkEncode(original); const rb = remainingBits(len); const decoded = cjkDecode(encoded, rb); expect(decoded).toEqual(original); } }); }); describe("remainingBits", () => { it("returns zero when bytes * 8 is a multiple of 14", () => { // 7 bytes = 56 bits = 4 chars exactly expect(remainingBits(7)).toBe(0); // 14 bytes = 112 bits = 8 chars exactly expect(remainingBits(14)).toBe(0); }); it("returns the non-zero remainder otherwise", () => { expect(remainingBits(1)).toBe(8); expect(remainingBits(2)).toBe(2); expect(remainingBits(3)).toBe(10); }); }); describe("isValidPhraseCharset", () => { it("accepts the alphabet bounds", () => { expect(isValidPhraseCharset("\u4E00")).toBe(true); expect(isValidPhraseCharset("\u8DFF")).toBe(true); }); it("rejects characters just outside the alphabet", () => { expect(isValidPhraseCharset("\u4DFF")).toBe(false); // one below start expect(isValidPhraseCharset("\u8E00")).toBe(false); // one above end }); it("rejects non-CJK characters", () => { expect(isValidPhraseCharset("a")).toBe(false); expect(isValidPhraseCharset("一二 三")).toBe(false); // contains space }); }); describe("alphabet constants", () => { it("has 16,384 entries so each character packs exactly 14 bits", () => { expect(ALPHABET_SIZE).toBe(2 ** 14); expect(ALPHABET_END - ALPHABET_START + 1).toBe(ALPHABET_SIZE); }); });