openreader/tests/unit/pdf-tts-planning.vitest.spec.ts

107 lines
3.4 KiB
TypeScript

import { describe, expect, test } from 'vitest';
import { buildPdfPageSourceUnits, buildPdfPrefetchPayload } from '../../src/lib/client/pdf-tts-planning';
import type { ParsedPdfPage } from '../../src/types/parsed-pdf';
function buildPage(pageNumber: number): ParsedPdfPage {
return {
pageNumber,
width: 800,
height: 1200,
blocks: [
{
id: `h-${pageNumber}`,
kind: 'header',
text: 'Header text',
fragments: [{ page: pageNumber, bbox: [0, 0, 1, 1], text: 'Header text', readingOrder: 0 }],
},
{
id: `p-${pageNumber}-a`,
kind: 'text',
text: `Paragraph A on page ${pageNumber}.`,
// Regression guard: fragment page can drift; locator must stay pinned
// to the requested page number for stable planning/grouping.
fragments: [{ page: pageNumber + 100, bbox: [0, 0, 1, 1], text: 'x', readingOrder: 1 }],
},
{
id: `p-${pageNumber}-b`,
kind: 'paragraph_title',
text: `Section title ${pageNumber}`,
fragments: [{ page: pageNumber, bbox: [0, 0, 1, 1], text: 'y', readingOrder: 2 }],
},
{
id: `empty-${pageNumber}`,
kind: 'text',
text: ' ',
fragments: [{ page: pageNumber, bbox: [0, 0, 1, 1], text: 'z', readingOrder: 3 }],
},
],
};
}
describe('pdf tts planning helpers', () => {
test('buildPdfPageSourceUnits uses parsed blocks, honors skip kinds, and pins locator page', () => {
const page = buildPage(2);
const units = buildPdfPageSourceUnits(page, 2, ['header']);
expect(units.map((u) => u.sourceKey)).toEqual([
'pdf:2:p-2-a',
'pdf:2:p-2-b',
]);
expect(units.map((u) => u.text)).toEqual([
'Paragraph A on page 2.',
'Section title 2',
]);
expect(units.map((u) => u.locator)).toEqual([
{ readerType: 'pdf', page: 2, blockId: 'p-2-a' },
{ readerType: 'pdf', page: 2, blockId: 'p-2-b' },
]);
});
test('buildPdfPrefetchPayload includes parsed sourceUnits for next and upcoming pages', () => {
const pages = new Map<number, ParsedPdfPage>([
[2, buildPage(2)],
[3, buildPage(3)],
[4, buildPage(4)],
]);
const payload = buildPdfPrefetchPayload(
[2, 3, 4],
['Page 2 text', 'Page 3 text', 'Page 4 text'],
(pageNum) => buildPdfPageSourceUnits(pages.get(pageNum), pageNum, ['header']),
);
expect(payload.nextText).toBe('Page 2 text');
expect(payload.nextSourceUnits.map((u) => u.sourceKey)).toEqual([
'pdf:2:p-2-a',
'pdf:2:p-2-b',
]);
expect(payload.additionalUpcoming).toHaveLength(2);
expect(payload.additionalUpcoming[0]).toMatchObject({
location: 3,
text: 'Page 3 text',
});
expect(payload.additionalUpcoming[0].sourceUnits.map((u) => u.sourceKey)).toEqual([
'pdf:3:p-3-a',
'pdf:3:p-3-b',
]);
expect(payload.additionalUpcoming[1].sourceUnits.map((u) => u.sourceKey)).toEqual([
'pdf:4:p-4-a',
'pdf:4:p-4-b',
]);
});
test('buildPdfPrefetchPayload drops blank upcoming text entries', () => {
const pages = new Map<number, ParsedPdfPage>([
[2, buildPage(2)],
[3, buildPage(3)],
]);
const payload = buildPdfPrefetchPayload(
[2, 3],
['Page 2 text', ' '],
(pageNum) => buildPdfPageSourceUnits(pages.get(pageNum), pageNum, ['header']),
);
expect(payload.nextText).toBe('Page 2 text');
expect(payload.additionalUpcoming).toHaveLength(0);
});
});