mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 13:22:14 +00:00
The matrix derives each row's label from the event name, so a new event type gets a row whether or not anybody wrote the strings for it. That is how SECURITY_ALERT has been rendering its own raw key since v1.23: the forward i18n coverage test only sees literal call sites, and this key is assembled from a runtime value. Move the derivation out of the page so a test can compute the keys the page computes, assert both resolve in all six bundles, and name the missing one. Write the sign-in row's strings, and the strings for the delegated-dose row and its push body.
115 lines
3.9 KiB
TypeScript
115 lines
3.9 KiB
TypeScript
/**
|
|
* Every event type the /notifications matrix renders has words to render.
|
|
*
|
|
* The matrix builds its rows from `EVENT_TYPES` and derives each row's label
|
|
* and description key from the event name, so a new event type silently gets a
|
|
* row whether or not anybody wrote the two strings for it. That is not a
|
|
* theory: `SECURITY_ALERT` shipped in v1.23 and rendered the raw key
|
|
* `notifications.eventSecurityAlert` in the matrix until v1.36.x, because
|
|
* nothing anywhere asserted the pairing — the forward i18n coverage test only
|
|
* sees literal `t("…")` call sites, and this key is assembled from a runtime
|
|
* value.
|
|
*
|
|
* So this test walks the event list the page walks, computes the keys the page
|
|
* computes, and asserts both resolve in every locale bundle.
|
|
*
|
|
* Mutation check: remove `notifications.eventSharedRecordIntakeDesc` from
|
|
* `messages/pl.json` and this file reports
|
|
* `pl is missing notifications.eventSharedRecordIntakeDesc`.
|
|
*/
|
|
import { describe, expect, it } from "vitest";
|
|
import { readdirSync, readFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { EVENT_TYPES } from "@/lib/notifications/types";
|
|
import {
|
|
eventDescriptionTranslationKey,
|
|
eventTranslationKey,
|
|
} from "@/lib/notifications/event-labels";
|
|
|
|
const MESSAGES_DIR = join(__dirname, "../../../../messages");
|
|
|
|
function bundle(locale: string): Record<string, unknown> {
|
|
return JSON.parse(
|
|
readFileSync(join(MESSAGES_DIR, `${locale}.json`), "utf8"),
|
|
) as Record<string, unknown>;
|
|
}
|
|
|
|
function resolve(root: Record<string, unknown>, key: string): unknown {
|
|
return key
|
|
.split(".")
|
|
.reduce<unknown>(
|
|
(node, part) =>
|
|
node && typeof node === "object"
|
|
? (node as Record<string, unknown>)[part]
|
|
: undefined,
|
|
root,
|
|
);
|
|
}
|
|
|
|
const LOCALES = readdirSync(MESSAGES_DIR)
|
|
.filter((f) => f.endsWith(".json"))
|
|
.map((f) => f.replace(/\.json$/, ""));
|
|
|
|
describe("notification event labels", () => {
|
|
it("derives the key the matrix renders under", () => {
|
|
expect(eventTranslationKey("MEDICATION_REMINDER")).toBe(
|
|
"notifications.eventMedicationReminder",
|
|
);
|
|
expect(eventDescriptionTranslationKey("SHARED_RECORD_INTAKE")).toBe(
|
|
"notifications.eventSharedRecordIntakeDesc",
|
|
);
|
|
});
|
|
|
|
it("has a label and a description for every event type, in every locale", () => {
|
|
expect(LOCALES.length).toBeGreaterThanOrEqual(6);
|
|
expect(EVENT_TYPES.length).toBeGreaterThan(10);
|
|
|
|
const missing: string[] = [];
|
|
for (const locale of LOCALES) {
|
|
const root = bundle(locale);
|
|
for (const eventType of EVENT_TYPES) {
|
|
for (const key of [
|
|
eventTranslationKey(eventType),
|
|
eventDescriptionTranslationKey(eventType),
|
|
]) {
|
|
const value = resolve(root, key);
|
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
missing.push(`${locale} is missing ${key}`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
expect(missing).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe("the delegated-dose push copy", () => {
|
|
it("interpolates the same names in every locale", () => {
|
|
const keys = [
|
|
"notifications.sharedRecordIntake.title",
|
|
"notifications.sharedRecordIntake.bodyTaken",
|
|
"notifications.sharedRecordIntake.bodySkipped",
|
|
];
|
|
const placeholders: Record<string, string[]> = {
|
|
"notifications.sharedRecordIntake.title": ["{name}"],
|
|
"notifications.sharedRecordIntake.bodyTaken": ["{name}", "{medication}"],
|
|
"notifications.sharedRecordIntake.bodySkipped": [
|
|
"{name}",
|
|
"{medication}",
|
|
],
|
|
};
|
|
|
|
for (const locale of LOCALES) {
|
|
const root = bundle(locale);
|
|
for (const key of keys) {
|
|
const value = resolve(root, key);
|
|
expect(typeof value, `${locale} ${key}`).toBe("string");
|
|
for (const token of placeholders[key]) {
|
|
expect(value as string, `${locale} ${key}`).toContain(token);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|