mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 06:40:50 +00:00
Add pt-BR to the locale list and everything that hangs off it: the client loader, Intl and date-fns mapping, the reply-language footers of the Coach and insights prompts (English body, pt-BR footer, the same path Korean and Dutch take), the citation-coverage normative words, the no-key fallback copy, the AUDIT-C standard-drink line, country labels, period search, CSV yes/no cells and the Telegram bare-word aliases. The locale enums in the extract, managed-profile and record-settings schemas now derive from the locale list instead of repeating it, and the five notification and login-alert routes share one isLocale guard instead of hand-rolled chains, so a later locale is one edit there. A language tag is matched exactly and case-insensitively before its primary subtag, so pt-BR and pt-PT browsers land on the Portuguese bundle rather than English, and AUDIT-C no longer cuts a region tag to two letters. PHQ-9, GAD-7 and WHO-5 are not marked validated for pt-BR: the wording has not been read against the published Brazilian forms, so readers get the English items plus the existing note.
115 lines
4.3 KiB
TypeScript
115 lines
4.3 KiB
TypeScript
import { readFile } from "node:fs/promises";
|
|
import { resolve } from "node:path";
|
|
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
const fromRoot = (...segments: string[]) => resolve(process.cwd(), ...segments);
|
|
|
|
function expectMatchingMessageKeys(expected: unknown, actual: unknown): void {
|
|
if (
|
|
expected === null ||
|
|
typeof expected !== "object" ||
|
|
actual === null ||
|
|
typeof actual !== "object"
|
|
) {
|
|
expect(typeof actual).toBe(typeof expected);
|
|
return;
|
|
}
|
|
|
|
const expectedRecord = expected as Record<string, unknown>;
|
|
const actualRecord = actual as Record<string, unknown>;
|
|
expect(Object.keys(actualRecord).sort()).toEqual(
|
|
Object.keys(expectedRecord).sort(),
|
|
);
|
|
for (const key of Object.keys(expectedRecord)) {
|
|
expectMatchingMessageKeys(expectedRecord[key], actualRecord[key]);
|
|
}
|
|
}
|
|
|
|
describe("managed record settings UI", () => {
|
|
it("gates shared deep links and controls integrations only through the managed-record routes", async () => {
|
|
const [
|
|
statusView,
|
|
managedSettingsView,
|
|
settingsPage,
|
|
layoutModulePage,
|
|
sectionGate,
|
|
...localeBundles
|
|
] = await Promise.all([
|
|
readFile(
|
|
fromRoot("src/components/settings/managed-integration-status.tsx"),
|
|
"utf8",
|
|
),
|
|
readFile(
|
|
fromRoot("src/components/settings/managed-record-settings-section.tsx"),
|
|
"utf8",
|
|
),
|
|
readFile(fromRoot("src/app/settings/[section]/page.tsx"), "utf8"),
|
|
readFile(fromRoot("src/app/settings/layout/[module]/page.tsx"), "utf8"),
|
|
readFile(
|
|
fromRoot("src/components/settings/record-settings-section-gate.tsx"),
|
|
"utf8",
|
|
),
|
|
...["de", "en", "es", "fr", "it", "pl", "ko", "nl", "pt-BR"].map(
|
|
(locale) => readFile(fromRoot(`messages/${locale}.json`), "utf8"),
|
|
),
|
|
]);
|
|
|
|
expect(statusView).toContain("recordSettingsIntegrations");
|
|
expect(statusView).toContain("/api/record-settings/integrations");
|
|
expect(statusView).toContain("assertRecordSettingsResponseForRecord");
|
|
// A guardian's controls go through the fenced managed-record routes, never
|
|
// the owner's provider routes and never a navigation to the API.
|
|
expect(statusView).toContain(
|
|
"/api/record-settings/integrations/${provider}/connect",
|
|
);
|
|
expect(statusView).toContain(
|
|
"/api/record-settings/integrations/${provider}/disconnect",
|
|
);
|
|
expect(statusView).not.toMatch(/["`]\/api\/\$\{provider\}\//);
|
|
expect(statusView).not.toMatch(/loadDocument\(\s*["`]\/api/);
|
|
expect(statusView).not.toMatch(/<Link[\s>]/);
|
|
expect(settingsPage).toContain("RecordSettingsSectionGate");
|
|
expect(sectionGate).toContain("if (!inSharedRecord) return children");
|
|
expect(sectionGate).toContain("ManagedIntegrationStatus");
|
|
expect(sectionGate).toContain("ManagedRecordSettingsSection");
|
|
expect(sectionGate).toContain("if (isLoading)");
|
|
expect(layoutModulePage).toContain(
|
|
'RecordSettingsSectionGate section="layout"',
|
|
);
|
|
expect(managedSettingsView).toContain(
|
|
"safeParseManagedRecordSettingsPatch",
|
|
);
|
|
expect(managedSettingsView).toContain("/api/record-settings/${family}");
|
|
expect(managedSettingsView).not.toContain("/api/auth/");
|
|
expect(managedSettingsView).not.toContain("/api/user/");
|
|
expect(managedSettingsView).not.toContain("<textarea");
|
|
expect(managedSettingsView).not.toContain("Display name");
|
|
expect(managedSettingsView).not.toContain("Enable mood reminder");
|
|
const referenceMessages = JSON.parse(localeBundles[0]).settings.sharedRecord
|
|
.managedSettings;
|
|
for (const bundle of localeBundles) {
|
|
const sharedRecord = JSON.parse(bundle).settings.sharedRecord;
|
|
expect(sharedRecord).toMatchObject({
|
|
unavailableTitle: expect.any(String),
|
|
integrationStatusDescription: expect.any(String),
|
|
managedSettings: {
|
|
profile: {
|
|
title: expect.any(String),
|
|
displayName: expect.any(String),
|
|
},
|
|
modules: { title: expect.any(String) },
|
|
notifications: { title: expect.any(String) },
|
|
thresholds: { title: expect.any(String) },
|
|
coach: { title: expect.any(String) },
|
|
insights: { title: expect.any(String) },
|
|
},
|
|
});
|
|
expectMatchingMessageKeys(
|
|
referenceMessages,
|
|
sharedRecord.managedSettings,
|
|
);
|
|
}
|
|
});
|
|
});
|