Files
Nodecast/test/t33CastFeature.test.js
blomios fd314f0a59 feat(player): add PWA-compatible stream casting (#33)
Add a browser-native casting service with Live and Watch player controls for Remote Playback and AirPlay-compatible devices.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-06 22:07:49 +02:00

83 lines
3.3 KiB
JavaScript

const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const root = path.resolve(__dirname, '..');
const castServicePath = path.join(root, 'public', 'js', 'services', 'castService.js');
function loadCastService() {
assert.ok(fs.existsSync(castServicePath), 'CastService module should exist at public/js/services/castService.js');
return require(castServicePath);
}
test('CastService reports Remote Playback availability and prompts through video.remote', async () => {
const CastService = loadCastService();
let availabilityCallback;
let promptCalls = 0;
const video = {
remote: {
watchAvailability: async callback => {
availabilityCallback = callback;
callback(true);
return 7;
},
prompt: async () => { promptCalls += 1; }
}
};
const service = new CastService(video);
await service.watchAvailability();
assert.equal(service.state.available, true);
assert.equal(service.state.type, 'remote-playback');
availabilityCallback(false);
assert.equal(service.state.available, false);
await service.prompt();
assert.equal(promptCalls, 1);
});
test('CastService uses AirPlay picker when webkitShowPlaybackTargetPicker is available', async () => {
const CastService = loadCastService();
let airplayCalls = 0;
const video = {
webkitShowPlaybackTargetPicker: () => { airplayCalls += 1; }
};
const service = new CastService(video);
await service.watchAvailability();
assert.equal(service.state.available, true);
assert.equal(service.state.type, 'airplay');
await service.prompt();
assert.equal(airplayCalls, 1);
});
test('CastService stays unsupported without native casting APIs', async () => {
const CastService = loadCastService();
const service = new CastService({});
await assert.doesNotReject(() => service.watchAvailability());
await assert.doesNotReject(() => service.prompt());
assert.equal(service.state.available, false);
assert.equal(service.state.type, 'unsupported');
});
test('Live and Watch players expose cast controls and load CastService', () => {
const html = fs.readFileSync(path.join(root, 'public', 'index.html'), 'utf8');
assert.match(html, /id="player-cast"/, 'Live player should expose a cast button');
assert.match(html, /id="watch-cast"/, 'Watch player should expose a cast button');
assert.match(html, /\/js\/services\/castService\.js/, 'index.html should load CastService before player pages');
});
test('PWA/service worker cache rules do not precache live stream endpoints', () => {
const publicDir = path.join(root, 'public');
const candidates = fs.readdirSync(publicDir)
.filter(name => /(?:service-worker|sw|workbox).*\.js$/i.test(name))
.map(name => path.join(publicDir, name));
for (const file of candidates) {
const text = fs.readFileSync(file, 'utf8');
assert.doesNotMatch(text, /\/api\/(?:proxy|transcode|live)/, `${path.basename(file)} should not precache stream API routes`);
assert.doesNotMatch(text, /\.m3u8|\.ts\b/, `${path.basename(file)} should not precache HLS playlists or segments`);
}
});