118 lines
6.8 KiB
JavaScript
118 lines
6.8 KiB
JavaScript
// @ts-check
|
|
// Section-specific feature code should not be fetched until its section is opened.
|
|
const { test, expect } = require('@playwright/test');
|
|
|
|
test('heavy feature modules load on demand', async ({ page }) => {
|
|
const requests = [];
|
|
page.on('request', request => {
|
|
if (request.url().includes('/static/js/')) requests.push(request.url());
|
|
});
|
|
|
|
await page.goto('/', { waitUntil: 'domcontentloaded' });
|
|
await page.waitForFunction(() => typeof window.navTo === 'function', null, { timeout: 15_000 });
|
|
await page.waitForSelector('#app-skeleton', { state: 'detached', timeout: 15_000 });
|
|
expect(requests.some(url => url.includes('/rehearser.js'))).toBeFalsy();
|
|
expect(requests.some(url => url.includes('/library-characters.js'))).toBeFalsy();
|
|
|
|
await page.evaluate(() => window.navTo('s-rehearser'));
|
|
await page.waitForFunction(() => window.rehState?.synthCache, null, { timeout: 15_000 });
|
|
expect(requests.some(url => url.includes('/rehearser.js'))).toBeTruthy();
|
|
|
|
const countBeforeRepeat = requests.filter(url => url.includes('/rehearser.js')).length;
|
|
await page.evaluate(() => window.navTo('s-rehearser'));
|
|
await page.waitForTimeout(100);
|
|
expect(requests.filter(url => url.includes('/rehearser.js')).length).toBe(countBeforeRepeat);
|
|
});
|
|
|
|
test('Audiobook lazy modules publish their namespace APIs after loading', async ({ page }) => {
|
|
await page.goto('/', { waitUntil: 'domcontentloaded' });
|
|
await page.waitForFunction(() => typeof window.navTo === 'function', null, { timeout: 15_000 });
|
|
await page.waitForSelector('#app-skeleton', { state: 'detached', timeout: 15_000 });
|
|
await page.evaluate(() => window.navTo('s-reader'));
|
|
await page.waitForFunction(() => typeof window.TTVC?.audiobook?.core?.dialogueKey === 'function', null, { timeout: 15_000 });
|
|
await expect.poll(() => page.evaluate(() => ({
|
|
castView: typeof window.TTVC?.audiobook?.cast?.castView,
|
|
stream: typeof window.TTVC?.audiobook?.attributeStream,
|
|
startCast: typeof window.TTVC?.audiobook?.startCast,
|
|
pipelineCast: typeof window.TTVC?.audiobook?.pipeline?.cast,
|
|
core: typeof window.TTVC?.audiobook?.core?.dialogueKey,
|
|
preview: typeof window.TTVC?.audiobook?.preview?.show,
|
|
exportAudio: typeof window.TTVC?.audiobook?.exporting?.export,
|
|
}))).toEqual({ castView: 'function', stream: 'function', startCast: 'function', pipelineCast: 'function', core: 'function', preview: 'function', exportAudio: 'function' });
|
|
});
|
|
|
|
test('Audiobook recast merge preserves narration and merges only known dialogue pairs', async ({ page }) => {
|
|
await page.goto('/', { waitUntil: 'domcontentloaded' });
|
|
await page.waitForFunction(() => typeof window.navTo === 'function', null, { timeout: 15_000 });
|
|
await page.waitForSelector('#app-skeleton', { state: 'detached', timeout: 15_000 });
|
|
await page.evaluate(() => window.navTo('s-reader'));
|
|
await page.waitForFunction(() => typeof window.TTVC?.audiobook?.cast?.mergeAdjacentDialogue === 'function', null, { timeout: 15_000 });
|
|
await expect.poll(() => page.evaluate(() => {
|
|
const merge = window.TTVC.audiobook.cast.mergeAdjacentDialogue;
|
|
return merge([
|
|
{ type: 'narration', speaker: 'Narrator', text: 'A.' },
|
|
{ type: 'narration', speaker: 'Narrator', text: 'B.' },
|
|
{ type: 'dialogue', speaker: 'Mira', text: 'C.' },
|
|
{ type: 'dialogue', speaker: 'Mira', text: 'D.' },
|
|
{ type: 'dialogue', speaker: 'Unknown', text: 'E.' },
|
|
{ type: 'dialogue', speaker: 'Unknown', text: 'F.' },
|
|
]);
|
|
})).toEqual({
|
|
segments: [
|
|
{ type: 'narration', speaker: 'Narrator', text: 'A.' },
|
|
{ type: 'narration', speaker: 'Narrator', text: 'B.' },
|
|
{ type: 'dialogue', speaker: 'Mira', text: 'C.\n\nD.' },
|
|
{ type: 'dialogue', speaker: 'Unknown', text: 'E.' },
|
|
{ type: 'dialogue', speaker: 'Unknown', text: 'F.' },
|
|
],
|
|
merged: 1,
|
|
});
|
|
});
|
|
|
|
test('Rehearser playback module publishes transport APIs after loading', async ({ page }) => {
|
|
await page.goto('/', { waitUntil: 'domcontentloaded' });
|
|
await page.waitForFunction(() => typeof window.navTo === 'function', null, { timeout: 15_000 });
|
|
await page.waitForSelector('#app-skeleton', { state: 'detached', timeout: 15_000 });
|
|
await page.evaluate(() => window.navTo('s-rehearser'));
|
|
await page.waitForFunction(() => window.rehState?.synthCache, null, { timeout: 15_000 });
|
|
await expect.poll(() => page.evaluate(() => ({
|
|
start: typeof window.TTVC?.rehearser?.playback?.start,
|
|
stop: typeof window.TTVC?.rehearser?.playback?.stop,
|
|
synthAll: typeof window.TTVC?.rehearser?.playback?.synthAll,
|
|
loadRecord: typeof window.TTVC?.rehearser?.io?.loadRecord,
|
|
exportSession: typeof window.TTVC?.rehearser?.io?.exportSession,
|
|
recording: typeof window.TTVC?.rehearser?.recording?.start,
|
|
training: typeof window.TTVC?.rehearser?.training?.enter,
|
|
core: typeof window.TTVC?.rehearser?.core?.buildScriptPage,
|
|
state: !!window.TTVC?.rehearser?.state,
|
|
}))).toEqual({ start: 'function', stop: 'function', synthAll: 'function', loadRecord: 'function', exportSession: 'function', recording: 'function', training: 'function', core: 'function', state: true });
|
|
});
|
|
|
|
test('pure leaf modules keep private declarations behind compatibility exports', async ({ page }) => {
|
|
await page.goto('/', { waitUntil: 'domcontentloaded' });
|
|
await page.waitForFunction(() => typeof window.navTo === 'function', null, { timeout: 15_000 });
|
|
await page.waitForSelector('#app-skeleton', { state: 'detached', timeout: 15_000 });
|
|
await page.evaluate(() => window.navTo('s-reader'));
|
|
await page.waitForFunction(() => typeof window.audiobookSplitByQuotes === 'function', null, { timeout: 15_000 });
|
|
await expect.poll(() => page.evaluate(() => ({
|
|
parse: typeof window.TTVC?.rehearser?.parse?.parseScript,
|
|
compare: typeof window.TTVC?.rehearser?.train?.compare,
|
|
dialogue: typeof window.TTVC?.audiobook?.parse?.audiobookHasDialogue,
|
|
split: typeof window.TTVC?.audiobook?.segmentation?.splitByQuotes,
|
|
}))).toEqual({ parse: 'function', compare: 'function', dialogue: 'function', split: 'function' });
|
|
});
|
|
|
|
test('initial cast normalization merges narration without fusing dialogue turns', async ({ page }) => {
|
|
await page.goto('/', { waitUntil: 'domcontentloaded' });
|
|
await page.waitForFunction(() => typeof window.navTo === 'function', null, { timeout: 15_000 });
|
|
await page.evaluate(() => window.navTo('s-reader'));
|
|
await page.waitForFunction(() => typeof window.TTVC?.audiobook?.cast?.mergeAdjacentNarration === 'function', null, { timeout: 15_000 });
|
|
const result = await page.evaluate(() => window.TTVC.audiobook.cast.mergeAdjacentNarration([
|
|
{ type: 'narration', speaker: 'Narrator', page: 1, text: 'A.' },
|
|
{ type: 'narration', speaker: 'Narrator', page: 1, text: 'B.' },
|
|
{ type: 'dialogue', speaker: 'Mira', page: 1, text: 'C.' },
|
|
{ type: 'dialogue', speaker: 'Mira', page: 1, text: 'D.' },
|
|
]));
|
|
expect(result.map(s => s.text)).toEqual(['A.\n\nB.', 'C.', 'D.']);
|
|
});
|