openreader/tests/navigation.spec.ts
Richard R 2e4f36f5c5 feat(pdf): introduce parser versioning and playback readiness state
Add PDF_PARSER_VERSION constant and propagate parser versioning throughout
the PDF parsing, job, and API layers. Implement normalization of parse state
to ensure compatibility with the current parser version, and enable reuse of
parsed PDF results when possible. Add isPlaybackReady state to document hooks
and TTS player, improving playback UX by disabling controls until content is
ready. Update tests to reflect new playback readiness logic.

BREAKING CHANGE: PDF parse state and job logic now require explicit parserVersion;
older parse states may be treated as pending until reprocessed.
2026-06-03 18:51:29 -06:00

126 lines
4.6 KiB
TypeScript

import { test, expect, type Page } from '@playwright/test';
import {
setupTest,
uploadFiles,
ensureDocumentsListed,
clickDocumentLink,
expectViewerForFile,
uploadAndDisplay,
playTTSAndWaitForASecond,
expectProcessingTransition,
} from './helpers';
// Single-spec helpers kept local to avoid cluttering shared helpers:
async function navigateToPdfPageViaNavigator(page: Page, targetPage: number) {
// Navigator popover shows "X / Y"
const navTrigger = page.getByRole('button', { name: /\d+\s*\/\s*\d+/ });
await expect(navTrigger).toBeVisible({ timeout: 10000 });
await navTrigger.click();
const input = page.getByLabel('Page number');
await expect(input).toBeVisible({ timeout: 10000 });
await input.fill(String(targetPage));
await input.press('Enter');
}
async function countRenderedPdfPages(page: Page): Promise<number> {
return await page.locator('.react-pdf__Page').count();
}
async function triggerViewportResize(page: Page, width: number, height: number) {
await page.setViewportSize({ width, height });
}
test.describe('Document link navigation by type', () => {
test.beforeEach(async ({ page }, testInfo) => {
await setupTest(page, testInfo);
});
test('navigates to /pdf, /epub, /html and renders correct viewers', async ({ page }) => {
test.setTimeout(120_000);
// Upload documents
await uploadFiles(page, 'sample.pdf', 'sample.epub', 'sample.txt');
// Ensure links exist
await ensureDocumentsListed(page, ['sample.pdf', 'sample.epub', 'sample.txt']);
// PDF
await clickDocumentLink(page, 'sample.pdf');
await expectViewerForFile(page, 'sample.pdf');
await page.goBack();
// EPUB
await clickDocumentLink(page, 'sample.epub');
await expectViewerForFile(page, 'sample.epub');
await page.goBack();
// TXT (HTML viewer)
await clickDocumentLink(page, 'sample.txt');
await expectViewerForFile(page, 'sample.txt');
});
});
test.describe('PDF view modes and Navigator', () => {
test.beforeEach(async ({ page }, testInfo) => {
await setupTest(page, testInfo);
});
test('switches Single/Dual/Scroll modes and uses Navigator to change page', async ({ page }) => {
test.setTimeout(120_000);
// Open PDF viewer
await uploadAndDisplay(page, 'sample.pdf');
// Open document settings (page-level settings)
await page.getByRole('button', { name: 'Open settings' }).click();
// Page mode now uses pill-style radios; switch from Single Page to Two Pages
await page.getByRole('radiogroup', { name: 'Page mode' }).getByRole('radio', { name: 'Two Pages' }).click();
await page.getByRole('button', { name: 'Close' }).click();
// Expect dual-page rendering (sample.pdf has >= 2 pages)
const dualCount = await countRenderedPdfPages(page);
expect(dualCount).toBeGreaterThanOrEqual(2);
// Switch to Continuous Scroll
await page.getByRole('button', { name: 'Open settings' }).click();
await page.getByRole('radiogroup', { name: 'Page mode' }).getByRole('radio', { name: 'Continuous Scroll' }).click();
await page.getByRole('button', { name: 'Close' }).click();
// Expect continuous scroll renders at least as many pages as dual mode
const scrollCount = await countRenderedPdfPages(page);
expect(scrollCount).toBeGreaterThanOrEqual(dualCount);
// Use Navigator to go to a page (clamps to last if too large)
await navigateToPdfPageViaNavigator(page, 999);
// Navigator jump is configured to pause; ensure Play is visible then resume
await expect(page.getByRole('button', { name: 'Play' })).toBeVisible({ timeout: 10000 });
await page.getByRole('button', { name: 'Play' }).click();
await expectProcessingTransition(page);
});
});
test.describe('EPUB resize pauses TTS', () => {
test.beforeEach(async ({ page }, testInfo) => {
await setupTest(page, testInfo);
});
test('resizing viewport pauses playback and play resumes after', async ({ page }) => {
// Start playback on EPUB
await playTTSAndWaitForASecond(page, 'sample.epub');
// Trigger a significant viewport resize to fire useEPUBResize
await triggerViewportResize(page, 1200, 900);
await page.waitForTimeout(750); // allow resize flag to propagate
await triggerViewportResize(page, 900, 700);
await page.waitForTimeout(750);
// After resize, playback should have paused (Play button visible)
const playButton = page.getByRole('button', { name: 'Play' });
await expect(playButton).toBeVisible({ timeout: 15000 });
await expect(playButton).toBeEnabled({ timeout: 15000 });
// Resume playback and ensure processing -> playing
await playButton.click();
await expectProcessingTransition(page);
});
});