Bedside is now a top-level tab instead of a sub-pill inside Calculators — it's the highest-traffic emergency reference in the app and deserves a one-click entry. Same DOM structure and JS modules; only the container moved. Sidebar reorg: - Notes: Hospital Course, Chart Review, SOAP Note, Well Visit, Sick Visit (Well Visit + Sick Visit relocated from the old "Pediatric" group — they're clinical note workflows, not pure reference tools). - Section rename: "Pediatric" → "Clinical Tools". The section now holds Vaccine Schedule, Catch-Up Schedule, Physical Exam Guide, Bedside, Calculators, Pagers & Extensions, Learning Hub, Content Manager — mix of reference tables + active calculators + utilities, none strictly pediatric. "Clinical Tools" reads naturally for the combined set. Layout changes: - calculators.html: dropped 480 lines of bedside panel + the Bedside nav-pill. The shared age→weight estimator moved with it. - bedside.html: new component file, contains the full bedside card + the age→weight estimator prepended. - index.html: added bedside-tab section, sidebar restructured. - e2e-harness.html: renders calculators + bedside side-by-side (not the old calc-tab→bedside-pill dance) so the bedside smoke suite still works without auth. e2e-bootstrap fetches both with a cache-buster. - bedside-smoke.spec.js: removed the now-obsolete calc-nav-pill click from each test. Tab persistence is unchanged — ped_last_tab already survives sign-out, and the lazy-load cache keeps sub-pill state across navigation within a session. Persistence across browser restart for sub-pills is a separate follow-up.
148 lines
6.6 KiB
JavaScript
148 lines
6.6 KiB
JavaScript
// Bedside module smoke tests.
|
|
// The harness renders the Calculators + Bedside components side-by-side, so
|
|
// each test selects the target sub-pill (if any) and asserts a known string
|
|
// is rendered. Bedside was promoted to a top-level tab, so there is no longer
|
|
// a calc-nav-pill[data-calc="bedside"] — the panel is always visible in the
|
|
// harness and always the full tab in the live app.
|
|
|
|
const { test, expect } = require('@playwright/test');
|
|
|
|
async function openCalculators(page) {
|
|
await page.goto('/e2e-harness.html');
|
|
await page.waitForFunction(() => window.__harnessReady === true);
|
|
// Wait for the bedside component to finish injecting — #bedside-age is the
|
|
// first input in the shared age→weight estimator at the top of the tab.
|
|
await page.waitForSelector('#bedside-age');
|
|
}
|
|
|
|
async function openBedside(page, subPill) {
|
|
await openCalculators(page);
|
|
if (subPill) {
|
|
await page.click(`button.calc-pill[data-em="${subPill}"]`);
|
|
}
|
|
}
|
|
|
|
test.describe('Bedside — top-level', () => {
|
|
test('Calculators tab shows age-weight estimator', async ({ page }) => {
|
|
await openCalculators(page);
|
|
await expect(page.locator('#bedside-age')).toBeVisible();
|
|
await expect(page.locator('#bedside-formula')).toBeVisible();
|
|
await expect(page.locator('#bedside-weight')).toBeVisible();
|
|
});
|
|
|
|
test('Age → Weight: typing 3y auto-fills weight (APLS)', async ({ page }) => {
|
|
await openCalculators(page);
|
|
await page.fill('#bedside-age', '3y');
|
|
await expect(page.locator('#bedside-weight')).toHaveValue('14');
|
|
await expect(page.locator('#bedside-estimate-note')).toContainText('APLS');
|
|
});
|
|
|
|
test('Formula switch to Best Guess updates weight', async ({ page }) => {
|
|
await openCalculators(page);
|
|
await page.fill('#bedside-age', '3y');
|
|
await page.selectOption('#bedside-formula', 'bestguess');
|
|
await expect(page.locator('#bedside-weight')).toHaveValue('16'); // 2 * (3+5) = 16
|
|
await expect(page.locator('#bedside-estimate-note')).toContainText('Best Guess');
|
|
});
|
|
|
|
test('Clear button resets the estimator', async ({ page }) => {
|
|
await openCalculators(page);
|
|
await page.fill('#bedside-age', '5y');
|
|
await page.click('#btn-bedside-clear');
|
|
await expect(page.locator('#bedside-age')).toHaveValue('');
|
|
await expect(page.locator('#bedside-weight')).toHaveValue('');
|
|
});
|
|
});
|
|
|
|
test.describe('Bedside — every sub-pill renders', () => {
|
|
const subPills = [
|
|
{ key: 'neonatal', expected: /Neonatal Assessment/i },
|
|
{ key: 'airway', expected: /Airway Management/i },
|
|
{ key: 'cardiac', expected: /Cardiac Arrest/i },
|
|
{ key: 'respiratory', expected: /Respiratory Management/i },
|
|
{ key: 'ventilation', expected: /Oxygen & Ventilation/i },
|
|
{ key: 'seizure', expected: /Status Epilepticus/i },
|
|
{ key: 'sepsis', expected: /Sepsis & Fever/i },
|
|
{ key: 'anaphylaxis', expected: /Anaphylaxis/i },
|
|
{ key: 'sedation', expected: /Procedural Sedation/i },
|
|
{ key: 'agitation', expected: /Acute Agitation/i },
|
|
{ key: 'antiemetics', expected: /Antiemetics/i },
|
|
{ key: 'antimicrobials', expected: /Empiric Antimicrobials/i },
|
|
{ key: 'burns', expected: /Burn Management/i },
|
|
{ key: 'toxicology', expected: /Toxicology/i },
|
|
{ key: 'trauma', expected: /Trauma/i },
|
|
];
|
|
|
|
for (const { key, expected } of subPills) {
|
|
test(`${key} sub-pill shows header`, async ({ page }) => {
|
|
await openBedside(page, key);
|
|
await expect(page.locator(`#em-${key}`)).toContainText(expected);
|
|
});
|
|
}
|
|
});
|
|
|
|
test.describe('Bedside — dose calculators fire', () => {
|
|
test('Status Epilepticus: Show Pathway renders timeline with weight', async ({ page }) => {
|
|
await openBedside(page, 'seizure');
|
|
await page.fill('#seizure-weight', '20');
|
|
await page.click('#btn-seizure-calc');
|
|
await expect(page.locator('#seizure-result')).toContainText(/20 kg/);
|
|
await expect(page.locator('#seizure-result')).toContainText(/Lorazepam/);
|
|
await expect(page.locator('#seizure-result')).toContainText(/0\.1 mg\/kg/); // per-kg visible
|
|
});
|
|
|
|
test('Sepsis: Show Approach renders Phoenix criteria + first-hour bundle', async ({ page }) => {
|
|
await openBedside(page, 'sepsis');
|
|
await page.fill('#sepsis-weight', '25');
|
|
await page.click('#btn-sepsis-show');
|
|
await expect(page.locator('#sepsis-result')).toContainText(/Phoenix/);
|
|
await expect(page.locator('#sepsis-result')).toContainText(/first-hour/i);
|
|
});
|
|
|
|
test('Anaphylaxis: Calculate Doses shows weight-based epinephrine', async ({ page }) => {
|
|
await openBedside(page, 'anaphylaxis');
|
|
await page.fill('#anaph-weight', '25');
|
|
await page.click('#btn-anaph-calc');
|
|
await expect(page.locator('#anaph-result')).toContainText(/Epinephrine/);
|
|
await expect(page.locator('#anaph-result')).toContainText(/0\.25 mg/); // 25*0.01
|
|
});
|
|
|
|
test('Burns: body-parts calculator + Parkland', async ({ page }) => {
|
|
await openBedside(page, 'burns');
|
|
await page.fill('#burn-weight', '20');
|
|
await page.fill('input[data-burn-region="head"]', '50'); // 50% of 13 (young) = 6.5
|
|
await page.fill('input[data-burn-region="ant_trunk"]', '100'); // 100% of 13 = 13
|
|
await page.click('#btn-burn-calc');
|
|
await expect(page.locator('#burn-result')).toContainText(/Parkland/);
|
|
await expect(page.locator('#burn-result')).toContainText(/20 kg/);
|
|
});
|
|
|
|
test('Airway: Calculate renders RSI drugs with per-kg', async ({ page }) => {
|
|
await openBedside(page, 'airway');
|
|
await page.fill('#airway-weight', '20');
|
|
await page.fill('#airway-age', '5');
|
|
await page.click('#btn-airway-calc');
|
|
await expect(page.locator('#airway-result')).toContainText(/Ketamine/);
|
|
await expect(page.locator('#airway-result')).toContainText(/mg\/kg/); // per-kg visible
|
|
await expect(page.locator('#airway-result')).toContainText(/ETT/);
|
|
});
|
|
});
|
|
|
|
test.describe('Bedside — interactive widgets', () => {
|
|
test('Lightbox: View pathway image button opens modal', async ({ page }) => {
|
|
await openBedside(page, 'seizure');
|
|
await page.click('button[data-img-src="/img/epilepsy_eiic_pathway.png"]');
|
|
await expect(page.locator('#img-lightbox')).toBeVisible();
|
|
await page.click('#img-lightbox-close');
|
|
await expect(page.locator('#img-lightbox')).toBeHidden();
|
|
});
|
|
|
|
test('Ventilation: Show Reference renders pressure-time SVG', async ({ page }) => {
|
|
await openBedside(page, 'ventilation');
|
|
await page.fill('#vent-weight', '20');
|
|
await page.click('#btn-vent-show');
|
|
await expect(page.locator('#vent-result svg')).toBeVisible();
|
|
await expect(page.locator('#vent-result')).toContainText(/Target SpO2/);
|
|
await expect(page.locator('#vent-result')).toContainText(/PEEP/);
|
|
});
|
|
});
|