import { PlateNormalizerService, normalizePlateText } from "./plate-normalizer.service"; describe("normalizePlateText (pure function)", () => { it("returns unchanged text for a clean plate letter", () => { expect(normalizePlateText("ه")).toBe("ه"); expect(normalizePlateText("الف")).toBe("الف"); }); it("strips Zero-Width Joiner (U+200D)", () => { // ه + ZWJ — the exact scenario from the issue const input = "ه\u200D"; expect(normalizePlateText(input)).toBe("ه"); }); it("strips Zero-Width Non-Joiner (U+200C)", () => { expect(normalizePlateText("ه\u200C")).toBe("ه"); }); it("strips Left-to-Right Mark (U+200E)", () => { expect(normalizePlateText("ه\u200E")).toBe("ه"); }); it("strips Right-to-Left Mark (U+200F)", () => { expect(normalizePlateText("ه\u200F")).toBe("ه"); }); it("strips BOM (U+FEFF)", () => { expect(normalizePlateText("\uFEFFه")).toBe("ه"); }); it("converts Arabic ي (U+064A) to Persian ی (U+06CC)", () => { expect(normalizePlateText("\u064A")).toBe("\u06CC"); }); it("does not alter already-Persian ی (U+06CC)", () => { expect(normalizePlateText("\u06CC")).toBe("\u06CC"); }); it("handles a full plate string: 339ه\u200D77 → 339ه77", () => { expect(normalizePlateText("339ه\u200D77")).toBe("339ه77"); }); it("handles multiple invisible chars interleaved", () => { const input = "\u200F339\u200Cه\u200D\uFEFF77\u200E"; expect(normalizePlateText(input)).toBe("339ه77"); }); it("trims whitespace", () => { expect(normalizePlateText(" ه ")).toBe("ه"); }); it("applies NFKC normalization", () => { // Full-width digit 3 (U+FF13) → ASCII 3 expect(normalizePlateText("3")).toBe("3"); }); it("returns empty string for empty input", () => { expect(normalizePlateText("")).toBe(""); }); }); describe("PlateNormalizerService", () => { let service: PlateNormalizerService; beforeEach(() => { service = new PlateNormalizerService(); }); describe("normalizePlateText", () => { it("delegates to the pure function", () => { expect(service.normalizePlateText("ه\u200D")).toBe("ه"); }); }); describe("resolvePlateLetterCode", () => { const PLATE_LETTER_MAP: Record = { "\u0627\u0644\u0641": 1, // الف "\u0628": 2, "\u067E": 3, "\u062C": 4, "\u062F": 5, "\u0633": 6, "\u0635": 7, "\u0637": 8, "\u0639": 9, "\u0642": 10, "\u0644": 11, "\u0645": 12, "\u0646": 13, "\u0648": 14, "\u0647": 15, // ه "\u06CC": 16, // ی "\u06A9": 17, "\u0698": 18, "\u062A": 19, "\u062B": 20, "\u0632": 21, "\u0634": 22, "\u0641": 23, "\u06AF": 24, }; it("returns 15 for clean ه", () => { expect(service.resolvePlateLetterCode("ه", PLATE_LETTER_MAP)).toBe(15); }); it("returns 15 for ه + ZWJ (the bug scenario)", () => { expect(service.resolvePlateLetterCode("ه\u200D", PLATE_LETTER_MAP)).toBe( 15, ); }); it("returns 16 for Arabic ي (mapped to Persian ی)", () => { expect( service.resolvePlateLetterCode("\u064A", PLATE_LETTER_MAP), ).toBe(16); }); it("returns null for an unknown letter and logs it", () => { const spy = jest.spyOn( (service as any).logger, "debug", ); expect(service.resolvePlateLetterCode("؟", PLATE_LETTER_MAP)).toBeNull(); expect(spy).toHaveBeenCalledWith( expect.stringContaining("Unknown plate letter"), ); spy.mockRestore(); }); }); });