Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
41 changes: 33 additions & 8 deletions care.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,32 @@ const resolveApiUrl = (): string => {
return env.REACT_CARE_API_URL ?? "";
};

/**
* E2E test-only runtime overrides for patient-registration config.
*
* Build-time `import.meta.env.REACT_*` values are inlined and frozen into the
* bundle, so Playwright specs (which run against the production `preview`
* build) cannot flip them per test file. To make per-file control possible, a
* spec sets `window.__CARE_E2E_CONFIG__` via `page.addInitScript(...)` — which
* runs before this module loads — and the values below are consulted first.
*
* This seam is gated behind the `REACT_ENABLE_E2E_CONFIG_OVERRIDES` build flag
* (default off), so it is completely inert in normal production builds. The
* affected flags only relax client-side form validation; the backend
* independently enforces required fields, so this is not a security boundary.
*/
interface E2EConfigOverrides {
minGeoOrganizationLevelsRequired?: number;
minimalPatientRegistration?: boolean;
}

const e2eConfigOverrides: E2EConfigOverrides =
booleanFromString(env.REACT_ENABLE_E2E_CONFIG_OVERRIDES, false) &&
typeof window !== "undefined"
? ((window as unknown as { __CARE_E2E_CONFIG__?: E2EConfigOverrides })
.__CARE_E2E_CONFIG__ ?? {})
: {};

Comment on lines +53 to +119

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

E2EConfigOverrides and CareE2EConfig duplicate the same override contract across app and test code. Both types describe the identical window.__CARE_E2E_CONFIG__ shape by hand, with no import relationship, so the two can silently drift out of sync as fields are added or changed.

  • care.config.ts#L53-L78: export E2EConfigOverrides (or an equivalent shared type) so it can be reused, instead of keeping it private to this module.
  • tests/helper/careConfig.ts#L10-L13: import the exported type from care.config.ts (or a shared location both files can reach) instead of redeclaring CareE2EConfig independently; verify the tests' tsconfig/build setup permits importing from src before doing so.
