/** * The phone's primary navigation (plan 016 B2). * * Three of these are about the thing that makes a second nav dangerous: * it has to agree with the first one. `bottom-nav` emits the same * bubbling, composed `navigate` event `app-sidebar` does, and reads * which tab is lit from `activeViewStore` — the shell's one statement * of where the user is — so it follows a navigation from anywhere: a * card, a detail view, the drawer's own sidebar, or the back gesture. * * That last one is why the source is the store and not the `navigate` * event these tests used to dispatch. `popstate` dispatches no * `navigate` (index.ts calls `handleNavigate` directly), so a tab bar * listening for the event looked right until the user pressed back — * #72. */ import { describe, expect, it, beforeEach } from 'vitest'; import '@components/bottom-nav/bottom-nav'; import { activeViewStore } from '@store/active-view-store'; import type { BottomNav } from '@components/bottom-nav/bottom-nav'; import { fixture, shadow, shadowAll, update } from '@test/support/render'; import { resetHarness } from '@test/support/harness'; type Nav = BottomNav; const tabs = (el: HTMLElement) => shadowAll(el, 'nav button'); /** The testids of whatever the bar says is the current page. */ const current = (el: HTMLElement) => tabs(el) .filter((b) => b.getAttribute('aria-current') === 'page') .map((b) => b.dataset.testid); /** Resolve on one occurrence of an event, or reject loudly on time. */ const once = (el: Element, name: string, timeoutMs = 2000) => new Promise((resolve, reject) => { const timer = setTimeout( () => reject(new Error(`${name} never fired`)), timeoutMs, ); el.addEventListener(name, () => { clearTimeout(timer); resolve(); }, { once: true }); }); describe('bottom-nav', () => { beforeEach(() => { resetHarness(); }); it('offers the four phone destinations and a way to the rest', async () => { const el = await fixture