Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions tests/facility/patient/encounter/encounterFormHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { faker } from "@faker-js/faker";
import { expect, type Page } from "@playwright/test";
import { ENCOUNTER_CLASSES } from "tests/facility/patient/encounter/encounterClasses";
import { getFacilityId } from "tests/support/facilityId";
import { getPatientId } from "tests/support/patientId";

export async function openCreateEncounterDialog(page: Page) {
const facilityId = getFacilityId();
const patientId = getPatientId();

await page.goto(`/facility/${facilityId}/patient/${patientId}`);
await page.getByRole("link", { name: "Patient Home" }).click();

await expect(
page.getByRole("button", { name: "Create Encounter" }),
).toBeVisible();
await page.getByRole("button", { name: "Create Encounter" }).click();
}

export async function selectRandomEncounterClass(page: Page) {
const randomClass = faker.helpers.arrayElement(ENCOUNTER_CLASSES);
await page.getByRole("button", { name: randomClass }).click();
}

export async function selectStatusInCreateDialog(page: Page, status: string) {
await page.getByRole("combobox", { name: "Status" }).click();
await page.getByRole("option", { name: status, exact: true }).click();
}

export async function openCalendarAndGetNextMonthButton(page: Page) {
await page
.locator('[data-slot="form-item"]')
.filter({ hasText: "Date and Time" })
.locator('[data-slot="popover-trigger"]')
.click();

const nextMonthButton = page.getByRole("button", {
name: "Go to the Next Month",
});
await expect(nextMonthButton).toBeVisible();
return nextMonthButton;
}

export function getFutureDateButtonFromCalendar(page: Page) {
return page
.getByRole("gridcell")
.filter({ hasText: /^15$/ })
.getByRole("button");
}

export function getEncounterCreateDialog(page: Page) {
return page.getByRole("dialog", { name: "Initiate Patient Encounter" });
}
Original file line number Diff line number Diff line change
@@ -1,57 +1,17 @@
import { faker } from "@faker-js/faker";
import { expect, test, type Page } from "@playwright/test";
import { ENCOUNTER_CLASSES } from "tests/facility/patient/encounter/encounterClasses";
import { getFacilityId } from "tests/support/facilityId";
import { getPatientId } from "tests/support/patientId";
import { expect, test } from "@playwright/test";
import {
getEncounterCreateDialog as getEncounterDialog,
getFutureDateButtonFromCalendar,
openCalendarAndGetNextMonthButton,
openCreateEncounterDialog,
selectRandomEncounterClass,
} from "tests/facility/patient/encounter/encounterFormHelpers";

test.use({ storageState: "tests/.auth/user.json" });

async function openEncounterForm(page: Page) {
const facilityId = getFacilityId();
const patientId = getPatientId();

await page.goto(`/facility/${facilityId}/patient/${patientId}`);
await page.getByRole("link", { name: "Patient Home" }).click();

await expect(
page.getByRole("button", { name: "Create Encounter" }),
).toBeVisible();
await page.getByRole("button", { name: "Create Encounter" }).click();
}

async function selectRandomEncounterClass(page: Page) {
const randomClass = faker.helpers.arrayElement(ENCOUNTER_CLASSES);
await page.getByRole("button", { name: randomClass }).click();
}

async function openCalendarAndGetNextMonthButton(page: Page) {
await page
.locator('[data-slot="form-item"]')
.filter({ hasText: "Date and Time" })
.locator('[data-slot="popover-trigger"]')
.click();

const nextMonthButton = page.getByRole("button", {
name: "Go to the Next Month",
});
await expect(nextMonthButton).toBeVisible();
return nextMonthButton;
}

function getFutureDateButtonFromCalendar(page: Page) {
return page
.getByRole("gridcell")
.filter({ hasText: /^15$/ })
.getByRole("button");
}

function getEncounterDialog(page: Page) {
return page.getByRole("dialog", { name: "Initiate Patient Encounter" });
}

