const test = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const { JSDOM } = require('jsdom'); const root = path.resolve(__dirname, '..'); const projection = require('../public/js/services/liveChannelProjection.js'); function script(relativePath) { return fs.readFileSync(path.join(root, 'public', relativePath), 'utf8'); } test('projection adapter preserves backend channel-language views and their variants', () => { const mapped = projection.mapResponse({ projection: { stale: false }, filters: { availableLanguages: [{ code: 'fr', short: 'FR', label: 'French' }] }, channels: [{ id: 'logical-1:fr', logicalChannelId: 'logical-1', displayName: 'beIN Sports 1', language: { code: 'fr', short: 'FR', label: 'French', confidence: 95 }, selectedVariantId: '7:hd', epg: { channelId: 'BeInSports1.fr', language: 'fr' }, variants: [ { id: 'sd', sourceId: 7, name: 'beIN 1 SD', categoryName: 'Sports', isHidden: false }, { id: 'hd', sourceId: 7, name: 'beIN 1 HD', categoryName: 'Sports', isHidden: false } ] }] }, [{ id: 7, type: 'm3u', name: 'Provider' }]); assert.equal(mapped.logicalChannels.length, 1); assert.equal(mapped.logicalChannels[0].fingerprint, 'logical-1:fr'); assert.equal(mapped.logicalChannels[0].language.code, 'fr'); assert.equal(mapped.logicalChannels[0].preferredVariant.id, 'hd'); assert.equal(mapped.logicalChannels[0].variants.length, 2); assert.equal(mapped.logicalChannels[0].tvgId, 'BeInSports1.fr'); assert.deepEqual(mapped.availableLanguages.map(language => language.code), ['fr']); }); test('live API sends flexible search and selected language filters', async () => { const dom = new JSDOM('', { url: 'http://nodecast.test/', runScripts: 'outside-only' }); let requestedUrl = ''; dom.window.fetch = async url => { requestedUrl = url; return { ok: true, headers: { get: () => 'application/json' }, json: async () => ({ channels: [] }) }; }; dom.window.eval(script('js/api.js')); await dom.window.API.live.getChannels({ q: 'Bein Sport 1', languages: ['fr', 'en'] }); const params = new URL(requestedUrl, 'http://nodecast.test').searchParams; assert.equal(params.get('q'), 'Bein Sport 1'); assert.equal(params.get('languages'), 'fr,en'); }); test('built-in EPG sources expose only refresh and enable controls', () => { const dom = new JSDOM('
', { runScripts: 'outside-only' }); dom.window.Icons = { series: '', refresh: '' }; dom.window.API = { sources: {} }; dom.window.eval(`${script('js/components/SourceManager.js')}\nwindow.SourceManager = SourceManager;`); const manager = Object.create(dom.window.SourceManager.prototype); manager.builtinEpgList = dom.window.document.getElementById('builtin-epg-list'); manager.refreshSource = () => {}; manager.toggleSource = () => {}; manager.renderBuiltinEpgSources([{ id: 4, name: 'EPG.PW France', builtinKey: 'epgpw-fr', enabled: false, language: 'fr', country: 'FR', readonly: true, removable: false }]); assert.match(manager.builtinEpgList.textContent, /Built-in/); assert.match(manager.builtinEpgList.textContent, /Free EPG data/); assert.equal(manager.builtinEpgList.querySelector('[data-action="delete"]'), null); assert.equal(manager.builtinEpgList.querySelector('[data-action="edit"]'), null); assert.equal(manager.builtinEpgList.querySelector('[data-action="toggle"]').getAttribute('aria-pressed'), 'false'); });