// The Import step card's sentences, as the locale files write them (#422). // // A sentence names a control through an interpolation (`StepCardText.labels`, // translated by `StepCard`); the label then reads between quotation marks, // « » in French and “ ” in English (D11), so the reader tells the button's // name from the sentence around it. The folder name of `noSources` is quoted // the same way. These tests hold the real files to that rule. import { describe, it, expect } from "vitest"; import fr from "../i18n/locales/fr.json"; import en from "../i18n/locales/en.json"; import { budgetCardModel, transactionsCardModel } from "./onboardingPath"; type Cards = Record; const IMPORT_CARDS: Record<"fr" | "en", Cards> = { fr: (fr as { onboarding: { card: { import: Cards } } }).onboarding.card.import, en: (en as { onboarding: { card: { import: Cards } } }).onboarding.card.import, }; const QUOTED: Record<"fr" | "en", (name: string) => RegExp> = { fr: (name) => new RegExp(`« \\{\\{${name}\\}\\} »`), en: (name) => new RegExp(`“\\{\\{${name}\\}\\}”`), }; /** The names of the plain interpolations of a sentence (`{{name}}`). */ function interpolations(sentence: string): string[] { return [...sentence.matchAll(/\{\{(\w+)\}\}/g)].map((match) => match[1]); } describe("Import step card sentences quote every control they name (#422)", () => { it.each(["fr", "en"] as const)("%s: each interpolation sits between quotation marks", (lang) => { const cards = IMPORT_CARDS[lang]; let named = 0; for (const [key, sentence] of Object.entries(cards)) { for (const name of interpolations(sentence)) { named += 1; expect(sentence, `onboarding.card.import.${key}: ${name}`).toMatch(QUOTED[lang](name)); } } // noFolder 1, noSources 3, sourceConfig 1, filePreview 2, duplicateCheck 1, confirm 1. expect(named).toBe(9); }); it.each(["fr", "en"] as const)("%s: the preview names the flip and the next button", (lang) => { expect(interpolations(IMPORT_CARDS[lang].filePreview).sort()).toEqual(["button", "flip"]); }); }); // The Transactions and Budget cards name their controls the same way (#430): // "Add as keyword" on both Transactions sentences, "Expand all" and the // "Annual" column on the Budget ones. type PageCards = { transactions: Cards; budget: Cards }; const PAGE_CARDS: Record<"fr" | "en", PageCards> = { fr: (fr as { onboarding: { card: PageCards } }).onboarding.card, en: (en as { onboarding: { card: PageCards } }).onboarding.card, }; /** Every sentence of the two cards, with the model that names its controls. */ const PAGE_SENTENCES = [ { page: "transactions", key: "uncategorized", text: transactionsCardModel({ uncategorizedOnly: true }, null).text }, { page: "transactions", key: "all", text: transactionsCardModel({ uncategorizedOnly: false }, null).text }, { page: "budget", key: "collapsed", text: budgetCardModel({ target: "expandAll" }, null).text }, { page: "budget", key: "flat", text: budgetCardModel({ target: "cell" }, null).text }, ] as const; /** The names of every interpolation of a sentence, formatted ones included (`{{filter, lowercase}}`). */ function allInterpolations(sentence: string): string[] { return [...sentence.matchAll(/\{\{(\w+)(?:,[^}]*)?\}\}/g)].map((match) => match[1]); } describe("Transactions and Budget step card sentences quote every control they name (#430)", () => { it.each(["fr", "en"] as const)("%s: each control interpolation sits between quotation marks", (lang) => { let named = 0; for (const page of ["transactions", "budget"] as const) { for (const [key, sentence] of Object.entries(PAGE_CARDS[lang][page])) { for (const name of interpolations(sentence)) { named += 1; expect(sentence, `onboarding.card.${page}.${key}: ${name}`).toMatch(QUOTED[lang](name)); } } } // transactions: uncategorized 1, all 1; budget: collapsed 2, flat 1. expect(named).toBe(5); }); it.each(["fr", "en"] as const)("%s: the model labels exactly the interpolations of its sentence", (lang) => { for (const { page, key, text } of PAGE_SENTENCES) { expect(text?.key).toBe(`onboarding.card.${page}.${key}`); const sentence = PAGE_CARDS[lang][page][key]; expect(Object.keys(text?.labels ?? {}).sort(), `${page}.${key}`).toEqual( allInterpolations(sentence).sort(), ); } }); it.each(["fr", "en"] as const)("%s: both Transactions sentences name 'Add as keyword', both Budget ones the 'Annual' column", (lang) => { for (const { page, key, text } of PAGE_SENTENCES) { const expected = page === "transactions" ? "addKeyword" : "annual"; expect(interpolations(PAGE_CARDS[lang][page][key]), `${page}.${key}`).toContain(expected); expect(text?.labels?.[expected], `${page}.${key}`).toBe( page === "transactions" ? "reports.keyword.addFromTransaction" : "budget.annual", ); } }); });