📍 Affects 2 files
  • care.config.ts#L53-L78 (this comment)
  • tests/helper/careConfig.ts#L10-L13
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@care.config.ts` around lines 53 - 78, Export the E2EConfigOverrides type used
by the window override in care.config.ts. In tests/helper/careConfig.ts, remove
the duplicate CareE2EConfig declaration and import the exported type from
care.config.ts, confirming the test TypeScript/build configuration supports that
src import; update both affected sites so the app and tests share one override
contract.

const careConfig = {
apiUrl: resolveApiUrl(),
sbomBaseUrl: env.REACT_SBOM_BASE_URL || "https://sbom.ohc.network",
Expand Down Expand Up @@ -79,8 +105,7 @@ const careConfig = {
: undefined),

defaultDischargeDisposition: env.REACT_DEFAULT_DISCHARGE_DISPOSITION as
| EncounterDischargeDisposition
| undefined,
EncounterDischargeDisposition | undefined,

mapFallbackUrlTemplate:
env.REACT_MAPS_FALLBACK_URL_TEMPLATE ||
Expand Down Expand Up @@ -274,19 +299,19 @@ const careConfig = {
* If not set, all levels are required.
*/
minGeoOrganizationLevelsRequired:
env.REACT_PATIENT_REG_MIN_GEO_ORG_LEVELS_REQUIRED
e2eConfigOverrides.minGeoOrganizationLevelsRequired ??
(env.REACT_PATIENT_REG_MIN_GEO_ORG_LEVELS_REQUIRED
? Math.max(
parseInt(env.REACT_PATIENT_REG_MIN_GEO_ORG_LEVELS_REQUIRED, 10),
1,
)
: undefined,
: undefined),
Comment thread
greptile-apps[bot] marked this conversation as resolved.

defaultGeoOrganization: env.REACT_PATIENT_REGISTRATION_DEFAULT_GEO_ORG,

minimalPatientRegistration: booleanFromString(
env.REACT_ENABLE_MINIMAL_PATIENT_REGISTRATION,
false,
),
minimalPatientRegistration:
e2eConfigOverrides.minimalPatientRegistration ??
booleanFromString(env.REACT_ENABLE_MINIMAL_PATIENT_REGISTRATION, false),

globalPatientEditAccessEnabled: booleanFromString(
env.REACT_PATIENT_GLOBAL_EDIT_ACCESS_ENABLED,
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"build:react": "cross-env NODE_ENV=production vite build",
"supported-browsers": "node ./scripts/generate-supported-browsers.mjs",
"build": "npm run build:meta && npm run supported-browsers && npm run build:react",
"build:e2e": "cross-env REACT_ENABLE_E2E_CONFIG_OVERRIDES=true npm run build",
"postinstall": "tsx scripts/install-platform-deps.ts && tsx scripts/generate-headers.ts",
"test": "snyk test",
"test:unit": "node --import tsx --test \"plugins/**/*.test.ts\"",
Expand Down
1 change: 1 addition & 0 deletions src/vite-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ interface ImportMetaEnv {
readonly REACT_PATIENT_GLOBAL_EDIT_ACCESS_ENABLED?: string;
readonly REACT_DISABLE_PATIENT_LOGIN?: string;
readonly REACT_ENABLE_QUESTIONNAIRE_DRAFT?: string;
readonly REACT_ENABLE_E2E_CONFIG_OVERRIDES?: string;
readonly REACT_CUSTOM_REMOTE_I18N_URL?: string;
readonly REACT_ENABLE_AUTO_INVOICE_AFTER_DISPENSE?: string;
readonly REACT_ENABLE_TOKEN_GENERATION_IN_PATIENT_HOME?: string;
Expand Down
107 changes: 107 additions & 0 deletions tests/facility/patient/patientRegistration.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { faker } from "@faker-js/faker";
import { expect, Page, test } from "@playwright/test";
import { applyCareConfig } from "tests/helper/careConfig";
import { getFacilityId } from "tests/support/facilityId";

// Use the authenticated state
Expand Down Expand Up @@ -150,6 +151,27 @@ async function submitRegistration(page: Page) {
});
}

/**
* Verifies the newly registered patient is shown on the patients home card.
* After registration the app navigates to `/patients/home`, where
* `PatientHoverCard` renders the patient name (heading) and an
* "{age} Y, {gender}" line.
*/
async function verifyPatientCard(
page: Page,
data: { name: string; gender: string },
) {
await test.step("Verify patient details in the card", async () => {
await page.waitForURL("**/patients/home**");
await expect(page.getByRole("heading", { name: data.name })).toBeVisible({
timeout: 15000,
});
Comment thread
Copilot marked this conversation as resolved.
Outdated
await expect(
page.getByText(new RegExp(`Y,\\s*${data.gender}`, "i")).first(),
).toBeVisible();
Comment on lines +168 to +181
});
}

/**
* Fills all standard required fields and submits.
* Useful for tests where registration is setup, not the focus.
Expand Down Expand Up @@ -412,3 +434,88 @@ test.describe("DOB timezone validation", () => {
).not.toBeVisible();
});
});

/**
* Demonstrates controlling patient-registration config flags per test file,
* without editing `.env.local`.
*
* `applyCareConfig` uses `page.addInitScript`, which runs before any app script
* loads, so `care.config.ts` picks up the values for this spec only (see the
* E2E override seam in that file).
*
* Requires the preview build to be built with `REACT_ENABLE_E2E_CONFIG_OVERRIDES=true`
* so the seam is active — e.g. `npm run build:e2e`.
*/
test.describe("Patient Registration config overrides (per-file)", () => {
// The override is set on each test's own isolated browser context, so it
// applies to that test only and Playwright discards it automatically when the
// context is torn down — no manual teardown needed.
test("minimal registration lets a patient be registered without address or geo org", async ({
page,
}) => {
const facilityId = getFacilityId();
await applyCareConfig(page, { minimalPatientRegistration: true });
await page.goto(`/facility/${facilityId}/patient/create`);

const patientData = generatePatientData();
await fillBasicInfo(page, patientData);
await fillDateOfBirth(page, patientData.dateOfBirth);
await selectBloodGroup(page, patientData.bloodGroup);

// No address / geo org filled — minimal mode makes them optional.
await submitRegistration(page);
await verifyPatientCard(page, patientData);
});

test("lowering required geo org levels to 1 accepts a single selected level", async ({
page,
}) => {
const facilityId = getFacilityId();
await applyCareConfig(page, { minGeoOrganizationLevelsRequired: 1 });
await page.goto(`/facility/${facilityId}/patient/create`);

const patientData = generatePatientData();
await fillBasicInfo(page, patientData);
await fillDateOfBirth(page, patientData.dateOfBirth);
await selectBloodGroup(page, patientData.bloodGroup);

await test.step("Open additional details and select only the first geo org level", async () => {
const additionalDetailsSection = page.getByRole("button", {
name: "Additional Details",
});
const additionalDetailsSectionText =
await additionalDetailsSection.textContent();

if (additionalDetailsSectionText?.toLowerCase().includes("optional")) {
await additionalDetailsSection.click();
}

await page
.getByRole("textbox", { name: "Address" })
.fill(patientData.address);
await page
.getByRole("spinbutton", { name: "PIN Code" })
Comment on lines +506 to +517

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Fragile section-expansion guard in the geo-org test

The test checks additionalDetailsSectionText?.toLowerCase().includes("optional") to decide whether to click and expand the "Additional Details" section, mirroring the same pattern already in fillAdditionalDetails. If the section is collapsed but its label text doesn't contain "optional" (e.g. the label changes or the section is required in a different locale), the if block is skipped, the section remains closed, and the subsequent getByRole("textbox", { name: "Address" }) fill will fail silently or throw. Checking aria-expanded on the trigger button would be a more robust signal of the section's open/closed state.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

.fill(patientData.pincode);

await page
.getByRole("button", { name: /register patient/i })
.scrollIntoViewIfNeeded();

const geoRegion = page.getByRole("region", {
name: "Additional Details",
});
const firstCombobox = geoRegion.getByRole("combobox").first();
await firstCombobox.waitFor({ state: "visible" });
await firstCombobox.click();

const option = page.getByRole("option").first();
await option.waitFor({ state: "visible" });
await option.click();
});

// With only 1 level required, a single selection must NOT raise the
// geo-org validation error and registration should succeed.
await submitRegistration(page);
await verifyPatientCard(page, patientData);
});
Comment thread
greptile-apps[bot] marked this conversation as resolved.
});
37 changes: 37 additions & 0 deletions tests/helper/careConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { type Page } from "@playwright/test";

/**
* Patient-registration config flags that can be overridden per test file.
*
* Mirrors the `E2EConfigOverrides` seam in `care.config.ts`, which reads
* `window.__CARE_E2E_CONFIG__` when the app is built with
* `REACT_ENABLE_E2E_CONFIG_OVERRIDES=true` (use `npm run build:e2e`).
*/
export type CareE2EConfig = {
minimalPatientRegistration?: boolean;
minGeoOrganizationLevelsRequired?: number;
};
Comment on lines +10 to +13

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Shape duplicates E2EConfigOverrides in care.config.ts.

Both types describe the same override contract but are maintained independently. See the consolidated comment for the shared fix across both files.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/helper/careConfig.ts` around lines 10 - 13, Remove the duplicate
CareE2EConfig shape and reuse the existing E2EConfigOverrides type from
care.config.ts for the same configuration contract. Update dependent references
in the careConfig test helper to import and use E2EConfigOverrides, keeping the
existing optional fields and behavior unchanged.


/**
* Overrides CARE runtime config for the current page/context, without editing
* `.env.local`.
*
* Build-time `import.meta.env.REACT_*` values are inlined and frozen into the
* bundle, so per-file control is only possible via a runtime `window` seam.
* `page.addInitScript` runs before any app script loads, so setting
* `window.__CARE_E2E_CONFIG__` here lets `care.config.ts` pick up the values
* for this spec only.
*
* Must be called BEFORE navigating (`page.goto`).
*
* @example
* await applyCareConfig(page, { minimalPatientRegistration: true });
* await page.goto(`/facility/${facilityId}/patient/create`);
*/
export async function applyCareConfig(page: Page, config: CareE2EConfig) {
await page.addInitScript((cfg) => {
(
window as unknown as { __CARE_E2E_CONFIG__?: CareE2EConfig }
).__CARE_E2E_CONFIG__ = cfg;
}, config);
}
Loading