mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 22:02:16 +00:00
fix: guard managed setting label inventories
- Treat inherited property names as unavailable settings - Preserve legacy tile aliases only for declared entries
This commit is contained in:
1 parent
24c5c49be3
commit
437d916fc6
4 files changed
+61
-7
No files matched your search
@@ -125,4 +125,28 @@ describe("managed record settings forms", () => {
|
||||
expect(insights).not.toContain("settings.sharedRecord");
|
||||
}
|
||||
});
|
||||
|
||||
it.each(["toString", "constructor", "__proto__"])(
|
||||
"renders the unavailable label for prototype property %s",
|
||||
(id) => {
|
||||
const coach = render("coach", {
|
||||
preferences: {
|
||||
excludeMetrics: [id],
|
||||
dataClusters: [id],
|
||||
},
|
||||
});
|
||||
const insights = render("insights", {
|
||||
layout: {
|
||||
version: 2,
|
||||
sections: [{ id, visible: true, order: 0 }],
|
||||
tiles: [{ id, visible: true, order: 0 }],
|
||||
},
|
||||
});
|
||||
|
||||
expect(coach).not.toContain(`>${id}<`);
|
||||
expect(insights).toContain("Unavailable insight");
|
||||
expect(insights).not.toContain(`>${id}<`);
|
||||
expect(insights).not.toContain("settings.sharedRecord");
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -171,18 +171,30 @@ function formNumber(form: FormData, name: string, fallback: number): number {
|
||||
return Number.isFinite(value) ? value : fallback;
|
||||
}
|
||||
|
||||
function ownStringValue(inventory: object, id: string): string | undefined {
|
||||
if (!Object.hasOwn(inventory, id)) return undefined;
|
||||
const value = Reflect.get(inventory, id);
|
||||
return typeof value === "string" ? value : undefined;
|
||||
}
|
||||
|
||||
function coachMetricLabel(
|
||||
t: ReturnType<typeof useTranslations>["t"],
|
||||
metric: (typeof COACH_EXCLUDE_METRICS)[number],
|
||||
): string {
|
||||
return t(COACH_EXCLUDE_METRIC_LABEL_KEYS[metric]);
|
||||
const labelKey = ownStringValue(COACH_EXCLUDE_METRIC_LABEL_KEYS, metric);
|
||||
return labelKey
|
||||
? t(labelKey)
|
||||
: t("settings.sharedRecord.managedSettings.insights.unknownItem");
|
||||
}
|
||||
|
||||
function coachClusterLabel(
|
||||
t: ReturnType<typeof useTranslations>["t"],
|
||||
cluster: (typeof COACH_DATA_CLUSTERS)[number],
|
||||
): string {
|
||||
return t(COACH_DATA_CLUSTER_LABEL_KEYS[cluster]);
|
||||
const labelKey = ownStringValue(COACH_DATA_CLUSTER_LABEL_KEYS, cluster);
|
||||
return labelKey
|
||||
? t(labelKey)
|
||||
: t("settings.sharedRecord.managedSettings.insights.unknownItem");
|
||||
}
|
||||
|
||||
function insightItemLabel(
|
||||
@@ -191,7 +203,7 @@ function insightItemLabel(
|
||||
id: string,
|
||||
): string {
|
||||
if (kind === "section") {
|
||||
const labelKey = INSIGHTS_SECTION_LABEL_KEYS[id as InsightsSectionId];
|
||||
const labelKey = ownStringValue(INSIGHTS_SECTION_LABEL_KEYS, id);
|
||||
return labelKey
|
||||
? t(labelKey)
|
||||
: t("settings.sharedRecord.managedSettings.insights.unknownItem");
|
||||
@@ -199,9 +211,14 @@ function insightItemLabel(
|
||||
|
||||
const normalizedId = normalizeInsightsTileId(id);
|
||||
if (normalizedId === "overview") return t("insights.navOverview");
|
||||
const tab = SUB_PAGE_TABS[normalizedId as keyof typeof SUB_PAGE_TABS];
|
||||
return tab
|
||||
? t(tab.labelKey)
|
||||
if (!Object.hasOwn(SUB_PAGE_TABS, normalizedId)) {
|
||||
return t("settings.sharedRecord.managedSettings.insights.unknownItem");
|
||||
}
|
||||
|
||||
const labelKey =
|
||||
SUB_PAGE_TABS[normalizedId as keyof typeof SUB_PAGE_TABS]?.labelKey;
|
||||
return typeof labelKey === "string"
|
||||
? t(labelKey)
|
||||
: t("settings.sharedRecord.managedSettings.insights.unknownItem");
|
||||
}
|
||||
|
||||
|
||||
@@ -16,9 +16,20 @@ import {
|
||||
resolveInsightsLayout,
|
||||
serializeInsightsLayout,
|
||||
orderedVisibleSectionIds,
|
||||
normalizeInsightsTileId,
|
||||
resolveTileLayout,
|
||||
} from "@/lib/insights-layout";
|
||||
|
||||
describe("normalizeInsightsTileId", () => {
|
||||
it("only resolves own legacy aliases", () => {
|
||||
expect(normalizeInsightsTileId("blutdruck")).toBe("blood-pressure");
|
||||
|
||||
for (const id of ["toString", "constructor", "__proto__"]) {
|
||||
expect(normalizeInsightsTileId(id)).toBe(id);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("insights-layout v2 — section id universe", () => {
|
||||
it("carries exactly the default sections in render order", () => {
|
||||
expect([...INSIGHTS_SECTION_IDS]).toEqual([
|
||||
|
||||
@@ -87,7 +87,9 @@ export const ACCEPTED_INSIGHTS_TILE_IDS = [
|
||||
* resolver's known-id filter drops anything truly unrecognised.
|
||||
*/
|
||||
export function normalizeInsightsTileId(id: string): string {
|
||||
return LEGACY_INSIGHTS_TILE_ID_ALIASES[id] ?? id;
|
||||
return Object.hasOwn(LEGACY_INSIGHTS_TILE_ID_ALIASES, id)
|
||||
? LEGACY_INSIGHTS_TILE_ID_ALIASES[id]
|
||||
: id;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user