pediatric-ai-scribe-v3/e2e/tests/calculators-react.spec.js
Daniel 18550e263f feat(client): port age→weight + BSA + dose + GCS; shared/clinical module
First batch of real calculator ports, routed through a new
shared/clinical/calculators.ts module that both the server tree and
the React client can import. Kept strictly to the simplest, closed-form
formulas — tables (Rosner BP, Fenton LMS, AAP 2022 bili, Bhutani, CDC
BMI) stay in the vanilla viewer until per-table vector files land.

shared/clinical/calculators.ts
  Verbatim ports from public/js/calc-math.js:
    • parseAgeMonths / formatAgeMonths — legacy age-string parser
    • estimateWeightFromAgeMonths — APLS (Luscombe 2007) + Best Guess
      (Tinning 2007) weight-for-age. Cross-checked line-by-line against
      calc-math.js:14-39; identical branches, formulas, and roundTo
      behavior.
    • calculateMostellerBsa — sqrt(h·w/3600), Mosteller 1987.
    • calculateWeightBasedDose — generic mg/kg with optional max cap
      and mg/mL → mL conversion.
    • calculateGcs — 1-15 sum with 8/12 severity thresholds.

shared/clinical/calculators.test.ts
  Vitest unit coverage for each helper. Numeric assertions match the
  legacy function outputs (3y APLS → 14 kg; 20 kg, 110 cm → 0.782 m²;
  15 kg × 100 mg/kg capped at 500 mg, etc.). For these closed-form
  formulas the hand-verified expected values are equivalent to a
  vanilla-captured vector file — table-driven calculators still need
  a JSON fixture before they port.

client/src/pages/Bedside.tsx
  Top-level age-to-weight estimator now runs in React
  (BedsideWeightEstimator). Formula dropdown switches between APLS
  and Best Guess live; weight field accepts a manual override. The
  15 clinical dosing sub-modules still fall through to the legacy
  viewer via LegacyPanel.

client/src/pages/Calculators.tsx
  BSA, Weight-Based Dosing, and GCS panels render real React forms
  backed by the shared helpers. PILLS gain a `ported` flag so the
  four covered panels (bsa, dose, gcs, + the already-shipped pills)
  swap out of the legacy fallback while the others remain linked
  out. Result blocks carry data-testid hooks for parity tests.

Config + dep hygiene picked up along the way
  • client/tsconfig.app.json: @shared/* path alias, exclude test files
    from the React tsc pass.
  • tsconfig.json: exclude **/*.test.ts from the backend tsc pass.
  • package.json: declare google-auth-library and jszip explicitly —
    both were already required() in src/utils/ttsGoogle.ts and
    src/routes/learningAI.ts but missing from dependencies, which
    would break a clean `npm install`. Also adds engines: node >=20
    and convenience verify / verify:full scripts.
  • knip.json: quiet now-expected ignoreDependencies / ignoreBinaries
    entries for marp-cli, tiptap, cap, etc.
  • .gitignore: ignore the .codex CLI marker.

e2e/tests/bedside-react.spec.js
  Adds the age→weight parity test: 3y APLS → 14 kg, 3y Best Guess →
  16 kg, weight field mirrors the estimator output.

e2e/tests/calculators-react.spec.js
  Adds BSA (20 kg, 110 cm → 0.782 m²), dose cap (15 kg × 100 mg/kg,
  max 500 mg → 500 mg capped), and GCS (15 → 10 when motor drops
  to 1) parity tests.

Client tsc -b + backend tsc --noEmit + vite build all clean.
Bundle 476.48 kB / 135.44 kB gzipped.

Co-Authored-By: Codex + vendor model Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 01:11:58 +02:00

68 lines
2.9 KiB
JavaScript

// ============================================================
// CALCULATORS (React port) — sub-nav shell smoke test.
//
// Pure shell test until test vectors + formula ports land in
// dedicated follow-up commits.
// ============================================================
const { test, expect, E2E_BASE } = require('../fixtures');
const EXPECTED_PILLS = [
'bp', 'bmi', 'growth', 'bili', 'vitals',
'bsa', 'dose', 'resus', 'gcs', 'equipment',
];
async function openReactCalc(page) {
await page.goto(E2E_BASE + '/app/calculators');
await page.waitForSelector('[data-testid="calc-subnav"]', { timeout: 15000 });
}
test.describe('React Calculators — sub-nav shell', () => {
test('all 10 pills render in expected order', async ({ authedPage: _, page }) => {
await openReactCalc(page);
for (const id of EXPECTED_PILLS) {
await expect(page.locator('[data-testid="calc-pill-' + id + '"]')).toBeVisible();
}
});
test('clicking a pill switches the active panel', async ({ authedPage: _, page }) => {
await openReactCalc(page);
await expect(page.locator('[data-testid="calc-panel-bp"]')).toBeVisible();
await page.click('[data-testid="calc-pill-bili"]');
await expect(page.locator('[data-testid="calc-panel-bili"]')).toBeVisible();
});
test('each panel carries a legacy-viewer link', async ({ authedPage: _, page }) => {
await openReactCalc(page);
await expect(page.getByText('Open in legacy viewer').first()).toBeVisible();
});
test('BSA calculator runs in React', async ({ authedPage: _, page }) => {
await openReactCalc(page);
await page.click('[data-testid="calc-pill-bsa"]');
await page.fill('#react-bsa-weight', '20');
await page.fill('#react-bsa-height', '110');
await page.click('[data-testid="calc-bsa-calculate"]');
await expect(page.locator('[data-testid="calc-bsa-result"]')).toContainText('0.782');
});
test('weight-based dose calculator caps max dose', async ({ authedPage: _, page }) => {
await openReactCalc(page);
await page.click('[data-testid="calc-pill-dose"]');
await page.fill('#react-dose-weight', '15');
await page.fill('#react-dose-per-kg', '100');
await page.fill('#react-dose-max', '500');
await page.click('[data-testid="calc-dose-calculate"]');
await expect(page.locator('[data-testid="calc-dose-result"]')).toContainText('500.0 mg');
await expect(page.locator('[data-testid="calc-dose-result"]')).toContainText('Capped');
});
test('GCS calculator updates score from selected components', async ({ authedPage: _, page }) => {
await openReactCalc(page);
await page.click('[data-testid="calc-pill-gcs"]');
await expect(page.locator('[data-testid="calc-gcs-result"]')).toContainText('GCS: 15/15');
await page.selectOption('#react-gcs-motor', '1');
await expect(page.locator('[data-testid="calc-gcs-result"]')).toContainText('GCS: 10/15');
});
});