9e8a6315e7
* fix: keep topical stock non-depleting in planner and reports * test: stabilize e2e selectors for updated medication semantics * fix(backend): add missing planner translation keys
194 lines
6.9 KiB
TypeScript
194 lines
6.9 KiB
TypeScript
import { authFile, createMedicationViaAPI, deleteAllMedicationsViaAPI, expect, navigateTo, test } from "./fixtures";
|
|
|
|
/**
|
|
* Medication Lifecycle Integration Tests
|
|
*
|
|
* End-to-end workflows that verify changes propagate across pages:
|
|
* create → verify on medications → check in planner → check in schedule → edit → delete
|
|
*/
|
|
test.describe("Medication lifecycle", () => {
|
|
test.use({ storageState: authFile });
|
|
test.describe.configure({ timeout: 90000 });
|
|
|
|
const MED_NAME = "Lifecycle TestMed";
|
|
const MED_EDITED = "Lifecycle Edited";
|
|
|
|
test.beforeAll(async () => {
|
|
await deleteAllMedicationsViaAPI();
|
|
});
|
|
|
|
test.afterAll(async () => {
|
|
await deleteAllMedicationsViaAPI();
|
|
});
|
|
|
|
test("create medication via API and verify it appears on all pages", async ({ page }) => {
|
|
const todayMorning = (() => {
|
|
const d = new Date();
|
|
d.setHours(8, 0, 0, 0);
|
|
const pad = (n: number) => n.toString().padStart(2, "0");
|
|
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
})();
|
|
|
|
// Step 1: Create medication
|
|
const created = await createMedicationViaAPI({
|
|
name: MED_NAME,
|
|
packageType: "blister",
|
|
packCount: 1,
|
|
blistersPerPack: 2,
|
|
pillsPerBlister: 10,
|
|
looseTablets: 0,
|
|
intakes: [{ usage: 1, every: 1, start: todayMorning, intakeRemindersEnabled: false }],
|
|
});
|
|
expect(created.id).toBeTruthy();
|
|
|
|
// Step 2: Verify on medications page
|
|
await navigateTo(page, "/medications");
|
|
await expect(page.getByText(MED_NAME).first()).toBeVisible({ timeout: 10000 });
|
|
|
|
// Step 3: Verify in planner
|
|
await navigateTo(page, "/planner");
|
|
await page.waitForLoadState("networkidle");
|
|
await page.locator('form.planner button[type="submit"]').click();
|
|
await expect(page.locator(".table")).toBeVisible({ timeout: 15000 });
|
|
await expect(page.locator(".table").getByText(MED_NAME)).toBeVisible();
|
|
|
|
// Step 4: Verify in schedule
|
|
await navigateTo(page, "/schedule");
|
|
await expect(page.getByText(MED_NAME).first()).toBeVisible({ timeout: 10000 });
|
|
});
|
|
|
|
test("edit medication name via UI and verify update propagates", async ({ page }) => {
|
|
await deleteAllMedicationsViaAPI();
|
|
|
|
const todayMorning = (() => {
|
|
const d = new Date();
|
|
d.setHours(8, 0, 0, 0);
|
|
const pad = (n: number) => n.toString().padStart(2, "0");
|
|
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
})();
|
|
|
|
// Create a fresh medication for this test
|
|
await createMedicationViaAPI({
|
|
name: MED_NAME,
|
|
packageType: "blister",
|
|
packCount: 1,
|
|
blistersPerPack: 2,
|
|
pillsPerBlister: 10,
|
|
looseTablets: 0,
|
|
intakes: [{ usage: 1, every: 1, start: todayMorning, intakeRemindersEnabled: false }],
|
|
});
|
|
|
|
// Navigate to medications page
|
|
await navigateTo(page, "/medications");
|
|
await expect(page.getByText(MED_NAME).first()).toBeVisible({ timeout: 10000 });
|
|
|
|
// Open edit view from medication row actions
|
|
const medRow = page.locator(".med-row").filter({ hasText: MED_NAME });
|
|
await expect(medRow.first()).toBeVisible({ timeout: 10000 });
|
|
await medRow.first().locator("button.info").click();
|
|
await expect(page.locator("h2").filter({ hasText: /(Edit(:| (entry|medication))|form\.editEntry)/i })).toBeVisible({
|
|
timeout: 5000,
|
|
});
|
|
|
|
// Update the name
|
|
const form = page.locator("form.form-grid:visible").first();
|
|
const nameInput = form.getByLabel(/(Commercial Name|Name|form\.name)/i).first();
|
|
await nameInput.fill(MED_EDITED);
|
|
|
|
// Save
|
|
const submitButton = form.locator('button[type="submit"]').first();
|
|
await expect(submitButton).toBeEnabled({ timeout: 5000 });
|
|
await submitButton.click();
|
|
|
|
// Wait for modal to close or save to complete
|
|
await page.waitForLoadState("networkidle");
|
|
|
|
// Verify edited name appears on medications page
|
|
await navigateTo(page, "/medications");
|
|
await expect(page.getByText(MED_EDITED).first()).toBeVisible({ timeout: 10000 });
|
|
// Old name should no longer appear
|
|
await expect(page.locator(".med-row").filter({ hasText: MED_NAME })).toHaveCount(0, { timeout: 5000 });
|
|
});
|
|
|
|
test("delete medication via API and verify it disappears from all pages", async ({ page }) => {
|
|
const todayMorning = (() => {
|
|
const d = new Date();
|
|
d.setHours(8, 0, 0, 0);
|
|
const pad = (n: number) => n.toString().padStart(2, "0");
|
|
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
})();
|
|
|
|
// Create and then delete
|
|
await deleteAllMedicationsViaAPI();
|
|
await createMedicationViaAPI({
|
|
name: MED_NAME,
|
|
packageType: "blister",
|
|
packCount: 1,
|
|
blistersPerPack: 1,
|
|
pillsPerBlister: 5,
|
|
looseTablets: 0,
|
|
intakes: [{ usage: 1, every: 1, start: todayMorning, intakeRemindersEnabled: false }],
|
|
});
|
|
|
|
// Verify it exists first
|
|
await navigateTo(page, "/medications");
|
|
await expect(page.getByText(MED_NAME)).toBeVisible({ timeout: 10000 });
|
|
|
|
// Delete via API
|
|
await deleteAllMedicationsViaAPI();
|
|
|
|
// Verify gone from medications page
|
|
await navigateTo(page, "/medications");
|
|
await expect(page.getByText(MED_NAME)).not.toBeVisible({ timeout: 5000 });
|
|
|
|
// Verify planner shows no results for this med
|
|
await navigateTo(page, "/planner");
|
|
await page.waitForLoadState("networkidle");
|
|
await page.locator('form.planner button[type="submit"]').click();
|
|
// Either no table or table without the medication name
|
|
const table = page.locator(".table");
|
|
const tableVisible = await table.isVisible().catch(() => false);
|
|
if (tableVisible) {
|
|
await expect(table.getByText(MED_NAME)).not.toBeVisible({ timeout: 3000 });
|
|
}
|
|
});
|
|
|
|
test("medication with multiple intakes shows all schedule entries", async ({ page }) => {
|
|
const todayMorning = (() => {
|
|
const d = new Date();
|
|
d.setHours(8, 0, 0, 0);
|
|
const pad = (n: number) => n.toString().padStart(2, "0");
|
|
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
})();
|
|
|
|
const todayEvening = (() => {
|
|
const d = new Date();
|
|
d.setHours(20, 0, 0, 0);
|
|
const pad = (n: number) => n.toString().padStart(2, "0");
|
|
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
})();
|
|
|
|
await deleteAllMedicationsViaAPI();
|
|
await createMedicationViaAPI({
|
|
name: "MultiIntake Med",
|
|
packageType: "blister",
|
|
packCount: 1,
|
|
blistersPerPack: 2,
|
|
pillsPerBlister: 10,
|
|
looseTablets: 0,
|
|
intakes: [
|
|
{ usage: 1, every: 1, start: todayMorning, intakeRemindersEnabled: false },
|
|
{ usage: 2, every: 1, start: todayEvening, intakeRemindersEnabled: false },
|
|
],
|
|
});
|
|
|
|
// Verify schedule shows this medication
|
|
await navigateTo(page, "/schedule");
|
|
await expect(page.getByText("MultiIntake Med").first()).toBeVisible({ timeout: 10000 });
|
|
|
|
// The medication should appear at least twice (morning + evening)
|
|
const medEntries = page.getByText("MultiIntake Med");
|
|
expect(await medEntries.count()).toBeGreaterThanOrEqual(2);
|
|
});
|
|
});
|