/* A lemma's id is a hash of the word, not its position in the build. Cards point at lemmas by id. When ids were positions, adding one entry near the top of the dictionary moved every card below it onto a different word, and a custom word's id meant different words on different devices. */ import { describe, it, expect } from "vitest"; import { readFileSync } from "node:fs"; import { gunzipSync } from "node:zlib"; import { lemmaId } from "@shared/lemma-id.mjs"; describe("lemmaId", () => { /* PINNED. Changing the hash re-keys every card on every device; if this test has to change, a migration has to move the cards with it. */ it("is exactly this function", () => { expect(lemmaId("밥", "noun")).toBe(55855054575946); expect(lemmaId("가다", "verb")).toBe(1434356355562999); }); it("depends on the part of speech as well as the headword", () => { expect(lemmaId("밥", "noun")).not.toBe(lemmaId("밥", "verb")); }); it("is a positive safe integer, which SQLite stores as INTEGER PRIMARY KEY", () => { for (const [h, p] of [["밥", "noun"], ["", ""], ["신경 쓰다", "verb"], ["x".repeat(500), "noun"]]) { const id = lemmaId(h!, p!); expect(Number.isSafeInteger(id)).toBe(true); expect(id).toBeGreaterThan(0); } }); }); describe("the shipped dictionary", () => { const manifest = JSON.parse( readFileSync(new URL("../../app/public/dict/manifest.json", import.meta.url), "utf8"), ) as { format: number; totals: { lemmas: number }; bands: { file: string }[] }; it("is format 2 — no ids in the files, derived as they load", () => { expect(manifest.format).toBe(2); }); it("gives every (headword, pos) its own id", () => { const owner = new Map(); let n = 0; for (const band of manifest.bands) { const data = JSON.parse( gunzipSync(readFileSync(new URL(`../../app/public/dict/${band.file}`, import.meta.url))).toString("utf8"), ) as { format: number; columns: { lemma: string[] }; lemmas: unknown[][] }; expect(data.format).toBe(2); const hw = data.columns.lemma.indexOf("headword"); const pos = data.columns.lemma.indexOf("pos"); for (const row of data.lemmas) { const key = `${row[hw]}/${row[pos]}`; const id = lemmaId(row[hw] as string, row[pos] as string); expect(owner.get(id) ?? key, `${key} collides`).toBe(key); owner.set(id, key); n++; } } expect(n).toBe(manifest.totals.lemmas); }); });