/** * Tests for mobile-navigation.js — pure helpers on the MobileNavigation class. * * checkViewport drives whether the mobile bottom-nav is shown at all, * and isCurrentPage decides which tab is highlighted. Both are pure * reads of `window.innerWidth` / `window.location.pathname`, so they * are testable without touching the rest of the class wiring. * * The module auto-instantiates a singleton when imported (`initMobileNav` * runs immediately because happy-dom reports `document.readyState === 'complete'`). * That constructor path renders HTML that references the global `URLS` * config, so we stub it before the dynamic import. */ let MobileNavigation; beforeAll(async () => { // Minimal URLS stub — only the PAGES keys the constructor's bottom-nav // and sheet-menu templates dereference. globalThis.URLS = { PAGES: { HOME: '/', HISTORY: '/history/', LIBRARY: '/library', NEWS: '/news', NEWS_SUBSCRIPTIONS: '/news/subscriptions', COLLECTIONS: '/library/collections', METRICS: '/metrics', BENCHMARK: '/benchmark', BENCHMARK_RESULTS: '/benchmark/results', EMBEDDING_SETTINGS: '/settings/embedding', SETTINGS: '/settings', }, }; await import('@js/mobile-navigation.js'); MobileNavigation = window.MobileNavigation; }); describe('MobileNavigation.checkViewport', () => { let originalInnerWidth; beforeEach(() => { originalInnerWidth = window.innerWidth; }); afterEach(() => { Object.defineProperty(window, 'innerWidth', { value: originalInnerWidth, configurable: true, writable: true, }); }); function setInnerWidth(px) { Object.defineProperty(window, 'innerWidth', { value: px, configurable: true, writable: true, }); } it('returns true and sets state.isVisible=true below the breakpoint', () => { const nav = new MobileNavigation(); setInnerWidth(500); expect(nav.checkViewport()).toBe(true); expect(nav.state.isVisible).toBe(true); }); it('returns false and sets state.isVisible=false at the breakpoint (768)', () => { // Uses strict < (not <=) — 768px is tablet, sidebar should be visible. const nav = new MobileNavigation(); setInnerWidth(768); expect(nav.checkViewport()).toBe(false); expect(nav.state.isVisible).toBe(false); }); it('returns false above the breakpoint', () => { const nav = new MobileNavigation(); setInnerWidth(1280); expect(nav.checkViewport()).toBe(false); expect(nav.state.isVisible).toBe(false); }); it('respects a custom breakpoint passed via options', () => { const nav = new MobileNavigation({ breakpoint: 1024 }); setInnerWidth(900); expect(nav.checkViewport()).toBe(true); setInnerWidth(1024); expect(nav.checkViewport()).toBe(false); }); }); describe('MobileNavigation.isCurrentPage', () => { let originalLocation; beforeAll(() => { originalLocation = window.location; }); afterAll(() => { Object.defineProperty(window, 'location', { value: originalLocation, configurable: true, }); }); function setPath(pathname) { Object.defineProperty(window, 'location', { value: { pathname }, configurable: true, writable: true, }); } function nav() { return new MobileNavigation(); } it('treats the research tab as active only on the bare root path', () => { setPath('/'); expect(nav().isCurrentPage({ id: 'research' })).toBe(true); setPath('/something'); expect(nav().isCurrentPage({ id: 'research' })).toBe(false); }); it('matches the history tab on /history and any sub-path', () => { setPath('/history'); expect(nav().isCurrentPage({ id: 'history' })).toBe(true); setPath('/history/abc-123'); expect(nav().isCurrentPage({ id: 'history' })).toBe(true); setPath('/'); expect(nav().isCurrentPage({ id: 'history' })).toBe(false); }); it('matches the library tab on /library prefix', () => { setPath('/library'); expect(nav().isCurrentPage({ id: 'library' })).toBe(true); setPath('/library/collections/42'); expect(nav().isCurrentPage({ id: 'library' })).toBe(true); }); it('matches the metrics tab on /metrics prefix', () => { setPath('/metrics'); expect(nav().isCurrentPage({ id: 'metrics' })).toBe(true); setPath('/metrics/dashboard'); expect(nav().isCurrentPage({ id: 'metrics' })).toBe(true); }); it('matches the news tab on /news prefix', () => { setPath('/news'); expect(nav().isCurrentPage({ id: 'news' })).toBe(true); setPath('/news/subscriptions'); expect(nav().isCurrentPage({ id: 'news' })).toBe(true); }); it('returns false for unknown tab ids', () => { setPath('/'); expect(nav().isCurrentPage({ id: 'unknown' })).toBe(false); }); it('does not cross-match: /library should not light up history or news', () => { setPath('/library'); expect(nav().isCurrentPage({ id: 'history' })).toBe(false); expect(nav().isCurrentPage({ id: 'news' })).toBe(false); }); });