import { describe, it } from './runner.mjs'; import { JSDOM } from 'jsdom'; describe('Viewer: resolveSrc()', () => { // We extract the pure logic functions from viewer.js for unit testing. // Since viewer.js uses browser globals, we test the logic independently. const resolveSrc = (entry) => { if (typeof entry === 'string') return entry; return entry.src || entry.url || ''; }; it('should resolve string entries directly', () => { const result = resolveSrc('https://example.com/video.mp4'); if (result !== 'https://example.com/video.mp4') { throw new Error(`Expected original string, got: ${result}`); } }); it('should prefer src over url in object entries', () => { const entry = { url: 'https://example.com/page', src: 'https://cdn.example.com/stream.mp4' }; const result = resolveSrc(entry); if (result !== 'https://cdn.example.com/stream.mp4') { throw new Error(`Expected src, got: ${result}`); } }); it('should fall back to url when src is missing', () => { const entry = { url: 'https://example.com/video.mp4' }; const result = resolveSrc(entry); if (result !== 'https://example.com/video.mp4') { throw new Error(`Expected url, got: ${result}`); } }); it('should return empty string for empty object', () => { const entry = {}; const result = resolveSrc(entry); if (result !== '') { throw new Error(`Expected empty string, got: ${result}`); } }); }); describe('Viewer: isHls()', () => { const isHls = (entry) => { return entry !== null && typeof entry === 'object' && entry.mediaType === 'hls'; }; it('should detect HLS entries with mediaType=hls', () => { const entry = { url: 'https://example.com/stream', mediaType: 'hls', hlsManifest: 'https://cdn.example.com/stream.m3u8' }; if (!isHls(entry)) { throw new Error('HLS entry not detected'); } }); it('should NOT detect direct mediaType', () => { const entry = { url: 'https://example.com/video.mp4', mediaType: 'direct' }; if (isHls(entry)) { throw new Error('Direct entry falsely detected as HLS'); } }); it('should NOT detect entries without mediaType (backward compat)', () => { const entry = { url: 'https://example.com/video.mp4' }; if (isHls(entry)) { throw new Error('Legacy entry without mediaType falsely detected as HLS'); } }); it('should NOT detect string entries', () => { if (isHls('https://example.com/video.mp4')) { throw new Error('String entry falsely detected as HLS'); } }); it('should NOT detect null entries (null guard)', () => { // With the null guard, isHls(null) safely returns false instead of throwing if (isHls(null)) { throw new Error('Null entry falsely detected as HLS'); } }); }); describe('Viewer: resolveHlsManifest()', () => { const resolveHlsManifest = (entry) => { if (typeof entry === 'object' && entry.hlsManifest) { return entry.hlsManifest; } return null; }; it('should return hlsManifest URL when present', () => { const entry = { url: 'https://example.com/page', hlsManifest: 'https://cdn.example.com/stream.m3u8' }; const result = resolveHlsManifest(entry); if (result !== 'https://cdn.example.com/stream.m3u8') { throw new Error(`Expected hlsManifest URL, got: ${result}`); } }); it('should return null when hlsManifest is missing', () => { const entry = { url: 'https://example.com/video.mp4' }; const result = resolveHlsManifest(entry); if (result !== null) { throw new Error(`Expected null, got: ${result}`); } }); it('should return null for string entries', () => { const result = resolveHlsManifest('https://example.com/video.mp4'); if (result !== null) { throw new Error(`Expected null for string, got: ${result}`); } }); }); describe('Viewer: DOM setup via jsdom', () => { it('should create panels container and video elements', () => { const dom = new JSDOM(`
`); const panelsEl = dom.window.document.getElementById('panels'); if (!panelsEl) { throw new Error('panels element not found'); } // Simulate what viewer.js does: create panels const panel = dom.window.document.createElement('div'); panel.classList.add('panel'); const video = dom.window.document.createElement('video'); video.autoplay = true; video.muted = true; video.loop = true; video.playsInline = true; video.src = 'https://example.com/video.mp4'; panel.appendChild(video); panelsEl.appendChild(panel); const videos = panelsEl.querySelectorAll('video'); if (videos.length !== 1) { throw new Error(`Expected 1 video, got ${videos.length}`); } if (!videos[0].autoplay) throw new Error('autoplay not set'); if (!videos[0].muted) throw new Error('muted not set'); if (!videos[0].loop) throw new Error('loop not set'); if (!videos[0].playsInline) throw new Error('playsInline not set'); }); it('should create an NxN grid layout', () => { const dom = new JSDOM(` `); const panelsEl = dom.window.document.getElementById('panels'); const gridN = 3; for (let i = 0; i < gridN * gridN; i++) { const panel = dom.window.document.createElement('div'); panel.classList.add('panel'); panelsEl.appendChild(panel); } panelsEl.style.gridTemplateColumns = `repeat(${gridN}, 1fr)`; panelsEl.style.gridTemplateRows = `repeat(${gridN}, 1fr)`; const panels = panelsEl.querySelectorAll('.panel'); if (panels.length !== 9) { throw new Error(`Expected 9 panels, got ${panels.length}`); } if (panelsEl.style.gridTemplateColumns !== 'repeat(3, 1fr)') { throw new Error('Grid columns not set correctly'); } }); it('should handle empty panels correctly', () => { const dom = new JSDOM(` `); const panelsEl = dom.window.document.getElementById('panels'); const panel = dom.window.document.createElement('div'); panel.classList.add('panel'); panel.classList.add('empty'); panelsEl.appendChild(panel); const emptyPanels = panelsEl.querySelectorAll('.panel.empty'); if (emptyPanels.length !== 1) { throw new Error(`Expected 1 empty panel, got ${emptyPanels.length}`); } }); });