Ensure Docker images keep their container-built native dependencies, wait for the Laya sidecar healthcheck, expose accurate Laya readiness, and treat empty AI language batches as fallback failures. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -178,6 +178,139 @@ test('user analysis click keeps visible channels after Local AI language failure
|
||||
assert.ok(consoleWarnings.some(line => line.includes('Local AI language detection unavailable')));
|
||||
});
|
||||
|
||||
test('successful Standard fallback after Local AI failure keeps channels and reports the AI failure', async () => {
|
||||
const { window } = createDom();
|
||||
let rebuildClicked = 0;
|
||||
let layaBatchCalls = 0;
|
||||
window.API = {
|
||||
live: {
|
||||
getChannels: async () => projectedChannels(),
|
||||
rebuildProjection: async () => {
|
||||
rebuildClicked += 1;
|
||||
return { projection: { status: 'building', processed: 0, total: 2 } };
|
||||
},
|
||||
getProjectionStatus: async () => ({ status: 'success', processed: 2, total: 2, projectedCount: 1 })
|
||||
},
|
||||
laya: {
|
||||
getStatus: async () => ({ modelPresent: true, runtimeAvailable: true }),
|
||||
detectLanguages: async () => {
|
||||
layaBatchCalls += 1;
|
||||
throw new Error('simulated local AI language failure');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const app = {
|
||||
player: {
|
||||
settings: { liveChannelListMode: 'grouped', languageDetectionMode: 'algo' },
|
||||
saveSettings() {}
|
||||
},
|
||||
epgGuide: null,
|
||||
navigateTo() {},
|
||||
pages: { settings: { switchTab() {} } }
|
||||
};
|
||||
window.app = app;
|
||||
const channelList = new window.ChannelList();
|
||||
channelList.sources = [{ id: 1, type: 'm3u', name: 'Fixture Provider' }];
|
||||
app.channelList = channelList;
|
||||
|
||||
await channelList.loadChannels();
|
||||
channelList.expandAll();
|
||||
assert.deepEqual(
|
||||
[...window.document.querySelectorAll('.channel-name')].map(item => item.textContent),
|
||||
['Sports One', 'Sports One']
|
||||
);
|
||||
|
||||
app.player.settings.languageDetectionMode = 'hybrid';
|
||||
const settingsPage = Object.create(window.SettingsPage.prototype);
|
||||
settingsPage.app = app;
|
||||
settingsPage.initLiveAnalysis();
|
||||
await Promise.resolve();
|
||||
|
||||
window.document.getElementById('live-analysis-button').click();
|
||||
assert.equal(rebuildClicked, 1);
|
||||
await new Promise(resolve => setTimeout(resolve, 1100));
|
||||
channelList.expandAll();
|
||||
window.clearInterval(channelList._epgRefreshTimer);
|
||||
window.clearInterval(settingsPage.liveAnalysisPollTimer);
|
||||
|
||||
const afterNames = [...window.document.querySelectorAll('.channel-name')].map(item => item.textContent);
|
||||
const notice = window.document.querySelector('.laya-language-notice');
|
||||
|
||||
assert.equal(layaBatchCalls, 1);
|
||||
assert.deepEqual(afterNames, ['Sports One', 'Sports One']);
|
||||
assert.equal(window.document.querySelector('.empty-state'), null);
|
||||
assert.equal(window.document.querySelector('#channel-list > .loading'), null);
|
||||
assert.ok(notice);
|
||||
assert.match(notice.textContent, /Local AI language detection failed/);
|
||||
assert.match(notice.textContent, /Standard matching/);
|
||||
assert.equal(channelList.languageDetectionFallback, true);
|
||||
});
|
||||
|
||||
test('successful Laya language batch updates visible variant languages without Standard fallback', async () => {
|
||||
const { window } = createDom();
|
||||
let layaBatchCalls = 0;
|
||||
let batchPayload = null;
|
||||
window.API = {
|
||||
live: {
|
||||
getChannels: async () => projectedChannels(),
|
||||
rebuildProjection: async () => ({ projection: { status: 'success', processed: 2, total: 2, projectedCount: 1 } }),
|
||||
getProjectionStatus: async () => ({ status: 'success', processed: 2, total: 2, projectedCount: 1 })
|
||||
},
|
||||
laya: {
|
||||
getStatus: async () => ({ modelPresent: true, runtimeAvailable: true }),
|
||||
detectLanguages: async items => {
|
||||
layaBatchCalls += 1;
|
||||
batchPayload = items;
|
||||
return {
|
||||
runtimeAvailable: true,
|
||||
model: 'multilingual',
|
||||
results: items.map(item => ({
|
||||
id: item.id,
|
||||
language: item.id.endsWith('fr-sd') ? 'en' : 'fr',
|
||||
confidence: item.id.endsWith('fr-sd') ? 0.91 : 0.87
|
||||
}))
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const app = {
|
||||
player: {
|
||||
settings: { liveChannelListMode: 'grouped', languageDetectionMode: 'algo' },
|
||||
saveSettings() {}
|
||||
},
|
||||
epgGuide: null,
|
||||
navigateTo() {},
|
||||
pages: { settings: { switchTab() {} } }
|
||||
};
|
||||
window.app = app;
|
||||
const channelList = new window.ChannelList();
|
||||
channelList.sources = [{ id: 1, type: 'm3u', name: 'Fixture Provider' }];
|
||||
app.channelList = channelList;
|
||||
|
||||
await channelList.loadChannels();
|
||||
app.player.settings.languageDetectionMode = 'laya';
|
||||
await channelList.refreshLayaLanguages('laya');
|
||||
channelList.expandedVariants.add('sports one');
|
||||
channelList.expandAll();
|
||||
|
||||
const badges = [...window.document.querySelectorAll('.variant-language')].map(item => item.textContent);
|
||||
const evidenceTypes = channelList.logicalChannels
|
||||
.flatMap(channel => channel.variants)
|
||||
.flatMap(variant => variant.languageEvidence.map(item => item.type));
|
||||
|
||||
window.clearInterval(channelList._epgRefreshTimer);
|
||||
|
||||
assert.equal(layaBatchCalls, 1);
|
||||
assert.equal(JSON.stringify(batchPayload.map(item => item.id).sort()), JSON.stringify(['1:en-4k', '1:fr-sd']));
|
||||
assert.deepEqual([...new Set(badges)].sort(), ['EN 91%', 'FR 87%']);
|
||||
assert.ok(evidenceTypes.every(type => type === 'laya'));
|
||||
assert.equal(channelList.languageDetectionFallback, false);
|
||||
assert.equal(channelList.languageDetectionNotice, '');
|
||||
assert.equal(window.document.querySelector('.laya-language-notice'), null);
|
||||
});
|
||||
|
||||
test('analysis click does not replace visible channels with an empty successful projection', async () => {
|
||||
const { window } = createDom();
|
||||
let rebuildClicked = 0;
|
||||
|
||||
Reference in New Issue
Block a user