// Every string a page of the module shows is a message in English and in Lithuanian (spec §10.4), the // Lithuanian plural forms are real ones, and no component of the module carries a string of its own. import { readdirSync, readFileSync } from "node:fs" import { join } from "node:path" import { buildRegistry } from "@conusai-kit/core" import { createI18n, lintUiStrings } from "@conusai-kit/i18n" import { describe, expect, it } from "vitest" import { appModules } from "../../index.js" import { feedbackLabels } from "../guides/feedback-labels.js" // kit:guides import { attachmentLabels, createFormLabels, deleteLabels, editFormLabels, listLabels, sizeLabel, } from "../ui/labels.js" // The messages as the app has them: from the manifest, flattened and merged by the kit. const catalogues = buildRegistry([...appModules]).catalogues const en = catalogues.en?.notes ?? {} const lt = catalogues.lt?.notes ?? {} /** The keys a builder asks for, recorded by running it with a lookup that notes each one. */ function keysUsed(): string[] { const asked: string[] = [] const t = (key: string) => { asked.push(key) return key } createFormLabels(t, "failed") editFormLabels(t, "failed") listLabels(t) attachmentLabels(t, "failed", 2) deleteLabels(t, "failed") sizeLabel(t, 2048) feedbackLabels(t) // kit:guides return asked } describe("the notes catalogues", () => { it("hold the same keys in English and in Lithuanian", () => { expect(Object.keys(lt).sort()).toEqual(Object.keys(en).sort()) }) it("hold no empty message", () => { for (const [key, text] of [...Object.entries(en), ...Object.entries(lt)]) expect(text.trim(), key).not.toBe("") }) it("hold every message the components' labels ask for, in both languages", () => { const asked = keysUsed() expect(asked.length).toBeGreaterThan(30) for (const key of asked) { expect(en[key as keyof typeof en], `en is missing notes.${key}`).toBeTruthy() expect(lt[key as keyof typeof lt], `lt is missing notes.${key}`).toBeTruthy() } }) it("hold no message nothing asks for, so a renamed label does not leave a dead string behind", () => { // The pages and the navigation ask for these directly, not through a label builder. const direct = new Set(["title", "intro", "detail.back", "detail.changed"]) const asked = new Set([...keysUsed(), ...direct]) expect(Object.keys(en).filter((key) => !asked.has(key))).toEqual([]) }) it("answer the navigation label the manifest names", () => { const nav = appModules.find((module) => module.id === "notes")?.ui?.nav ?? [] expect(nav.map((item) => item.label)).toEqual(["notes.title"]) expect(en.title).toBeTruthy() expect(lt.title).toBeTruthy() }) }) describe("the Lithuanian plural forms", () => { const count = (locale: "en" | "lt", n: number) => createI18n(locale, { en: { notes: en }, lt: { notes: lt } }).message("notes.attachments.count", { count: n }) it("select one, few and other the way Lithuanian counts", () => { expect(count("lt", 1)).toBe("1 priedas") expect(count("lt", 2)).toBe("2 priedai") expect(count("lt", 9)).toBe("9 priedai") expect(count("lt", 10)).toBe("10 priedų") expect(count("lt", 21)).toBe("21 priedas") }) it("keep English to its two forms", () => { expect(count("en", 1)).toBe("1 attachment") expect(count("en", 2)).toBe("2 attachments") expect(lt["attachments.count"]).toContain("few {") expect(en["attachments.count"]).not.toContain("few {") }) }) describe("the components", () => { const dir = new URL("../ui/", import.meta.url).pathname const sources = readdirSync(dir) .filter((name) => name.endsWith(".tsx")) .map((name) => ({ name, text: readFileSync(join(dir, name), "utf8") })) it("carry no hard-coded user-facing string", () => { expect(sources.length).toBeGreaterThanOrEqual(4) for (const { name, text } of sources) expect(lintUiStrings(text, name), name).toEqual([]) }) it("carry no colour literal: the brand tokens are the only source of colour", () => { for (const { name, text } of sources) expect(text, name).not.toMatch(/#[0-9a-fA-F]{3,8}\b|\brgb\(|\bhsl\(|\boklch\(/) }) })