cjk.test.ts

3.5 KB · raw

1/**2 * CJK encode/decode vectors (spec §10.1).3 */45import { describe, expect, it } from "vitest";67import {8  ALPHABET_END,9  ALPHABET_SIZE,10  ALPHABET_START,11  cjkDecode,12  cjkEncode,13  isValidPhraseCharset,14  remainingBits,15} from "./cjk";1617describe("cjkEncode", () => {18  it("encodes 0x00 to U+4E00 (first character of the alphabet)", () => {19    // 0x00 = 8 bits, padded left into a 14-bit slot → index 020    expect(cjkEncode(new Uint8Array([0x00]))).toBe("\u4E00");21  });2223  it("encodes 0xFF to U+8DC0 (verified programmatically)", () => {24    // 0xFF = 8 bits = 11111111; left-padded to 14 = 11111111_000000 = 0x3FC025    // 0x4E00 + 0x3FC0 = 0x8DC0. Spec's Vector 1 second line cites U+6A3F,26    // which is incorrect — this test asserts the real value.27    expect(cjkEncode(new Uint8Array([0xFF]))).toBe("\u8DC0");28  });2930  it("encodes 0xAB 0xCD to [U+78F3, U+5E00]", () => {31    // 0xAB=10101011, 0xCD=11001101 → 1010101111001101 (16 bits)32    // First 14 bits → 0x2AF3; first char = 0x4E00 + 0x2AF3 = 0x78F333    // Remaining 2 bits = 01, left-padded → 0x1000; second = 0x4E00 + 0x1000 = 0x5E0034    expect(cjkEncode(new Uint8Array([0xAB, 0xCD]))).toBe("\u78F3\u5E00");35  });3637  it("produces only characters within the 16384-entry alphabet", () => {38    const bytes = new Uint8Array(256);39    for (let i = 0; i < 256; i++) bytes[i] = i;40    const encoded = cjkEncode(bytes);41    for (const ch of encoded) {42      const cp = ch.codePointAt(0)!;43      expect(cp).toBeGreaterThanOrEqual(ALPHABET_START);44      expect(cp).toBeLessThanOrEqual(ALPHABET_END);45    }46  });47});4849describe("cjkEncode / cjkDecode round-trip", () => {50  it("round-trips every single-byte value", () => {51    for (let b = 0; b <= 255; b++) {52      const original = new Uint8Array([b]);53      const encoded = cjkEncode(original);54      const decoded = cjkDecode(encoded, remainingBits(1));55      expect(decoded).toEqual(original);56    }57  });5859  it("round-trips random buffers of length 1..200", () => {60    for (let len = 1; len <= 200; len++) {61      const original = new Uint8Array(len);62      crypto.getRandomValues(original);63      const encoded = cjkEncode(original);64      const rb = remainingBits(len);65      const decoded = cjkDecode(encoded, rb);66      expect(decoded).toEqual(original);67    }68  });69});7071describe("remainingBits", () => {72  it("returns zero when bytes * 8 is a multiple of 14", () => {73    // 7 bytes = 56 bits = 4 chars exactly74    expect(remainingBits(7)).toBe(0);75    // 14 bytes = 112 bits = 8 chars exactly76    expect(remainingBits(14)).toBe(0);77  });7879  it("returns the non-zero remainder otherwise", () => {80    expect(remainingBits(1)).toBe(8);81    expect(remainingBits(2)).toBe(2);82    expect(remainingBits(3)).toBe(10);83  });84});8586describe("isValidPhraseCharset", () => {87  it("accepts the alphabet bounds", () => {88    expect(isValidPhraseCharset("\u4E00")).toBe(true);89    expect(isValidPhraseCharset("\u8DFF")).toBe(true);90  });9192  it("rejects characters just outside the alphabet", () => {93    expect(isValidPhraseCharset("\u4DFF")).toBe(false); // one below start94    expect(isValidPhraseCharset("\u8E00")).toBe(false); // one above end95  });9697  it("rejects non-CJK characters", () => {98    expect(isValidPhraseCharset("a")).toBe(false);99    expect(isValidPhraseCharset("一二 三")).toBe(false); // contains space100  });101});102103describe("alphabet constants", () => {104  it("has 16,384 entries so each character packs exactly 14 bits", () => {105    expect(ALPHABET_SIZE).toBe(2 ** 14);106    expect(ALPHABET_END - ALPHABET_START + 1).toBe(ALPHABET_SIZE);107  });108});