mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
refactor(medications): share the wizard cadence-line builder
`summariseCadence` and `summariseScheduleDraft` each re-spelled the
cadence phrase, its detail clause, and the interpolation count. Extract
a single `{mode, cadence, subControls}`-shaped helper both call so the
medication-wide and per-schedule summaries can't drift apart, and drop
the now-redundant `summaryKeyForCadence` wrapper. Behaviour is
identical; add coverage pinning the per-schedule summary to the
medication-wide summary's leading cadence segment.
This commit is contained in:
1 parent
4038cfcffe
commit
2c91dc7b75
2 files changed
+98
-54
No files matched your search
@@ -14,6 +14,7 @@ import {
|
||||
rowFromTreatment,
|
||||
setActiveSchedule,
|
||||
summariseCadence,
|
||||
summariseScheduleDraft,
|
||||
validateStep,
|
||||
type WizardPayload,
|
||||
WIZARD_TREATMENT_MAPPING,
|
||||
@@ -559,6 +560,49 @@ describe("summariseCadence", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("summariseScheduleDraft — shared cadence-line helper", () => {
|
||||
const t = makeStubT();
|
||||
|
||||
// The per-schedule summary and the medication-wide summary build their
|
||||
// cadence segment from the same extracted helper, so for an equivalent
|
||||
// cadence shape the draft summary equals the leading segment of the
|
||||
// full summary (which trails the times / course-window phrases).
|
||||
it("matches summariseCadence's leading cadence segment", () => {
|
||||
const cases: WizardPayload[] = [
|
||||
withCadence("daily"),
|
||||
withCadence("everyNWeeks", { intervalWeeks: 2, weekdays: ["MO"] }),
|
||||
withCadence("rolling", { rollingDays: 14 }),
|
||||
withCadence("weekdays", { weekdays: ["MO", "WE", "FR"] }),
|
||||
withCadence("monthly", { dayOfMonth: 15 }),
|
||||
];
|
||||
for (const base of cases) {
|
||||
// `withCadence` only sets the flat mirror fields; commit them onto
|
||||
// the active schedule draft so the draft carries the same cadence.
|
||||
const p = commitActiveDraft(base);
|
||||
const draft = p.schedules[p.activeScheduleIndex];
|
||||
const draftLine = summariseScheduleDraft(draft, t);
|
||||
const fullSummary = summariseCadence(p, t);
|
||||
// The draft line's cadence segment is the leading " · " segment of
|
||||
// the full summary (which trails times / course-window phrases).
|
||||
const firstSegment = fullSummary.split(" · ")[0];
|
||||
expect(draftLine.split(" · ")[0]).toBe(firstSegment);
|
||||
}
|
||||
});
|
||||
|
||||
it("threads the everyNWeeks interval into the cadence key", () => {
|
||||
const p = commitActiveDraft(withCadence("everyNWeeks", { intervalWeeks: 3 }));
|
||||
const out = summariseScheduleDraft(p.schedules[p.activeScheduleIndex], t);
|
||||
expect(out).toMatch(/cadence\.everyNWeeks[^|]*"n":3/);
|
||||
});
|
||||
|
||||
it("omits the cadence detail for a one-shot draft", () => {
|
||||
const p = commitActiveDraft(withCadence("oneShot"));
|
||||
const out = summariseScheduleDraft(p.schedules[p.activeScheduleIndex], t);
|
||||
expect(out).not.toContain("weekdaysDetail");
|
||||
expect(out).not.toContain("dayOfMonthDetail");
|
||||
});
|
||||
});
|
||||
|
||||
describe("hydrateWizardPayload", () => {
|
||||
function makeInitial(
|
||||
overrides: Partial<MedicationPayload> = {},
|
||||
|
||||
@@ -712,10 +712,6 @@ function summaryKeyForDraft(
|
||||
}
|
||||
}
|
||||
|
||||
function summaryKeyForCadence(payload: WizardPayload): string {
|
||||
return summaryKeyForDraft(payload.mode, payload.cadence, payload.subControls);
|
||||
}
|
||||
|
||||
function weekdaysDetail(
|
||||
tokens: readonly string[],
|
||||
t: (key: string, params?: Record<string, string | number>) => string,
|
||||
@@ -732,25 +728,71 @@ function weekdaysDetail(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The cadence-bearing slice both {@link WizardPayload} and
|
||||
* {@link ScheduleDraft} share. Summary helpers only ever read these three
|
||||
* fields, so the cadence-line builder below stays draft/payload-agnostic.
|
||||
*/
|
||||
type CadenceShape = Pick<ScheduleDraft, "mode" | "cadence" | "subControls">;
|
||||
|
||||
/**
|
||||
* The interpolation count the cadence summary keys read. `everyNWeeks`
|
||||
* surfaces the week interval; `rolling` the day interval; everything else
|
||||
* leaves it at zero. Extracted so the medication-wide and per-schedule
|
||||
* summaries can't drift apart.
|
||||
*/
|
||||
function cadenceInterpolationN(shape: CadenceShape): number {
|
||||
if (shape.cadence.kind === "everyNWeeks") return shape.subControls.intervalWeeks;
|
||||
if (shape.cadence.kind === "rolling")
|
||||
return shape.cadence.rollingIntervalDays ?? 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The trailing detail clause appended to a cadence phrase (the weekday
|
||||
* list or the day-of-month). Empty for one-shot doses and for cadences
|
||||
* that carry no extra detail.
|
||||
*/
|
||||
function cadenceDetailPhrase(
|
||||
payload: WizardPayload,
|
||||
shape: CadenceShape,
|
||||
t: (key: string, params?: Record<string, string | number>) => string,
|
||||
): string {
|
||||
if (payload.mode === "oneShot") return "";
|
||||
switch (payload.cadence.kind) {
|
||||
if (shape.mode === "oneShot") return "";
|
||||
switch (shape.cadence.kind) {
|
||||
case "weekdays":
|
||||
return weekdaysDetail(payload.subControls.weekdays, t);
|
||||
return weekdaysDetail(shape.subControls.weekdays, t);
|
||||
case "everyNWeeks":
|
||||
return weekdaysDetail(payload.subControls.weekdays, t);
|
||||
return weekdaysDetail(shape.subControls.weekdays, t);
|
||||
case "monthly":
|
||||
return t("medications.wizard.summary.dayOfMonthDetail", {
|
||||
day: payload.subControls.dayOfMonth,
|
||||
day: shape.subControls.dayOfMonth,
|
||||
});
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The full cadence line — phrase (`every N weeks`, `daily`, …) plus its
|
||||
* detail clause. Both {@link summariseCadence} and
|
||||
* {@link summariseScheduleDraft} build their first segment from this so
|
||||
* the two summaries render byte-identical cadence text.
|
||||
*/
|
||||
function cadenceLineFor(
|
||||
shape: CadenceShape,
|
||||
t: (key: string, params?: Record<string, string | number>) => string,
|
||||
): string {
|
||||
const cadenceKey = summaryKeyForDraft(
|
||||
shape.mode,
|
||||
shape.cadence,
|
||||
shape.subControls,
|
||||
);
|
||||
const cadencePhrase = t(`medications.wizard.summary.cadence.${cadenceKey}`, {
|
||||
n: cadenceInterpolationN(shape),
|
||||
});
|
||||
return cadencePhrase + cadenceDetailPhrase(shape, t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Plain-language summary for Step 8. Mirrors v1.5.3's helper shape so
|
||||
* the test surface stays familiar. The `t` callback is passed in so
|
||||
@@ -760,20 +802,7 @@ export function summariseCadence(
|
||||
payload: WizardPayload,
|
||||
t: (key: string, params?: Record<string, string | number>) => string,
|
||||
): string {
|
||||
const cadenceKey = summaryKeyForCadence(payload);
|
||||
const cadencePhrase = t(
|
||||
`medications.wizard.summary.cadence.${cadenceKey}`,
|
||||
{
|
||||
n:
|
||||
payload.cadence.kind === "everyNWeeks"
|
||||
? payload.subControls.intervalWeeks
|
||||
: payload.cadence.kind === "rolling"
|
||||
? (payload.cadence.rollingIntervalDays ?? 0)
|
||||
: 0,
|
||||
},
|
||||
);
|
||||
const cadenceDetail = cadenceDetailPhrase(payload, t);
|
||||
const cadenceLine = cadencePhrase + cadenceDetail;
|
||||
const cadenceLine = cadenceLineFor(payload, t);
|
||||
const timesPhrase =
|
||||
payload.mode !== "oneShot" && payload.timesOfDay.length > 0
|
||||
? t("medications.wizard.summary.times", {
|
||||
@@ -807,36 +836,7 @@ export function summariseScheduleDraft(
|
||||
draft: ScheduleDraft,
|
||||
t: (key: string, params?: Record<string, string | number>) => string,
|
||||
): string {
|
||||
const cadenceKey = summaryKeyForDraft(
|
||||
draft.mode,
|
||||
draft.cadence,
|
||||
draft.subControls,
|
||||
);
|
||||
const cadencePhrase = t(
|
||||
`medications.wizard.summary.cadence.${cadenceKey}`,
|
||||
{
|
||||
n:
|
||||
draft.cadence.kind === "everyNWeeks"
|
||||
? draft.subControls.intervalWeeks
|
||||
: draft.cadence.kind === "rolling"
|
||||
? (draft.cadence.rollingIntervalDays ?? 0)
|
||||
: 0,
|
||||
},
|
||||
);
|
||||
let cadenceDetail = "";
|
||||
if (draft.mode !== "oneShot") {
|
||||
if (
|
||||
draft.cadence.kind === "weekdays" ||
|
||||
draft.cadence.kind === "everyNWeeks"
|
||||
) {
|
||||
cadenceDetail = weekdaysDetail(draft.subControls.weekdays, t);
|
||||
} else if (draft.cadence.kind === "monthly") {
|
||||
cadenceDetail = t("medications.wizard.summary.dayOfMonthDetail", {
|
||||
day: draft.subControls.dayOfMonth,
|
||||
});
|
||||
}
|
||||
}
|
||||
const cadenceLine = cadencePhrase + cadenceDetail;
|
||||
const cadenceLine = cadenceLineFor(draft, t);
|
||||
const timesPhrase =
|
||||
draft.mode !== "oneShot" && draft.timesOfDay.length > 0
|
||||
? t("medications.wizard.summary.times", {
|
||||
|
||||
Reference in new issue
Block a user