pediatric-ai-scribe-v3/e2e/tests/ui-state-persistence.spec.js
Daniel f4140d45c4 feat(ui-state): persist sub-pill selections across reload + sign-out
Tab-level choice (ped_last_tab) already survived sign-out/in via
localStorage, but sub-pill and sub-tab selections inside a loaded tab
lived only in memory — they reset to defaults after a reload or
browser restart. Now the following are persisted under the ped_ui/
namespace:

  - Calculators nav pill (BP / BMI / GCS / …)
  - Bedside sub-pill (neonatal / airway / …)
  - Well Visit sub-tab (byvisit / milestones / shadess / note)
  - Physical Exam Guide age group + system

Implementation:
- Added public/js/ui-state.js — a ~30-line window.UIState wrapper
  around localStorage with a ped_ui/ prefix and try/catch around both
  read and write (Safari private mode + quota errors silently no-op).
- Each tab's click handler now also calls UIState.set; each tab's
  init path calls UIState.get and replays the saved value through
  the same function a click would call — so there is exactly one
  code path for "show this selection", whether it came from the user
  or from a restore. For Bedside, the restore additionally listens
  for tabChanged so the lazy-loaded HTML is guaranteed to exist by
  the time we re-activate the pill.

Tests:
- e2e/tests/ui-state-persistence.spec.js — 5 specs × 2 viewports =
  10 tests. Each clicks the feature, reloads the page, and asserts
  the same pill / subtab / dropdown value is still active. Catches
  any future regression in the persistence wiring.
- e2e/tests/soap-hospital-workflow.spec.js — fills SOAP transcript,
  generates via mocked AI, clears, opens/closes load popovers; also
  smoke-tests the Hospital Course save-bar.

Suite: 250 passed / 0 failed (+ 20 over the last run).
2026-04-23 18:58:38 +02:00

89 lines
4.2 KiB
JavaScript

// ============================================================
// UI STATE PERSISTENCE — sub-pill / sub-tab choices survive a
// full page reload (simulating sign-out / sign-in or browser
// restart). Guards against regressions in the ui-state.js +
// localStorage wiring.
// ============================================================
const { test, expect, E2E_BASE } = require('../fixtures');
async function gotoHome(page) {
await page.goto(E2E_BASE + '/');
await page.waitForSelector('button.tab-btn', { timeout: 15000 });
}
async function openDesktopTab(page, name) {
const vp = page.viewportSize();
if (vp && vp.width <= 768) {
await page.click('#btn-menu-toggle').catch(() => {});
}
await page.click(`button.tab-btn[data-tab="${name}"]`);
await page.waitForFunction((t) => {
const el = document.getElementById(t + '-tab');
return el && el.classList.contains('active') && el.innerHTML.trim().length > 100;
}, name, { timeout: 15000 });
}
test.describe('UI state survives page reload', () => {
test('ped_last_tab → user lands on last-active tab after reload', async ({ authedPage: _, page }) => {
await gotoHome(page);
await openDesktopTab(page, 'calculators');
// Reload (keeps cookie, clears _componentCache + in-memory DOM state)
await page.reload();
await page.waitForSelector('button.tab-btn', { timeout: 15000 });
await expect.poll(async () => {
return await page.locator('#calculators-tab.active').count();
}, { timeout: 5000 }).toBe(1);
});
test('calculators nav pill persists across reload', async ({ authedPage: _, page }) => {
await gotoHome(page);
await openDesktopTab(page, 'calculators');
// Switch to GCS
await page.click('button.calc-nav-pill[data-calc="gcs"]');
await expect(page.locator('button.calc-nav-pill[data-calc="gcs"].active')).toBeVisible();
await page.reload();
await page.waitForSelector('button.calc-nav-pill[data-calc="gcs"]', { timeout: 15000 });
// Same pill should be active after reload
await expect(page.locator('button.calc-nav-pill[data-calc="gcs"].active')).toBeVisible({ timeout: 5000 });
// And the corresponding panel should be un-hidden
await expect(page.locator('#calc-gcs')).not.toHaveClass(/hidden/);
});
test('bedside sub-pill persists across reload', async ({ authedPage: _, page }) => {
await gotoHome(page);
await openDesktopTab(page, 'bedside');
await page.click('button.calc-pill[data-em="anaphylaxis"]');
await expect(page.locator('button.calc-pill[data-em="anaphylaxis"].active')).toBeVisible();
await page.reload();
await page.waitForSelector('button.calc-pill[data-em="anaphylaxis"]', { timeout: 15000 });
await expect(page.locator('button.calc-pill[data-em="anaphylaxis"].active')).toBeVisible({ timeout: 5000 });
// The anaphylaxis em-section should be the visible one
await expect.poll(async () => {
return await page.locator('#em-anaphylaxis').evaluate(el => el.style.display);
}, { timeout: 5000 }).not.toBe('none');
});
test('well-visit sub-tab persists across reload', async ({ authedPage: _, page }) => {
await gotoHome(page);
await openDesktopTab(page, 'wellvisit');
await page.click('button.wv-subtab-btn[data-subtab="milestones"]');
await expect(page.locator('#wv-panel-milestones')).not.toHaveClass(/hidden/);
await page.reload();
await page.waitForSelector('button.wv-subtab-btn[data-subtab="milestones"]', { timeout: 15000 });
await expect(page.locator('#wv-panel-milestones')).not.toHaveClass(/hidden/, { timeout: 5000 });
});
test('PE guide age group + system persist across reload', async ({ authedPage: _, page }) => {
await gotoHome(page);
await openDesktopTab(page, 'peguide');
await page.selectOption('#pe-age-group', 'adolescent');
await page.click('button[data-pesystem="neuro"]');
await expect(page.locator('button[data-pesystem="neuro"].active')).toBeVisible();
await page.reload();
await page.waitForSelector('#pe-age-group', { timeout: 15000 });
await expect(page.locator('#pe-age-group')).toHaveValue('adolescent', { timeout: 5000 });
await expect(page.locator('button[data-pesystem="neuro"].active')).toBeVisible({ timeout: 5000 });
});
});