pediatric-ai-scribe-v3/e2e/tests/bedside-react.spec.js
Daniel 95a5a5e40e feat(client): port Bedside Anaphylaxis + Cardiac + Seizure panels
Replaces the legacy-viewer fallback for three of the fifteen Bedside
sub-modules with real React implementations. Weight-based dosing now
runs locally for the most time-critical emergencies — the other 12
modules (neonatal, airway, respiratory, ventilation, sepsis, sedation,
agitation, antiemetics, antimicrobials, burns, toxicology, trauma)
still fall through to LegacyPanel.

shared/clinical/calculators.ts
  New formatDose(weightKg, perKg, max?, unit = 'mg') helper that
  mirrors the vanilla S.dStr exactly — same rounding, same cap rule,
  same label format. Returns { value, unit, capped, perKg, max,
  label } so consumers can render rich UI without re-parsing a HTML
  string. Pure function; unit tests in the existing vitest suite
  still pass.

client/src/pages/BedsidePanels.tsx (new)
  Three panels keyed to the Bedside pill id:
    • AnaphylaxisPanel — STEP 1 epi IM callout + full 9-row dosing
      table (fluids, diphenhydramine, ranitidine, dex, methylpred,
      refractory epi gtt, glucagon). Drug per-kg + max values ported
      verbatim from ANAPH_FALLBACK in the vanilla module.
    • CardiacPanel — PALS dosing with 6 sub-views (general,
      asystole/PEA, bradycardia, SVT, VF/pulseless VT, stable VT).
      Every drug row carries the same mg/kg + max values as the
      vanilla cardiac.js (epi, amio, lido, atropine, adenosine ×2,
      bicarb, CaCl/CaGluc, Mg, D10, defib energies). Concentration
      disclaimer + AHA citation preserved.
    • SeizurePanel — full status-epilepticus timeline (0 min →
      40 min refractory) with the vanilla SEIZURE_FALLBACK drug
      table (loraz / midaz / diaz for benzo; levetiracetam /
      fosphenytoin / valproate / phenobarb for 2nd-line; midaz
      pentobarb propofol ketamine infusions for refractory). Key
      points + citation preserved.

  REAL_BEDSIDE_PANELS set + renderBedsideRealPanel(pillId) export so
  Bedside.tsx can dispatch by id without import sprawl.

client/src/pages/Bedside.tsx
  When the active pill is in REAL_BEDSIDE_PANELS, render the real
  panel; otherwise fall back to LegacyPanel. No other changes — the
  sub-nav, age→weight estimator, and 12 legacy-linked modules stay
  exactly as they were.

e2e/tests/bedside-react.spec.js
  Three new parity tests (in addition to the existing shell checks):
    • Anaphylaxis: 20 kg → 0.2 mg epi IM; 70 kg → 0.5 mg (capped).
    • Cardiac: 25 kg → 0.25 mg epi IV in general view and asystole.
    • Seizure: 10 kg → D10W 20-50 mL, Lorazepam 1 mg.

Client tsc -b + vite build clean. Bundle 609 kB / 173 kB gz (+28 kB
for three full panels; vite's 500 kB chunk warning noted for later
code-splitting, not blocking).
2026-04-24 01:27:29 +02:00

