mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 13:22:14 +00:00
Environment, external assistant access, Documents, mental wellbeing and nutrient intake ran 120-165 characters in German where their thirteen neighbours sat at 30-75. Every one of the five carried the same second sentence, "off by default", which restates what the switch beside the row already shows. The mental-wellbeing row also named two screeners where the module ships four, and its second sentence held the one statement in the app about what happens to the answers. That statement moves to the mental-wellbeing page itself, where somebody is about to answer them. A guard holds every module row to one sentence in all six locales, and under the 120-character guideline in English and German.
78 lines
2.9 KiB
TypeScript
78 lines
2.9 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { readFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
|
|
import { MODULE_KEYS } from "@/lib/modules/registry";
|
|
import { locales } from "@/lib/i18n/config";
|
|
|
|
/**
|
|
* A module row in the "What you track" hub gets ONE sentence, same as a card
|
|
* header's description slot (design standards §3).
|
|
*
|
|
* Five of the eighteen rows had grown a second sentence — always the same
|
|
* one, "Off by default", and in one case a third clause after it. The
|
|
* neighbours sat at 30-75 characters and those five at 120-165, so the hub
|
|
* read as two different lists stacked on each other. The switch beside the
|
|
* row already says whether the module is on, so the sentence was restating
|
|
* what the control shows.
|
|
*
|
|
* The single-sentence half of the rule is checked in every locale: it is a
|
|
* structural claim, not a length one, and a translation that re-adds the
|
|
* clause is the same defect. The character cap is checked in EN and DE, the
|
|
* two the maintainer reads, matching `card-description-length.test.ts`.
|
|
*/
|
|
|
|
const ROOT = process.cwd();
|
|
const MAX_CHARS = 120;
|
|
|
|
function bundle(locale: string): Record<string, { description?: string }> {
|
|
const raw = readFileSync(join(ROOT, "messages", `${locale}.json`), "utf8");
|
|
return JSON.parse(raw).modules;
|
|
}
|
|
|
|
describe("module hub descriptions", () => {
|
|
it("names a description for every module in every locale", () => {
|
|
// Positive control: without it, a renamed `modules` namespace would make
|
|
// the two checks below iterate an empty set and report green.
|
|
for (const locale of locales) {
|
|
const modules = bundle(locale);
|
|
const missing = MODULE_KEYS.filter(
|
|
(key) => !modules[key]?.description?.trim(),
|
|
);
|
|
expect(missing, `${locale} is missing descriptions`).toEqual([]);
|
|
}
|
|
});
|
|
|
|
it("holds each one to a single sentence, in every locale", () => {
|
|
const offenders: string[] = [];
|
|
for (const locale of locales) {
|
|
const modules = bundle(locale);
|
|
for (const key of MODULE_KEYS) {
|
|
const text = modules[key].description!;
|
|
// Drop the closing terminator, then any sentence break left inside
|
|
// is a second sentence. A semicolon splice counts — it is how the
|
|
// longest of the five carried its third clause.
|
|
const body = text.replace(/[.!?…]\s*$/, "");
|
|
if (/[.!?;]\s/.test(body)) {
|
|
offenders.push(`${locale}/${key}: ${text}`);
|
|
}
|
|
}
|
|
}
|
|
expect(offenders).toEqual([]);
|
|
});
|
|
|
|
it("holds each one under the character guideline in EN and DE", () => {
|
|
const offenders: string[] = [];
|
|
for (const locale of ["en", "de"] as const) {
|
|
const modules = bundle(locale);
|
|
for (const key of MODULE_KEYS) {
|
|
const text = modules[key].description!;
|
|
if (text.length > MAX_CHARS) {
|
|
offenders.push(`${locale}/${key}: ${text.length} chars`);
|
|
}
|
|
}
|
|
}
|
|
expect(offenders).toEqual([]);
|
|
});
|
|
});
|