Read source sync status when projection analysis is idle so Settings can keep Channel Analysis progress visible during startup or externally-triggered live source refreshes. Refs #47 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
135 lines
5.7 KiB
JavaScript
135 lines
5.7 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, '..');
|
|
|
|
function readPublicScript(relativePath, exposeGlobal = '') {
|
|
const source = fs.readFileSync(path.join(root, 'public', relativePath), 'utf8');
|
|
return exposeGlobal ? `${source}\nwindow.${exposeGlobal} = ${exposeGlobal};` : source;
|
|
}
|
|
|
|
function createSettingsDom() {
|
|
const dom = new JSDOM(`<!doctype html>
|
|
<button type="button" id="live-check-new-button">Check new streams</button>
|
|
<button type="button" id="live-rebuild-catalog-button">Run full refresh</button>
|
|
<button type="button" id="live-analysis-button">Analyze channels</button>
|
|
<div id="live-analysis-status" role="status"></div>
|
|
<div id="live-analysis-progress" hidden>
|
|
<div id="live-analysis-progress-track"><span id="live-analysis-progress-bar"></span></div>
|
|
<div id="live-analysis-progress-text"></div>
|
|
</div>
|
|
<div id="live-analysis-fallback"></div>
|
|
<button type="button" id="live-analysis-details-toggle" hidden></button>
|
|
<pre id="live-analysis-error-details" hidden></pre>
|
|
`, {
|
|
url: 'http://nodecast.test/',
|
|
runScripts: 'outside-only',
|
|
pretendToBeVisual: true
|
|
});
|
|
const { window } = dom;
|
|
window.console = console;
|
|
window.eval(readPublicScript('js/services/liveAnalysisPresentation.js'));
|
|
window.eval(readPublicScript('js/pages/Settings.js', 'SettingsPage'));
|
|
return window;
|
|
}
|
|
|
|
test('Channel Analysis keeps progress visible for a live source sync started outside the current UI action', async () => {
|
|
const window = createSettingsDom();
|
|
let sourceStatusRead = false;
|
|
window.API = {
|
|
live: {
|
|
getProjectionStatus: async () => ({
|
|
status: 'success',
|
|
phase: 'complete',
|
|
projectedCount: 42,
|
|
processed: 42,
|
|
total: 42
|
|
})
|
|
},
|
|
sources: {
|
|
getStatus: async () => {
|
|
sourceStatusRead = true;
|
|
return [{
|
|
source_id: 1,
|
|
type: 'all',
|
|
status: 'syncing',
|
|
last_sync: Date.now(),
|
|
error: null
|
|
}];
|
|
}
|
|
}
|
|
};
|
|
|
|
const settingsPage = Object.create(window.SettingsPage.prototype);
|
|
settingsPage.app = {
|
|
player: { settings: { languageDetectionMode: 'algo' } },
|
|
channelList: { loadChannels() {} }
|
|
};
|
|
settingsPage.layaStatus = {};
|
|
settingsPage.currentLiveAnalysisAction = '';
|
|
|
|
try {
|
|
await settingsPage.loadLiveAnalysisStatus();
|
|
|
|
assert.equal(sourceStatusRead, true, 'Settings should consult /api/sources/status when projection status is not busy');
|
|
assert.equal(window.document.getElementById('live-analysis-progress').hidden, false);
|
|
assert.equal(window.document.getElementById('live-analysis-status').textContent, 'Refreshing IPTV sources…');
|
|
assert.equal(window.document.getElementById('live-analysis-progress-text').textContent, 'In progress — this step may take several minutes.');
|
|
assert.equal(window.document.getElementById('live-analysis-progress-track').classList.contains('indeterminate'), true);
|
|
const rebuildButton = window.document.getElementById('live-rebuild-catalog-button');
|
|
assert.equal(rebuildButton.disabled, true);
|
|
assert.equal(rebuildButton.textContent, 'Run full refresh');
|
|
} finally {
|
|
window.close();
|
|
}
|
|
});
|
|
|
|
test('busy projection status remains authoritative over a simultaneous source sync', async () => {
|
|
const window = createSettingsDom();
|
|
window.API = {
|
|
live: { getProjectionStatus: async () => ({ status: 'building', phase: 'laya_language', phaseProcessed: 1, phaseTotal: 2 }) },
|
|
sources: { getStatus: async () => { throw new Error('should not be called'); } }
|
|
};
|
|
const settingsPage = Object.create(window.SettingsPage.prototype);
|
|
settingsPage.app = { player: { settings: { languageDetectionMode: 'laya' } } };
|
|
settingsPage.layaStatus = {};
|
|
settingsPage.currentLiveAnalysisAction = '';
|
|
|
|
try {
|
|
await settingsPage.loadLiveAnalysisStatus();
|
|
assert.equal(window.document.getElementById('live-analysis-status').textContent, 'Detecting stream languages…');
|
|
assert.equal(window.document.getElementById('live-rebuild-catalog-button').textContent, 'Refreshing…');
|
|
} finally {
|
|
window.close();
|
|
}
|
|
});
|
|
|
|
test('EPG-only source sync is ignored and source status failures preserve projection status', async () => {
|
|
for (const getStatus of [
|
|
async () => [{ type: 'epg', status: 'syncing' }],
|
|
async () => { throw new Error('source status unavailable'); }
|
|
]) {
|
|
const window = createSettingsDom();
|
|
window.API = {
|
|
live: { getProjectionStatus: async () => ({ status: 'success', phase: 'complete', projectedCount: 42 }) },
|
|
sources: { getStatus }
|
|
};
|
|
const settingsPage = Object.create(window.SettingsPage.prototype);
|
|
settingsPage.app = { player: { settings: { languageDetectionMode: 'algo' } } };
|
|
settingsPage.layaStatus = {};
|
|
settingsPage.currentLiveAnalysisAction = '';
|
|
|
|
try {
|
|
const status = await settingsPage.loadLiveAnalysisStatus();
|
|
assert.equal(status.phase, 'complete');
|
|
assert.equal(window.document.getElementById('live-analysis-progress').hidden, true);
|
|
assert.equal(window.document.getElementById('live-rebuild-catalog-button').disabled, false);
|
|
} finally {
|
|
window.close();
|
|
}
|
|
}
|
|
});
|