test.describe("Encounter Future Date Restriction", () => {
test.beforeEach(async ({ page }) => {
await openEncounterForm(page);
await openCreateEncounterDialog(page);
});

test("should disable future dates when status is In Progress", async ({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import { expect, test, type Page } from "@playwright/test";
import {
getEncounterCreateDialog,
getFutureDateButtonFromCalendar,
openCalendarAndGetNextMonthButton,
openCreateEncounterDialog,
selectRandomEncounterClass,
selectStatusInCreateDialog,
} from "tests/facility/patient/encounter/encounterFormHelpers";

test.use({ storageState: "tests/.auth/user.json" });

const VALIDATION_ERROR_TEXT =
"period: Value error, Start Date cannot be greater than End Date";

async function selectFutureDateInCalendar(page: Page) {
const nextMonthButton = await openCalendarAndGetNextMonthButton(page);
await expect(nextMonthButton).toBeEnabled();
await nextMonthButton.click();

const futureDayButton = getFutureDateButtonFromCalendar(page);
await expect(futureDayButton).toBeEnabled();
await futureDayButton.click();
}
Comment thread
greptile-apps[bot] marked this conversation as resolved.

async function createPlannedEncounterWithFutureDate(page: Page) {
await openCreateEncounterDialog(page);
await selectRandomEncounterClass(page);
await selectStatusInCreateDialog(page, "Planned");
await selectFutureDateInCalendar(page);

const dialog = getEncounterCreateDialog(page);
await dialog.getByRole("button", { name: /^Create Encounter/ }).click();

// Submit navigates to the encounter detail page.
await page.waitForURL(/\/encounter\/[^/]+/);
await expect(
page.getByRole("button", { name: "Encounter Actions" }),
).toBeVisible();
}

// Update-encounter form uses a bare <Label> + <SelectTrigger> (no FormLabel/htmlFor),
// so the combobox has no programmatic accessible name. Anchor on the label and walk
// to its immediate parent (the space-y-2 wrapper that contains exactly one combobox).
function encounterStatusCombobox(page: Page) {
return page
.locator('label[data-slot="label"]')
.filter({ hasText: /^Encounter Status$/ })
.locator("xpath=..")
.getByRole("combobox");
}
Comment thread
nihal467 marked this conversation as resolved.

async function openEncounterUpdateForm(page: Page) {
await page.getByRole("tab", { name: "Details" }).click();
await page.getByRole("link", { name: "Update Encounter" }).click();
await expect(encounterStatusCombobox(page)).toBeVisible();
}

async function changeStatus(page: Page, status: string) {
await encounterStatusCombobox(page).click();
await page.getByRole("option", { name: status, exact: true }).click();
}

async function submitQuestionnaire(page: Page) {
await page.getByRole("button", { name: "Submit", exact: true }).click();
}

async function expectSubmissionSuccess(page: Page) {
await expect(
page.getByText("Questionnaire submitted successfully"),
).toBeVisible();
}

async function expectSubmissionBlockedByPeriodError(page: Page) {
await expect(page.getByText("Failed to submit questionnaire")).toBeVisible();
await expect(page.getByText(VALIDATION_ERROR_TEXT)).toBeVisible();
}

// Ensures the encounter ends in a terminal state so the patient's 5-live-encounter
// limit isn't reached across repeated runs.
async function cancelEncounterFromCurrentForm(page: Page) {
await changeStatus(page, "Cancelled");
await submitQuestionnaire(page);
await expectSubmissionSuccess(page);
}
Comment thread
greptile-apps[bot] marked this conversation as resolved.

test.describe("Planned Encounter Status Transition", () => {
test.beforeEach(async ({ page }) => {
await createPlannedEncounterWithFutureDate(page);
});

test("allows transition from Planned to In Progress", async ({ page }) => {
await test.step("Open update form", async () => {
await openEncounterUpdateForm(page);
});

await test.step("Transition Planned to In Progress", async () => {
await changeStatus(page, "In Progress");
await submitQuestionnaire(page);
await expectSubmissionSuccess(page);
});

await test.step("Cleanup: cancel encounter", async () => {
await openEncounterUpdateForm(page);
await cancelEncounterFromCurrentForm(page);
});
});

test("allows transition from Planned to On Hold", async ({ page }) => {
await test.step("Open update form", async () => {
await openEncounterUpdateForm(page);
});

await test.step("Transition Planned to On Hold", async () => {
await changeStatus(page, "On Hold");
await submitQuestionnaire(page);
await expectSubmissionSuccess(page);
});

await test.step("Cleanup: cancel encounter", async () => {
await openEncounterUpdateForm(page);
await cancelEncounterFromCurrentForm(page);
});
});

test("allows transition from Planned to Cancelled", async ({ page }) => {
await test.step("Open update form", async () => {
await openEncounterUpdateForm(page);
});

await test.step("Transition Planned to Cancelled", async () => {
await cancelEncounterFromCurrentForm(page);
});
});

test("allows transition from Planned to Entered in error", async ({
page,
}) => {
await test.step("Open update form", async () => {
await openEncounterUpdateForm(page);
});

await test.step("Transition Planned to Entered in error", async () => {
await changeStatus(page, "Entered in error");
await submitQuestionnaire(page);
await expectSubmissionSuccess(page);
});
});

test("blocks transition from Planned to Completed with period error", async ({
page,
}) => {
await test.step("Open update form", async () => {
await openEncounterUpdateForm(page);
});

await test.step("Attempt Planned to Completed", async () => {
await changeStatus(page, "Completed");
await submitQuestionnaire(page);
await expectSubmissionBlockedByPeriodError(page);
});

await test.step("Cleanup: cancel encounter (still Planned on backend)", async () => {
// Backend rejected the transition, so the encounter is still Planned.
// Selecting Cancelled clears the auto-set end date (see EncounterQuestion
// useEffect handling for future-start Cancelled/EnteredInError), so this
// submit succeeds and moves the encounter to a terminal state.
await cancelEncounterFromCurrentForm(page);
});
});

test("blocks transition from Planned to Discontinued with period error", async ({
page,
}) => {
await test.step("Open update form", async () => {
await openEncounterUpdateForm(page);
});

await test.step("Attempt Planned to Discontinued", async () => {
await changeStatus(page, "Discontinued");
await submitQuestionnaire(page);
await expectSubmissionBlockedByPeriodError(page);
});

await test.step("Cleanup: cancel encounter (still Planned on backend)", async () => {
await cancelEncounterFromCurrentForm(page);
});
});
});
Loading