// @vitest-environment happy-dom import { describe, it, expect, vi, beforeEach } from 'vitest'; vi.mock('../public/modules/ws.js', () => ({ send: vi.fn(() => true) })); import { agents, repos, orphans, setActiveSession, setView } from '../public/modules/state.js'; import { showJobBoard, hideJobBoard } from '../public/modules/jobs.js'; import { handleFileDiff, closeDiffViewer } from '../public/modules/explorer.js'; import { switchToSession } from '../public/modules/terminal.js'; // Below 700px style.css shows one panel, keyed on body[data-view]; these are // the state transitions behind it. The nav itself is plain markup + CSS. const NAV_HTML = ` `; const PANELS_HTML = `
`; const current = () => [...document.querySelectorAll('.mobile-nav button')] .filter(b => b.getAttribute('aria-current') === 'true').map(b => b.dataset.view); beforeEach(() => { document.body.innerHTML = NAV_HTML + PANELS_HTML; document.body.dataset.view = 'office'; agents.clear(); repos.clear(); orphans.clear(); setActiveSession(null); closeDiffViewer(); hideJobBoard(); }); describe('setView', () => { it('sets body[data-view] and moves aria-current to the matching nav button', () => { setView('files'); expect(document.body.dataset.view).toBe('files'); expect(current()).toEqual(['files']); setView('terminal'); expect(document.body.dataset.view).toBe('terminal'); expect(current()).toEqual(['terminal']); }); it('is harmless without the nav in the DOM (desktop-only fixtures)', () => { document.body.innerHTML = PANELS_HTML; expect(() => setView('terminal')).not.toThrow(); expect(document.body.dataset.view).toBe('terminal'); }); it('an unknown view still lands on the body and clears every nav button', () => { setView('nope'); expect(document.body.dataset.view).toBe('nope'); expect(current()).toEqual([]); }); }); describe('panel changes pick the phone view', () => { it('opening a file diff lands on the office panel, closing it returns to files', () => { setView('files'); handleFileDiff({ sessionId: 's1', filePath: 'src/a.js', diff: '@@ -1 +1 @@\n-a\n+b' }); expect(document.body.dataset.view).toBe('office'); expect(current()).toEqual(['office']); expect(document.getElementById('diff-viewer').style.display).toBe('flex'); closeDiffViewer(); expect(document.body.dataset.view).toBe('files'); closeDiffViewer(); // already closed: a state-change repaint must not move the view setView('office'); closeDiffViewer(); expect(document.body.dataset.view).toBe('office'); }); // Spawns, exits and the reconnect replay all switch sessions or open the // board; only a tap on the office canvas moves a phone to the terminal. it('switching sessions or opening the board in the background leaves the view alone', () => { const termEl = document.createElement('div'); document.body.appendChild(termEl); agents.set('s1', { name: 'Viper', state: 'IDLE', termEl, term: { scrollToBottom() {}, focus() {} } }); setView('files'); switchToSession('s1'); expect(termEl.style.display).toBe('block'); expect(document.body.dataset.view).toBe('files'); showJobBoard(); expect(document.getElementById('job-board').style.display).toBe('flex'); expect(document.body.dataset.view).toBe('files'); }); });