refactor: decompose frontend state and medication dialog flows
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { MedicationDialogs } from "../../components/medications/MedicationDialogs";
|
||||
import type { Medication } from "../../types";
|
||||
|
||||
vi.mock("../../components/ConfirmModal", () => ({
|
||||
ConfirmModal: ({
|
||||
title,
|
||||
confirmLabel,
|
||||
cancelLabel,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
overlayClassName,
|
||||
}: {
|
||||
title: string;
|
||||
confirmLabel: string;
|
||||
cancelLabel: string;
|
||||
onConfirm: () => void;
|
||||
onCancel: () => void;
|
||||
overlayClassName?: string;
|
||||
}) => (
|
||||
<div data-testid={`confirm-${title}`} data-overlay-class={overlayClassName ?? ""}>
|
||||
<button type="button" onClick={onConfirm}>
|
||||
{confirmLabel}
|
||||
</button>
|
||||
<button type="button" onClick={onCancel}>
|
||||
{cancelLabel}
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock("../../components/Lightbox", () => ({
|
||||
Lightbox: ({ src, alt }: { src: string; alt: string }) => <div data-testid="lightbox">{`${src}|${alt}`}</div>,
|
||||
}));
|
||||
|
||||
vi.mock("../../components/ReportModal", () => ({
|
||||
default: ({ isOpen }: { isOpen: boolean }) => (isOpen ? <div data-testid="report-modal">report</div> : null),
|
||||
}));
|
||||
|
||||
const baseMedication: Medication = {
|
||||
id: 1,
|
||||
name: "Aspirin",
|
||||
genericName: "Acetylsalicylic acid",
|
||||
takenBy: ["John"],
|
||||
packageType: "blister",
|
||||
packCount: 1,
|
||||
blistersPerPack: 2,
|
||||
pillsPerBlister: 10,
|
||||
totalPills: null,
|
||||
packageAmountValue: null,
|
||||
packageAmountUnit: null,
|
||||
looseTablets: 0,
|
||||
stockAdjustment: 0,
|
||||
lastStockCorrectionAt: null,
|
||||
pillWeightMg: 500,
|
||||
doseUnit: "mg",
|
||||
medicationForm: "tablet",
|
||||
pillForm: "tablet",
|
||||
lifecycleCategory: "refill_when_empty",
|
||||
blisters: [{ usage: 1, every: 1, start: "2024-01-01T09:00:00Z" }],
|
||||
intakes: [
|
||||
{
|
||||
usage: 1,
|
||||
every: 1,
|
||||
start: "2024-01-01T09:00:00Z",
|
||||
takenBy: "",
|
||||
intakeRemindersEnabled: false,
|
||||
scheduleMode: "interval",
|
||||
weekdays: [],
|
||||
},
|
||||
],
|
||||
imageUrl: null,
|
||||
expiryDate: null,
|
||||
notes: null,
|
||||
intakeRemindersEnabled: false,
|
||||
medicationStartDate: "",
|
||||
medicationEndDate: null,
|
||||
autoMarkObsoleteAfterEndDate: true,
|
||||
isObsolete: false,
|
||||
obsoleteAt: null,
|
||||
prescriptionEnabled: false,
|
||||
prescriptionAuthorizedRefills: null,
|
||||
prescriptionRemainingRefills: null,
|
||||
prescriptionLowRefillThreshold: 1,
|
||||
prescriptionExpiryDate: null,
|
||||
dismissedUntil: null,
|
||||
updatedAt: null,
|
||||
};
|
||||
|
||||
function createProps(overrides: Partial<React.ComponentProps<typeof MedicationDialogs>> = {}) {
|
||||
return {
|
||||
mobileEditModal: <div data-testid="mobile-edit">mobile</div>,
|
||||
showUnsavedConfirm: false,
|
||||
unsavedCancelLabel: "cancel-unsaved",
|
||||
unsavedConfirmLabel: "confirm-unsaved",
|
||||
unsavedMessage: "unsaved-message",
|
||||
unsavedTitle: "unsaved-title",
|
||||
onConfirmClose: vi.fn(),
|
||||
onCancelClose: vi.fn(),
|
||||
showObsoleteConfirm: false,
|
||||
obsoleteCandidate: null,
|
||||
obsoleteTitle: "obsolete-title",
|
||||
obsoleteMessage: "obsolete-message",
|
||||
obsoleteConfirmLabel: "confirm-obsolete",
|
||||
obsoleteCancelLabel: "cancel-obsolete",
|
||||
onConfirmMarkObsolete: vi.fn(),
|
||||
onCancelMarkObsolete: vi.fn(),
|
||||
showDeleteConfirm: false,
|
||||
deleteCandidate: null,
|
||||
deleteTitle: "delete-title",
|
||||
deleteMessage: "delete-message",
|
||||
deleteConfirmLabel: "confirm-delete",
|
||||
deleteCancelLabel: "cancel-delete",
|
||||
onConfirmDelete: vi.fn(),
|
||||
onCancelDelete: vi.fn(),
|
||||
showEditModal: false,
|
||||
lightboxImage: null,
|
||||
onCloseLightbox: vi.fn(),
|
||||
showReportModal: false,
|
||||
onCloseReportModal: vi.fn(),
|
||||
medications: [baseMedication],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("MedicationDialogs", () => {
|
||||
it("always renders mobile edit container and report modal state", () => {
|
||||
render(<MedicationDialogs {...createProps({ showReportModal: true })} />);
|
||||
|
||||
expect(screen.getByTestId("mobile-edit")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("report-modal")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders nested unsaved confirm when edit modal is open and triggers callbacks", () => {
|
||||
const onConfirmClose = vi.fn();
|
||||
const onCancelClose = vi.fn();
|
||||
|
||||
render(
|
||||
<MedicationDialogs
|
||||
{...createProps({
|
||||
showUnsavedConfirm: true,
|
||||
showEditModal: true,
|
||||
onConfirmClose,
|
||||
onCancelClose,
|
||||
})}
|
||||
/>
|
||||
);
|
||||
|
||||
const modal = screen.getByTestId("confirm-unsaved-title");
|
||||
expect(modal).toHaveAttribute("data-overlay-class", "nested-confirm");
|
||||
|
||||
fireEvent.click(screen.getByText("confirm-unsaved"));
|
||||
fireEvent.click(screen.getByText("cancel-unsaved"));
|
||||
|
||||
expect(onConfirmClose).toHaveBeenCalledTimes(1);
|
||||
expect(onCancelClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("renders obsolete and delete confirms only when a candidate exists", () => {
|
||||
const { rerender } = render(
|
||||
<MedicationDialogs
|
||||
{...createProps({
|
||||
showObsoleteConfirm: true,
|
||||
showDeleteConfirm: true,
|
||||
obsoleteCandidate: baseMedication,
|
||||
deleteCandidate: baseMedication,
|
||||
})}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId("confirm-obsolete-title")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("confirm-delete-title")).toBeInTheDocument();
|
||||
|
||||
rerender(
|
||||
<MedicationDialogs
|
||||
{...createProps({
|
||||
showObsoleteConfirm: true,
|
||||
showDeleteConfirm: true,
|
||||
obsoleteCandidate: null,
|
||||
deleteCandidate: null,
|
||||
})}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.queryByTestId("confirm-obsolete-title")).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("confirm-delete-title")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders lightbox when image data is present", () => {
|
||||
render(
|
||||
<MedicationDialogs
|
||||
{...createProps({
|
||||
lightboxImage: { src: "https://example.com/a.jpg", alt: "Medication image" },
|
||||
})}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId("lightbox")).toHaveTextContent("https://example.com/a.jpg|Medication image");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,71 @@
|
||||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import type { FormEvent } from "react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { MedicationEditCoordinator } from "../../components/medications/MedicationEditCoordinator";
|
||||
|
||||
vi.mock("react-i18next", () => ({
|
||||
useTranslation: () => ({ t: (key: string) => key }),
|
||||
}));
|
||||
|
||||
describe("MedicationEditCoordinator", () => {
|
||||
it("renders new-entry header and closes via back action", () => {
|
||||
const onBack = vi.fn();
|
||||
const onSubmit = vi.fn((event: FormEvent<HTMLFormElement>) => event.preventDefault());
|
||||
|
||||
render(
|
||||
<MedicationEditCoordinator
|
||||
viewMode="grid"
|
||||
editingId={null}
|
||||
readOnlyView={false}
|
||||
onBack={onBack}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<div>content</div>
|
||||
</MedicationEditCoordinator>
|
||||
);
|
||||
|
||||
expect(screen.getByText("form.newEntry")).toBeInTheDocument();
|
||||
expect(document.querySelector(".edit-sidebar.open")).not.toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "<- common.back" }));
|
||||
expect(onBack).toHaveBeenCalledTimes(1);
|
||||
|
||||
fireEvent.submit(document.querySelector("form") as HTMLFormElement);
|
||||
expect(onSubmit).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("renders edit header for editable and read-only flows", () => {
|
||||
const onSubmit = vi.fn((event: FormEvent<HTMLFormElement>) => event.preventDefault());
|
||||
|
||||
const { rerender } = render(
|
||||
<MedicationEditCoordinator
|
||||
viewMode="form"
|
||||
editingId={42}
|
||||
readOnlyView={false}
|
||||
selectedMedicationName="Aspirin"
|
||||
onBack={vi.fn()}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<div>content</div>
|
||||
</MedicationEditCoordinator>
|
||||
);
|
||||
|
||||
expect(document.querySelector(".edit-sidebar.open")).toBeInTheDocument();
|
||||
expect(screen.getByRole("heading", { name: "form.editEntry: Aspirin" })).toBeInTheDocument();
|
||||
|
||||
rerender(
|
||||
<MedicationEditCoordinator
|
||||
viewMode="form"
|
||||
editingId={42}
|
||||
readOnlyView={true}
|
||||
selectedMedicationName="Aspirin"
|
||||
onBack={vi.fn()}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<div>content</div>
|
||||
</MedicationEditCoordinator>
|
||||
);
|
||||
|
||||
expect(screen.getByRole("heading", { name: "form.viewEntry: Aspirin" })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user