import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { mount } from '@vue/test-utils'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { defineComponent, h, ref } from 'vue'; import DriveControls from '@/components/DriveControls.vue'; import { useDashcam } from '@/composables/useDashcam'; import { readDashcam } from '@/journey/dashcam-feed'; import { validateProductionInputs } from '@/content/schemas'; import { render } from '@/entry-server'; import type { MotionPolicy } from '@/journey/motion-policy'; const fixture = (name: string): unknown => JSON.parse(readFileSync(resolve(process.cwd(), 'tests/fixtures', name), 'utf8')); const policy: MotionPolicy = { allowSceneRequest: true, allowAutomaticVideo: true, maximumTier: 'high', reason: 'full', }; describe('dashcam server HTML', () => { it('renders nothing of the dashcam on the server', async () => { const html = await render('/', validateProductionInputs( fixture('portfolio-content.valid.json'), fixture('asset-manifest.valid.json'), fixture('site-config.preview.json'), new URL('file:///prepared-assets/'), )); expect(html).not.toMatch(/dashcam/iu); }); }); describe('dashcam button', () => { const mountControls = (dashcam?: boolean) => mount(DriveControls, { props: { policy, maximumQuality: 'high', sceneStatus: 'running', audioState: { kind: 'idle' }, ...(dashcam === undefined ? {} : { dashcam }), }, }); it('is absent unless the shell offers it', () => { expect(mountControls().find('.drive-controls__dashcam').exists()).toBe(false); expect(mountControls(false).find('.drive-controls__dashcam').exists()).toBe(false); }); it('opens on click and prefetches the frame on hover and focus', async () => { const wrapper = mountControls(true); const button = wrapper.get('.drive-controls__dashcam'); expect(button.attributes('aria-label')).toBe('Dashcam'); expect(button.attributes('aria-keyshortcuts')).toBe('c'); expect(button.find('svg').exists()).toBe(true); await button.trigger('pointerenter'); await button.trigger('focus'); await button.trigger('click'); expect(wrapper.emitted('dashcamPrefetch')).toHaveLength(2); expect(wrapper.emitted('dashcam')).toHaveLength(1); }); }); describe('useDashcam', () => { let stage: HTMLElement; const allowed = ref(true); const running = ref(true); const host = () => mount(defineComponent({ setup() { const dashcam = useDashcam({ allowed, sceneRunning: running, drift: () => true }); return () => h('button', { class: 'drive-controls__dashcam', onClick: dashcam.show }); }, }), { attachTo: document.body }); const press = (key: string, init: KeyboardEventInit = {}): KeyboardEvent => { const event = new KeyboardEvent('keydown', { key, cancelable: true, bubbles: true, ...init }); window.dispatchEvent(event); return event; }; beforeEach(() => { vi.useFakeTimers(); allowed.value = true; running.value = true; stage = document.createElement('div'); stage.className = 'reading-stage'; stage.dataset.readingStage = 'live'; document.body.append(stage); }); afterEach(() => { stage.remove(); vi.useRealTimers(); delete document.documentElement.dataset.dashcam; }); it('opens and closes on c, then lifts the rise state after 400 ms', () => { const wrapper = host(); expect(press('c').defaultPrevented).toBe(true); expect(document.documentElement.dataset.dashcam).toBe('on'); expect(readDashcam()).toMatchObject({ open: true, drift: true }); expect(press('C', { shiftKey: true }).defaultPrevented).toBe(false); press('c'); expect(document.documentElement.dataset.dashcam).toBe('rise'); expect(readDashcam().open).toBe(false); vi.advanceTimersByTime(400); expect(document.documentElement.dataset.dashcam).toBeUndefined(); wrapper.unmount(); }); it('closes on Escape', () => { const wrapper = host(); press('c'); expect(press('Escape').defaultPrevented).toBe(true); expect(readDashcam().open).toBe(false); wrapper.unmount(); }); it('ignores c with modifiers or inside an editable field', () => { const wrapper = host(); expect(press('c', { ctrlKey: true }).defaultPrevented).toBe(false); const input = document.createElement('input'); document.body.append(input); const event = new KeyboardEvent('keydown', { key: 'c', cancelable: true, bubbles: true }); input.dispatchEvent(event); expect(event.defaultPrevented).toBe(false); expect(document.documentElement.dataset.dashcam).toBeUndefined(); input.remove(); wrapper.unmount(); }); it.each([ ['motion is withheld', () => { allowed.value = false; }], ['the scene is not running', () => { running.value = false; }], ['the reading stage is not live', () => { stage.dataset.readingStage = 'still'; }], ])('does nothing when %s', (_label, arrange) => { const wrapper = host(); arrange(); expect(press('c').defaultPrevented).toBe(false); expect(document.documentElement.dataset.dashcam).toBeUndefined(); expect(readDashcam().open).toBe(false); wrapper.unmount(); }); it('does nothing on a phone-width viewport, where the button is hidden too', () => { const matchMedia = vi.fn((query: string) => ({ matches: query === '(max-width: 767px)' }) as MediaQueryList); vi.stubGlobal('matchMedia', matchMedia); try { const wrapper = host(); expect(press('c').defaultPrevented).toBe(false); expect(document.documentElement.dataset.dashcam).toBeUndefined(); expect(readDashcam().open).toBe(false); wrapper.unmount(); } finally { vi.unstubAllGlobals(); } }); it('closes when motion is withdrawn while open', async () => { const wrapper = host(); press('c'); allowed.value = false; await vi.runOnlyPendingTimersAsync(); expect(readDashcam().open).toBe(false); wrapper.unmount(); }); });