// @vitest-environment happy-dom import { beforeEach, describe, expect, it, vi } from 'vitest'; import { VideoPanel, type VariantsPort } from '../../src/content/video-panel.js'; import type { MediaVariant } from '../../src/shared/protocol/index.js'; function variant(over: Partial = {}): MediaVariant { return { variantId: 'v1', kind: 'muxed', drm: false, ...over }; } describe('VideoPanel', () => { beforeEach(() => { document.body.innerHTML = ''; }); it('renders a button and an initially-hidden menu', () => { const port: VariantsPort = { listVariants: vi.fn(), addVariant: vi.fn() }; const panel = new VideoPanel(port); panel.show('https://cdn.example/m.m3u8'); const button = document.querySelector('#velox-video-panel button')!; expect(button.textContent).toBe('Download this video ▾'); expect((document.querySelector('#velox-video-panel ul') as HTMLUListElement).hidden).toBe(true); }); it('lists variants on click, skipping the daemon\'s manifest parsing entirely', async () => { const listVariants = vi.fn(async () => ({ variants: [variant({ variantId: 'v-1080', resolution: '1920x1080' })], drmProtected: false, })); const port: VariantsPort = { listVariants, addVariant: vi.fn() }; const panel = new VideoPanel(port); panel.show('https://cdn.example/m.m3u8'); document.querySelector('#velox-video-panel button')!.dispatchEvent(new Event('click')); await vi.waitFor(() => expect(listVariants).toHaveBeenCalledWith('https://cdn.example/m.m3u8')); await new Promise((r) => setTimeout(r, 0)); const items = document.querySelectorAll('#velox-video-panel li'); expect(items.length).toBe(1); expect(items[0]!.textContent).toContain('1920x1080'); }); it('greys out a DRM variant with "Protected content" and does not wire a click handler', async () => { const listVariants = vi.fn(async () => ({ variants: [variant({ variantId: 'v-drm', drm: true, resolution: '1920x1080' })], drmProtected: false, })); const addVariant = vi.fn(); const panel = new VideoPanel({ listVariants, addVariant }); panel.show('https://cdn.example/m.m3u8'); document.querySelector('#velox-video-panel button')!.dispatchEvent(new Event('click')); await new Promise((r) => setTimeout(r, 0)); const item = document.querySelector('#velox-video-panel li button')!; expect(item.disabled).toBe(true); expect(item.textContent).toContain('Protected content'); item.dispatchEvent(new Event('click')); expect(addVariant).not.toHaveBeenCalled(); }); it('shows "Protected content" for a wholly DRM-protected manifest', async () => { const listVariants = vi.fn(async () => ({ variants: [variant()], drmProtected: true })); const panel = new VideoPanel({ listVariants, addVariant: vi.fn() }); panel.show('https://cdn.example/m.m3u8'); document.querySelector('#velox-video-panel button')!.dispatchEvent(new Event('click')); await new Promise((r) => setTimeout(r, 0)); expect(document.querySelector('#velox-video-panel li')!.textContent).toBe('Protected content'); }); it('calls addVariant when a non-DRM item is clicked', async () => { const listVariants = vi.fn(async () => ({ variants: [variant({ variantId: 'v-720' })], drmProtected: false })); const addVariant = vi.fn(async () => ({ taskId: 't1' })); const panel = new VideoPanel({ listVariants, addVariant }); panel.show('https://cdn.example/m.m3u8'); document.querySelector('#velox-video-panel button')!.dispatchEvent(new Event('click')); await new Promise((r) => setTimeout(r, 0)); document.querySelector('#velox-video-panel li button')!.dispatchEvent(new Event('click')); expect(addVariant).toHaveBeenCalledWith('https://cdn.example/m.m3u8', 'v-720'); }); it('surfaces an error from listVariants instead of an empty menu', async () => { const listVariants = vi.fn(async () => ({ error: 'refused: not a manifest' })); const panel = new VideoPanel({ listVariants, addVariant: vi.fn() }); panel.show('https://cdn.example/m.m3u8'); document.querySelector('#velox-video-panel button')!.dispatchEvent(new Event('click')); await new Promise((r) => setTimeout(r, 0)); expect(document.querySelector('#velox-video-panel li')!.textContent).toContain('refused: not a manifest'); }); it('show() replaces a previous panel rather than stacking them', () => { const port: VariantsPort = { listVariants: vi.fn(), addVariant: vi.fn() }; const panel = new VideoPanel(port); panel.show('https://cdn.example/a.m3u8'); panel.show('https://cdn.example/b.m3u8'); expect(document.querySelectorAll('#velox-video-panel')).toHaveLength(1); }); });