mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 13:22:14 +00:00
684 lines
20 KiB
TypeScript
684 lines
20 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
DEFAULT_LOW_STOCK_RUNWAY_DAYS,
|
|
DEFAULT_MOOD_REMINDER_HOUR,
|
|
DEFAULT_NOTIFICATION_PREFS,
|
|
DEFAULT_REORDER_LEAD_DAYS,
|
|
isCycleReminderClientManaged,
|
|
isMeasurementReminderClientManaged,
|
|
isMedicationReminderClientManaged,
|
|
notificationPrefsSchema,
|
|
parseNotificationPrefs,
|
|
resolveCoachNudgePrefs,
|
|
resolveDeviceDelivery,
|
|
resolveLowStockRunwayDays,
|
|
resolveMoodReminderHour,
|
|
resolveNotificationPrefs,
|
|
notificationsShowFigures,
|
|
resolveReorderLeadDays,
|
|
} from "../notification-prefs";
|
|
|
|
/**
|
|
* v1.4.49 M-DOUBLE-REMINDER — per-user notification preferences blob.
|
|
*
|
|
* The cron at `src/lib/jobs/reminder-worker.ts` reads
|
|
* `user.notificationPrefs` and calls
|
|
* `isMedicationReminderClientManaged` to decide whether to skip the
|
|
* MEDICATION_REMINDER APNs dispatch. These tests pin the helper's
|
|
* contract so the cron skip is deterministic across schema drift.
|
|
*/
|
|
describe("notificationPrefsSchema", () => {
|
|
it("accepts an empty object (every key is optional)", () => {
|
|
const out = notificationPrefsSchema.parse({});
|
|
expect(out).toEqual({});
|
|
});
|
|
|
|
it("accepts the medication.clientManaged shape", () => {
|
|
const input = { medication: { clientManaged: true } };
|
|
const out = notificationPrefsSchema.parse(input);
|
|
expect(out).toEqual(input);
|
|
});
|
|
|
|
it("rejects a non-boolean clientManaged", () => {
|
|
const res = notificationPrefsSchema.safeParse({
|
|
medication: { clientManaged: "yes" },
|
|
});
|
|
expect(res.success).toBe(false);
|
|
});
|
|
|
|
it("accepts a valid mood.reminderHour (0..23)", () => {
|
|
expect(
|
|
notificationPrefsSchema.safeParse({ mood: { reminderHour: 0 } }).success,
|
|
).toBe(true);
|
|
expect(
|
|
notificationPrefsSchema.safeParse({ mood: { reminderHour: 23 } }).success,
|
|
).toBe(true);
|
|
});
|
|
|
|
it("rejects a mood.reminderHour outside 0..23", () => {
|
|
expect(
|
|
notificationPrefsSchema.safeParse({ mood: { reminderHour: 24 } }).success,
|
|
).toBe(false);
|
|
expect(
|
|
notificationPrefsSchema.safeParse({ mood: { reminderHour: -1 } }).success,
|
|
).toBe(false);
|
|
});
|
|
|
|
it("rejects a non-integer mood.reminderHour", () => {
|
|
expect(
|
|
notificationPrefsSchema.safeParse({ mood: { reminderHour: 9.5 } })
|
|
.success,
|
|
).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("parseNotificationPrefs", () => {
|
|
it("returns documented defaults for null", () => {
|
|
expect(parseNotificationPrefs(null)).toEqual(DEFAULT_NOTIFICATION_PREFS);
|
|
});
|
|
|
|
it("returns documented defaults for undefined", () => {
|
|
expect(parseNotificationPrefs(undefined)).toEqual(
|
|
DEFAULT_NOTIFICATION_PREFS,
|
|
);
|
|
});
|
|
|
|
it("returns defaults when the persisted shape has drifted", () => {
|
|
expect(parseNotificationPrefs({ unknown: "shape" })).toEqual(
|
|
DEFAULT_NOTIFICATION_PREFS,
|
|
);
|
|
});
|
|
|
|
it("returns the persisted shape when valid", () => {
|
|
// v1.7.0 — the resolved shape now carries the roaming
|
|
// `deliveryDefault` (defaulted to "server" when the row omits it).
|
|
expect(
|
|
parseNotificationPrefs({ medication: { clientManaged: true } }),
|
|
).toEqual({
|
|
medication: {
|
|
clientManaged: true,
|
|
deliveryDefault: "server",
|
|
lowStockRunwayDays: 7,
|
|
reorderLeadDays: 10,
|
|
},
|
|
mood: { reminderHour: DEFAULT_MOOD_REMINDER_HOUR },
|
|
cycle: { clientManaged: false },
|
|
coach: {
|
|
nudgesEnabled: true,
|
|
nudgeMedication: true,
|
|
nudgeVitals: true,
|
|
nudgeRoutine: true,
|
|
nudgeFrequency: "weekly",
|
|
nudgeAiComposed: false,
|
|
},
|
|
measurementReminder: {
|
|
clientManaged: false,
|
|
},
|
|
content: { detail: "generic" },
|
|
});
|
|
});
|
|
|
|
it("fills missing keys from the defaults", () => {
|
|
expect(parseNotificationPrefs({ medication: {} })).toEqual({
|
|
medication: {
|
|
clientManaged: false,
|
|
deliveryDefault: "server",
|
|
lowStockRunwayDays: 7,
|
|
reorderLeadDays: 10,
|
|
},
|
|
mood: { reminderHour: DEFAULT_MOOD_REMINDER_HOUR },
|
|
cycle: { clientManaged: false },
|
|
coach: {
|
|
nudgesEnabled: true,
|
|
nudgeMedication: true,
|
|
nudgeVitals: true,
|
|
nudgeRoutine: true,
|
|
nudgeFrequency: "weekly",
|
|
nudgeAiComposed: false,
|
|
},
|
|
measurementReminder: {
|
|
clientManaged: false,
|
|
},
|
|
content: { detail: "generic" },
|
|
});
|
|
});
|
|
|
|
it("v1.7.0 — deliveryDefault 'client' maps onto clientManaged true", () => {
|
|
expect(
|
|
parseNotificationPrefs({ medication: { deliveryDefault: "client" } }),
|
|
).toEqual({
|
|
medication: {
|
|
clientManaged: true,
|
|
deliveryDefault: "client",
|
|
lowStockRunwayDays: 7,
|
|
reorderLeadDays: 10,
|
|
},
|
|
mood: { reminderHour: DEFAULT_MOOD_REMINDER_HOUR },
|
|
cycle: { clientManaged: false },
|
|
coach: {
|
|
nudgesEnabled: true,
|
|
nudgeMedication: true,
|
|
nudgeVitals: true,
|
|
nudgeRoutine: true,
|
|
nudgeFrequency: "weekly",
|
|
nudgeAiComposed: false,
|
|
},
|
|
measurementReminder: {
|
|
clientManaged: false,
|
|
},
|
|
content: { detail: "generic" },
|
|
});
|
|
});
|
|
|
|
it("v1.7.0 — persists a custom mood.reminderHour", () => {
|
|
expect(parseNotificationPrefs({ mood: { reminderHour: 9 } }).mood).toEqual({
|
|
reminderHour: 9,
|
|
});
|
|
});
|
|
|
|
it("v1.7.0 — falls back to the default hour when mood is absent", () => {
|
|
expect(parseNotificationPrefs(null).mood.reminderHour).toBe(
|
|
DEFAULT_MOOD_REMINDER_HOUR,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("resolveNotificationPrefs (deep-merge)", () => {
|
|
it("layers the input over the persisted row", () => {
|
|
const out = resolveNotificationPrefs(
|
|
{ medication: { clientManaged: false } },
|
|
{ medication: { clientManaged: true } },
|
|
);
|
|
expect(out).toEqual({
|
|
medication: {
|
|
clientManaged: true,
|
|
deliveryDefault: "server",
|
|
lowStockRunwayDays: 7,
|
|
reorderLeadDays: 10,
|
|
},
|
|
mood: { reminderHour: DEFAULT_MOOD_REMINDER_HOUR },
|
|
cycle: { clientManaged: false },
|
|
coach: {
|
|
nudgesEnabled: true,
|
|
nudgeMedication: true,
|
|
nudgeVitals: true,
|
|
nudgeRoutine: true,
|
|
nudgeFrequency: "weekly",
|
|
nudgeAiComposed: false,
|
|
},
|
|
measurementReminder: {
|
|
clientManaged: false,
|
|
},
|
|
content: { detail: "generic" },
|
|
});
|
|
});
|
|
|
|
it("preserves the persisted medication keys when the input only touches new sub-keys", () => {
|
|
// Future-proof: the route hands the merged shape to Prisma, so
|
|
// sibling keys inside `medication` must survive a partial PATCH.
|
|
const out = resolveNotificationPrefs(
|
|
{ medication: { clientManaged: true } },
|
|
{ medication: {} },
|
|
);
|
|
expect(out).toEqual({
|
|
medication: {
|
|
clientManaged: true,
|
|
deliveryDefault: "server",
|
|
lowStockRunwayDays: 7,
|
|
reorderLeadDays: 10,
|
|
},
|
|
mood: { reminderHour: DEFAULT_MOOD_REMINDER_HOUR },
|
|
cycle: { clientManaged: false },
|
|
coach: {
|
|
nudgesEnabled: true,
|
|
nudgeMedication: true,
|
|
nudgeVitals: true,
|
|
nudgeRoutine: true,
|
|
nudgeFrequency: "weekly",
|
|
nudgeAiComposed: false,
|
|
},
|
|
measurementReminder: {
|
|
clientManaged: false,
|
|
},
|
|
content: { detail: "generic" },
|
|
});
|
|
});
|
|
|
|
it("falls back to defaults when the persisted row is null", () => {
|
|
const out = resolveNotificationPrefs(null, {
|
|
medication: { clientManaged: true },
|
|
});
|
|
expect(out).toEqual({
|
|
medication: {
|
|
clientManaged: true,
|
|
deliveryDefault: "server",
|
|
lowStockRunwayDays: 7,
|
|
reorderLeadDays: 10,
|
|
},
|
|
mood: { reminderHour: DEFAULT_MOOD_REMINDER_HOUR },
|
|
cycle: { clientManaged: false },
|
|
coach: {
|
|
nudgesEnabled: true,
|
|
nudgeMedication: true,
|
|
nudgeVitals: true,
|
|
nudgeRoutine: true,
|
|
nudgeFrequency: "weekly",
|
|
nudgeAiComposed: false,
|
|
},
|
|
measurementReminder: {
|
|
clientManaged: false,
|
|
},
|
|
content: { detail: "generic" },
|
|
});
|
|
});
|
|
|
|
it("v1.7.0 — PATCHing deliveryDefault 'client' roams + maps to clientManaged", () => {
|
|
const out = resolveNotificationPrefs(null, {
|
|
medication: { deliveryDefault: "client" },
|
|
});
|
|
expect(out).toEqual({
|
|
medication: {
|
|
clientManaged: true,
|
|
deliveryDefault: "client",
|
|
lowStockRunwayDays: 7,
|
|
reorderLeadDays: 10,
|
|
},
|
|
mood: { reminderHour: DEFAULT_MOOD_REMINDER_HOUR },
|
|
cycle: { clientManaged: false },
|
|
coach: {
|
|
nudgesEnabled: true,
|
|
nudgeMedication: true,
|
|
nudgeVitals: true,
|
|
nudgeRoutine: true,
|
|
nudgeFrequency: "weekly",
|
|
nudgeAiComposed: false,
|
|
},
|
|
measurementReminder: {
|
|
clientManaged: false,
|
|
},
|
|
content: { detail: "generic" },
|
|
});
|
|
});
|
|
|
|
it("v1.7.0 — PATCHing mood.reminderHour preserves medication siblings", () => {
|
|
const out = resolveNotificationPrefs(
|
|
{ medication: { clientManaged: true } },
|
|
{ mood: { reminderHour: 8 } },
|
|
);
|
|
expect(out).toEqual({
|
|
medication: {
|
|
clientManaged: true,
|
|
deliveryDefault: "server",
|
|
lowStockRunwayDays: 7,
|
|
reorderLeadDays: 10,
|
|
},
|
|
mood: { reminderHour: 8 },
|
|
cycle: { clientManaged: false },
|
|
coach: {
|
|
nudgesEnabled: true,
|
|
nudgeMedication: true,
|
|
nudgeVitals: true,
|
|
nudgeRoutine: true,
|
|
nudgeFrequency: "weekly",
|
|
nudgeAiComposed: false,
|
|
},
|
|
measurementReminder: {
|
|
clientManaged: false,
|
|
},
|
|
content: { detail: "generic" },
|
|
});
|
|
});
|
|
|
|
it("returns the defaults when neither side carries a value", () => {
|
|
const out = resolveNotificationPrefs(null, {});
|
|
expect(out).toEqual(DEFAULT_NOTIFICATION_PREFS);
|
|
});
|
|
});
|
|
|
|
describe("isMedicationReminderClientManaged — cron-skip gate", () => {
|
|
it("returns false for a null row (legacy server-managed default)", () => {
|
|
expect(isMedicationReminderClientManaged(null)).toBe(false);
|
|
});
|
|
|
|
it("returns false for undefined (Prisma findUnique miss)", () => {
|
|
expect(isMedicationReminderClientManaged(undefined)).toBe(false);
|
|
});
|
|
|
|
it("returns false when the user has not opted in", () => {
|
|
expect(
|
|
isMedicationReminderClientManaged({
|
|
medication: { clientManaged: false },
|
|
}),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("returns true when the user has opted in via the iOS app", () => {
|
|
expect(
|
|
isMedicationReminderClientManaged({
|
|
medication: { clientManaged: true },
|
|
}),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("v1.7.0 — returns true when deliveryDefault is 'client'", () => {
|
|
expect(
|
|
isMedicationReminderClientManaged({
|
|
medication: { deliveryDefault: "client" },
|
|
}),
|
|
).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("isCycleReminderClientManaged — cron-skip gate", () => {
|
|
it("returns false for a null / undefined row (server-managed default)", () => {
|
|
expect(isCycleReminderClientManaged(null)).toBe(false);
|
|
expect(isCycleReminderClientManaged(undefined)).toBe(false);
|
|
});
|
|
|
|
it("returns false when the user has not opted in", () => {
|
|
expect(
|
|
isCycleReminderClientManaged({ cycle: { clientManaged: false } }),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("returns true when the iOS app owns local cycle reminders", () => {
|
|
expect(
|
|
isCycleReminderClientManaged({ cycle: { clientManaged: true } }),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("is independent of the medication delivery default", () => {
|
|
// The medication `deliveryDefault: client` must NOT suppress cycle pushes.
|
|
expect(
|
|
isCycleReminderClientManaged({
|
|
medication: { deliveryDefault: "client" },
|
|
}),
|
|
).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("isMeasurementReminderClientManaged — cron-skip gate", () => {
|
|
it("returns false for a null / undefined row (server-managed default)", () => {
|
|
expect(isMeasurementReminderClientManaged(null)).toBe(false);
|
|
expect(isMeasurementReminderClientManaged(undefined)).toBe(false);
|
|
});
|
|
|
|
it("returns false when the user has not opted in", () => {
|
|
expect(
|
|
isMeasurementReminderClientManaged({
|
|
measurementReminder: { clientManaged: false },
|
|
}),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("returns true when the iOS app owns local Vorsorge reminders", () => {
|
|
expect(
|
|
isMeasurementReminderClientManaged({
|
|
measurementReminder: { clientManaged: true },
|
|
}),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("is independent of the cycle / medication client-managed flags", () => {
|
|
expect(
|
|
isMeasurementReminderClientManaged({
|
|
cycle: { clientManaged: true },
|
|
medication: { deliveryDefault: "client" },
|
|
}),
|
|
).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("resolveMoodReminderHour — cron hour gate", () => {
|
|
it("returns the default hour for a null row", () => {
|
|
expect(resolveMoodReminderHour(null)).toBe(DEFAULT_MOOD_REMINDER_HOUR);
|
|
});
|
|
|
|
it("returns the default hour for undefined", () => {
|
|
expect(resolveMoodReminderHour(undefined)).toBe(DEFAULT_MOOD_REMINDER_HOUR);
|
|
});
|
|
|
|
it("returns the default hour for a drifted shape", () => {
|
|
expect(resolveMoodReminderHour({ unknown: "shape" })).toBe(
|
|
DEFAULT_MOOD_REMINDER_HOUR,
|
|
);
|
|
});
|
|
|
|
it("returns the persisted custom hour", () => {
|
|
expect(resolveMoodReminderHour({ mood: { reminderHour: 7 } })).toBe(7);
|
|
});
|
|
|
|
it("returns the default hour when mood is present but empty", () => {
|
|
expect(resolveMoodReminderHour({ mood: {} })).toBe(
|
|
DEFAULT_MOOD_REMINDER_HOUR,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("resolveDeviceDelivery — per-device override", () => {
|
|
it("defaults to server when nothing is set", () => {
|
|
expect(resolveDeviceDelivery(null, null)).toBe("server");
|
|
expect(resolveDeviceDelivery(null, undefined)).toBe("server");
|
|
});
|
|
|
|
it("the device override wins over the user-level default", () => {
|
|
expect(
|
|
resolveDeviceDelivery(
|
|
{ medication: { deliveryDefault: "server" } },
|
|
"client",
|
|
),
|
|
).toBe("client");
|
|
expect(
|
|
resolveDeviceDelivery(
|
|
{ medication: { deliveryDefault: "client" } },
|
|
"server",
|
|
),
|
|
).toBe("server");
|
|
});
|
|
|
|
it("a null override inherits the user-level roaming default", () => {
|
|
expect(
|
|
resolveDeviceDelivery(
|
|
{ medication: { deliveryDefault: "client" } },
|
|
null,
|
|
),
|
|
).toBe("client");
|
|
});
|
|
|
|
it("returns false for a drifted persisted shape (forward-compat)", () => {
|
|
expect(
|
|
isMedicationReminderClientManaged({ notACategory: { foo: "bar" } }),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("returns false when only sibling categories are present", () => {
|
|
// Future-proof: another (future) category set to true must NOT
|
|
// gate MEDICATION_REMINDER — only `medication.clientManaged`
|
|
// affects the medication cron.
|
|
expect(
|
|
isMedicationReminderClientManaged({
|
|
// Cast through unknown — the production runtime sees this as
|
|
// a JSONB value that bypasses the zod schema, but the helper
|
|
// must still resolve to the safe default.
|
|
otherCategory: { enabled: true },
|
|
} as unknown),
|
|
).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("resolveCoachNudgePrefs — v1.16.5 nudge cron gate", () => {
|
|
it("resolves the documented defaults for a null row", () => {
|
|
expect(resolveCoachNudgePrefs(null)).toEqual({
|
|
enabled: true,
|
|
groups: { medication: true, vitals: true, routine: true },
|
|
minIntervalDays: 7,
|
|
aiComposed: false,
|
|
});
|
|
});
|
|
|
|
it("keeps legacy rows (master switch only) fully enabled", () => {
|
|
const out = resolveCoachNudgePrefs({ coach: { nudgesEnabled: true } });
|
|
expect(out.groups).toEqual({
|
|
medication: true,
|
|
vitals: true,
|
|
routine: true,
|
|
});
|
|
expect(out.minIntervalDays).toBe(7);
|
|
});
|
|
|
|
it("carries the master opt-out", () => {
|
|
expect(
|
|
resolveCoachNudgePrefs({ coach: { nudgesEnabled: false } }).enabled,
|
|
).toBe(false);
|
|
});
|
|
|
|
it("resolves per-group opt-outs independently", () => {
|
|
const out = resolveCoachNudgePrefs({
|
|
coach: { nudgeMedication: false, nudgeRoutine: false },
|
|
});
|
|
expect(out.groups).toEqual({
|
|
medication: false,
|
|
vitals: true,
|
|
routine: false,
|
|
});
|
|
// Group opt-outs do not touch the master switch.
|
|
expect(out.enabled).toBe(true);
|
|
});
|
|
|
|
it("maps the biweekly frequency pref onto 14 days", () => {
|
|
expect(
|
|
resolveCoachNudgePrefs({ coach: { nudgeFrequency: "biweekly" } })
|
|
.minIntervalDays,
|
|
).toBe(14);
|
|
});
|
|
|
|
it("rejects an unknown frequency by falling back to defaults", () => {
|
|
// A drifted enum value fails the schema; the whole blob resolves
|
|
// to the documented defaults rather than throwing in the cron.
|
|
expect(
|
|
resolveCoachNudgePrefs({ coach: { nudgeFrequency: "daily" } })
|
|
.minIntervalDays,
|
|
).toBe(7);
|
|
});
|
|
});
|
|
|
|
describe("lowStockRunwayDays — v1.16.11 low-stock threshold", () => {
|
|
it("accepts the 1..60 range and the explicit null (off)", () => {
|
|
for (const value of [1, 7, 60, null]) {
|
|
expect(
|
|
notificationPrefsSchema.safeParse({
|
|
medication: { lowStockRunwayDays: value },
|
|
}).success,
|
|
).toBe(true);
|
|
}
|
|
});
|
|
|
|
it("rejects out-of-range and non-integer values", () => {
|
|
for (const value of [0, 61, 7.5, "7"]) {
|
|
expect(
|
|
notificationPrefsSchema.safeParse({
|
|
medication: { lowStockRunwayDays: value },
|
|
}).success,
|
|
).toBe(false);
|
|
}
|
|
});
|
|
|
|
it("resolves the default (7) for a null / drifted row", () => {
|
|
expect(resolveLowStockRunwayDays(null)).toBe(DEFAULT_LOW_STOCK_RUNWAY_DAYS);
|
|
expect(resolveLowStockRunwayDays(undefined)).toBe(
|
|
DEFAULT_LOW_STOCK_RUNWAY_DAYS,
|
|
);
|
|
expect(resolveLowStockRunwayDays({ unknown: "shape" })).toBe(
|
|
DEFAULT_LOW_STOCK_RUNWAY_DAYS,
|
|
);
|
|
});
|
|
|
|
it("returns the persisted custom threshold", () => {
|
|
expect(
|
|
resolveLowStockRunwayDays({ medication: { lowStockRunwayDays: 14 } }),
|
|
).toBe(14);
|
|
});
|
|
|
|
it("resolves the reorder lead default (10) for a null / drifted row", () => {
|
|
expect(resolveReorderLeadDays(null, null)).toBe(DEFAULT_REORDER_LEAD_DAYS);
|
|
expect(resolveReorderLeadDays({ unknown: "shape" }, undefined)).toBe(
|
|
DEFAULT_REORDER_LEAD_DAYS,
|
|
);
|
|
});
|
|
|
|
it("reads the persisted user-level reorder lead when no per-med override", () => {
|
|
expect(
|
|
resolveReorderLeadDays({ medication: { reorderLeadDays: 21 } }, null),
|
|
).toBe(21);
|
|
});
|
|
|
|
it("a per-medication reorder-lead override beats the user default", () => {
|
|
// User default 21, per-med 0 → the per-med value wins (incl. 0).
|
|
expect(
|
|
resolveReorderLeadDays({ medication: { reorderLeadDays: 21 } }, 0),
|
|
).toBe(0);
|
|
expect(
|
|
resolveReorderLeadDays({ medication: { reorderLeadDays: 21 } }, 5),
|
|
).toBe(5);
|
|
});
|
|
|
|
it("returns null when the user switched the alert off", () => {
|
|
expect(
|
|
resolveLowStockRunwayDays({ medication: { lowStockRunwayDays: null } }),
|
|
).toBe(null);
|
|
});
|
|
|
|
it("survives a deep-merge round-trip (PATCH semantics)", () => {
|
|
// PATCH { medication: { lowStockRunwayDays: 21 } } over a row that
|
|
// already opted into clientManaged must preserve the sibling.
|
|
const merged = resolveNotificationPrefs(
|
|
{ medication: { clientManaged: true } },
|
|
{ medication: { lowStockRunwayDays: 21 } },
|
|
);
|
|
expect(merged.medication).toEqual({
|
|
clientManaged: true,
|
|
deliveryDefault: "server",
|
|
lowStockRunwayDays: 21,
|
|
reorderLeadDays: 10,
|
|
});
|
|
// ...and the next PATCH (threshold off) keeps the sibling again.
|
|
const off = resolveNotificationPrefs(merged, {
|
|
medication: { lowStockRunwayDays: null },
|
|
});
|
|
expect(off.medication).toEqual({
|
|
clientManaged: true,
|
|
deliveryDefault: "server",
|
|
lowStockRunwayDays: null,
|
|
reorderLeadDays: 10,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("notificationsShowFigures (content.detail)", () => {
|
|
it("defaults to generic: no figures for a null, empty or drifted row", () => {
|
|
expect(notificationsShowFigures(null)).toBe(false);
|
|
expect(notificationsShowFigures({})).toBe(false);
|
|
expect(notificationsShowFigures({ content: { detail: "all" } })).toBe(
|
|
false,
|
|
);
|
|
});
|
|
|
|
it("shows figures only when the user chose detailed", () => {
|
|
expect(notificationsShowFigures({ content: { detail: "detailed" } })).toBe(
|
|
true,
|
|
);
|
|
expect(notificationsShowFigures({ content: { detail: "generic" } })).toBe(
|
|
false,
|
|
);
|
|
});
|
|
|
|
it("survives a merge that touches another category", () => {
|
|
const merged = resolveNotificationPrefs(
|
|
{ content: { detail: "detailed" } },
|
|
{ mood: { reminderHour: 8 } },
|
|
);
|
|
expect(merged.content).toEqual({ detail: "detailed" });
|
|
});
|
|
});
|