the serial letter normalization added to the code to normalize the Heh , Ya , Kaf and etc to the correct character

This commit is contained in:
2026-07-27 10:38:35 +03:30
parent 778544c321
commit 182db56e15
13 changed files with 337 additions and 11 deletions

View File

@@ -0,0 +1,131 @@
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 (U+FF13) → ASCII 3
expect(normalizePlateText("")).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<string, number> = {
"\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();
});
});
});