feat: enable weekday-based medication scheduling

Closes #463
This commit is contained in:
Daniel Volz
2026-03-20 14:58:25 +01:00
committed by GitHub
parent 29f4c4e48d
commit 68ab79c713
35 changed files with 1856 additions and 841 deletions
@@ -2,7 +2,7 @@ import { fireEvent, render, screen } from "@testing-library/react";
import type { FormEvent } from "react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { MobileEditModal } from "../../components/MobileEditModal";
import type { FormState } from "../../types";
import type { FormState, WeekdayCode } from "../../types";
const defaultForm: FormState = {
name: "",
@@ -429,6 +429,61 @@ describe("MobileEditModal blister management", () => {
expect(onSetIntakeValue).toHaveBeenCalled();
}
});
it("shows weekday controls and validation error for weekday schedules", () => {
const form = {
...defaultForm,
name: "Weekday Med",
intakes: [
{
usage: "1",
every: "1",
startDate: "2024-01-01",
startTime: "09:00",
scheduleMode: "weekdays" as const,
weekdays: [],
takenBy: "",
intakeRemindersEnabled: false,
},
],
};
render(<MobileEditModal {...defaultProps} form={form} formChanged={true} />);
fireEvent.click(screen.getByRole("tab", { name: "form.sections.schedule" }));
expect(screen.getByText("form.blisters.weekdaysRequired")).toBeInTheDocument();
expect(screen.getByText("form.blisters.weekdays")).toBeInTheDocument();
expect(screen.queryByLabelText("form.blisters.everyDays")).not.toBeInTheDocument();
expect(document.querySelector('button[type="submit"]')).toHaveClass("has-validation-error");
});
it("toggles weekday selections for weekday schedules", () => {
const onSetIntakeValue = vi.fn();
const form = {
...defaultForm,
name: "Weekday Med",
intakes: [
{
usage: "1",
every: "1",
startDate: "2024-01-01",
startTime: "09:00",
scheduleMode: "weekdays" as const,
weekdays: ["wed"] satisfies WeekdayCode[],
takenBy: "",
intakeRemindersEnabled: false,
},
],
};
render(<MobileEditModal {...defaultProps} form={form} onSetIntakeValue={onSetIntakeValue} />);
fireEvent.click(screen.getByRole("tab", { name: "form.sections.schedule" }));
fireEvent.click(screen.getByTitle("form.blisters.weekdaysLong.mon"));
expect(onSetIntakeValue).toHaveBeenCalledWith(0, "weekdays", ["mon", "wed"]);
});
});
describe("MobileEditModal form submission", () => {
@@ -2,6 +2,7 @@ import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import ReportModal from "../../components/ReportModal";
import type { Medication } from "../../types";
import { formatDate, formatDateTime } from "../../utils/formatters";
function createMedication(overrides: Partial<Medication> = {}): Medication {
return {
@@ -65,6 +66,53 @@ describe("ReportModal", () => {
expect(URL.createObjectURL).toHaveBeenCalled();
});
it("renders shared formatter output in exported text reports", async () => {
const onClose = vi.fn();
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({
ok: true,
json: async () => ({
1: {
dosesTaken: 1,
automaticDosesTaken: 0,
dosesDismissed: 0,
firstDoseAt: "2026-02-03T12:00:00.000Z",
lastDoseAt: null,
refills: [],
},
}),
});
render(
<ReportModal
isOpen={true}
onClose={onClose}
medications={[
createMedication({
medicationStartDate: "2026-02-01",
blisters: [{ usage: 1, every: 1, start: "2026-02-02T08:30:00.000Z" }],
}),
]}
/>
);
fireEvent.click(screen.getByRole("radio", { name: /report\.formatTxt/i }));
fireEvent.click(screen.getByRole("button", { name: /report\.generate/i }));
await waitFor(() => {
expect(URL.createObjectURL).toHaveBeenCalled();
});
const [blob] = (URL.createObjectURL as ReturnType<typeof vi.fn>).mock.calls.at(-1) ?? [];
expect(blob).toBeInstanceOf(Blob);
const content = await (blob as Blob).text();
expect(content).toContain(formatDate("2026-02-01"));
expect(content).toContain(formatDateTime("2026-02-02T08:30:00.000Z"));
expect(content).toContain(formatDate("2026-02-03T12:00:00.000Z"));
expect(onClose).toHaveBeenCalledTimes(1);
});
it("generates printable report when PDF format is selected", async () => {
const onClose = vi.fn();
const mockWrite = vi.fn();
@@ -83,16 +131,35 @@ describe("ReportModal", () => {
ok: true,
json: async () => ({
1: {
dosesTaken: 0,
dosesTaken: 1,
automaticDosesTaken: 0,
dosesDismissed: 0,
firstDoseAt: null,
firstDoseAt: "2026-03-03T12:00:00.000Z",
lastDoseAt: null,
refills: [],
refills: [
{
packsAdded: 1,
loosePillsAdded: 0,
usedPrescription: false,
refillDate: "2026-03-04",
},
],
},
}),
});
render(<ReportModal isOpen={true} onClose={onClose} medications={[createMedication()]} />);
render(
<ReportModal
isOpen={true}
onClose={onClose}
medications={[
createMedication({
medicationStartDate: "2026-03-01",
blisters: [{ usage: 1, every: 1, start: "2026-03-02T08:30:00.000Z" }],
}),
]}
/>
);
fireEvent.click(screen.getByRole("button", { name: /report\.generate/i }));
await waitFor(() => {
@@ -101,6 +168,11 @@ describe("ReportModal", () => {
expect(mockClose).toHaveBeenCalled();
});
const [html] = mockWrite.mock.calls.at(-1) ?? [];
expect(html).toContain(formatDate("2026-03-01"));
expect(html).toContain(formatDateTime("2026-03-02T08:30:00.000Z"));
expect(html).toContain(formatDate("2026-03-03T12:00:00.000Z"));
expect(html).toContain(formatDate("2026-03-04"));
expect(onClose).toHaveBeenCalledTimes(1);
});