Files
Nodecast/test/t23Frontend.test.js
blomios 8f2825f697 feat(live): show language-scoped stream variants
Keep live stream variant expansion scoped to the selected language while preserving the legacy all-language variants response when no language is provided.

Display projected current EPG programme data in live rows and distinguish selected streams from actively playing streams in the collapsed variant UI.

Refs #43

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-08 19:00:14 +02:00

77 lines
3.9 KiB
JavaScript

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');
await dom.window.API.live.getChannelVariants('logical:bein-1', { language: 'fr' });
const variantUrl = new URL(requestedUrl, 'http://nodecast.test');
assert.equal(variantUrl.pathname, '/api/live/channels/logical%3Abein-1/variants');
assert.equal(variantUrl.searchParams.get('language'), 'fr');
});
test('built-in EPG sources expose only refresh and enable controls', () => {
const dom = new JSDOM('<div id="builtin-epg-list"></div>', { runScripts: 'outside-only' });
dom.window.Icons = { series: '<i></i>', refresh: '<i></i>' };
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');
});