85 lines
4.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ============================================================
// BEDSIDE (React port) — sub-nav shell smoke test.
//
// First-commit scope: the 15 sub-pills render in the expected order
// and selecting a pill reveals a panel with a legacy-viewer link.
// Actual clinical dosing panels (neonatal through trauma) port in
// dedicated follow-up commits alongside the calculators.
// ============================================================
const { test, expect, E2E_BASE } = require('../fixtures');
const EXPECTED_PILLS = [
'neonatal', 'airway', 'cardiac', 'respiratory', 'ventilation',
'seizure', 'sepsis', 'anaphylaxis', 'sedation', 'agitation',
'antiemetics', 'antimicrobials', 'burns', 'toxicology', 'trauma',
];
async function openReactBedside(page) {
await page.goto(E2E_BASE + '/app/bedside');
await page.waitForSelector('[data-testid="bedside-subnav"]', { timeout: 15000 });
}
test.describe('React Bedside — sub-nav shell', () => {
test('all 15 sub-pills render in the expected order', async ({ authedPage: _, page }) => {
await openReactBedside(page);
for (const id of EXPECTED_PILLS) {
await expect(page.locator('[data-testid="bedside-pill-' + id + '"]')).toBeVisible();
}
});
test('clicking a pill switches the active panel', async ({ authedPage: _, page }) => {
await openReactBedside(page);
await expect(page.locator('[data-testid="bedside-panel-neonatal"]')).toBeVisible();
await page.click('[data-testid="bedside-pill-airway"]');
await expect(page.locator('[data-testid="bedside-panel-airway"]')).toBeVisible();
});
test('panel carries a legacy-viewer link', async ({ authedPage: _, page }) => {
await openReactBedside(page);
await expect(page.getByText('Open in legacy viewer').first()).toBeVisible();
});
test('age-to-weight estimator runs in React', async ({ authedPage: _, page }) => {
await openReactBedside(page);
await page.fill('[data-testid="bedside-age-input"]', '3y');
await expect(page.locator('[data-testid="bedside-estimate-result"]')).toContainText('14 kg');
await expect(page.locator('[data-testid="bedside-weight-input"]')).toHaveValue('14');
await page.selectOption('[data-testid="bedside-formula-select"]', 'bestguess');
await expect(page.locator('[data-testid="bedside-estimate-result"]')).toContainText('16 kg');
await expect(page.locator('[data-testid="bedside-weight-input"]')).toHaveValue('16');
});
test('anaphylaxis panel computes IM epinephrine dose by weight', async ({ authedPage: _, page }) => {
await openReactBedside(page);
await page.click('[data-testid="bedside-pill-anaphylaxis"]');
await page.fill('[data-testid="anaph-weight"]', '20');
// 20 kg × 0.01 mg/kg = 0.2 mg epi IM (below the 0.5 mg cap).
await expect(page.locator('[data-testid="bedside-panel-anaphylaxis"]')).toContainText('0.2 mg');
// Uncapped — ensure 0.5 kicks in past the cap threshold (wt ≥ 50 kg).
await page.fill('[data-testid="anaph-weight"]', '70');
await expect(page.locator('[data-testid="bedside-panel-anaphylaxis"]')).toContainText('0.5 mg');
});
test('cardiac arrest PALS general table renders weight-scaled epinephrine', async ({ authedPage: _, page }) => {
await openReactBedside(page);
await page.click('[data-testid="bedside-pill-cardiac"]');
await page.fill('[data-testid="cardiac-weight"]', '25');
// Default view is general. Epi IV = 0.01 mg/kg × 25 = 0.25 mg.
await expect(page.locator('[data-testid="bedside-panel-cardiac"]')).toContainText('0.25 mg');
// Switching to Asystole keeps the same dose logic.
await page.click('[data-testid="cardiac-view-asystole"]');
await expect(page.locator('[data-testid="bedside-panel-cardiac"]')).toContainText('0.25 mg');
});
test('seizure pathway computes D10W bolus range by weight', async ({ authedPage: _, page }) => {
await openReactBedside(page);
await page.click('[data-testid="bedside-pill-seizure"]');
await page.fill('[data-testid="seizure-weight"]', '10');
// 10 kg × 2-5 mL/kg = 20-50 mL D10W
await expect(page.locator('[data-testid="bedside-panel-seizure"]')).toContainText('20-50 mL');
// Lorazepam 0.1 mg/kg × 10 = 1 mg
await expect(page.locator('[data-testid="bedside-panel-seizure"]')).toContainText('1 mg');
});
});