import { expect, test } from '@playwright/test'; import { completedEpubBoundarySegment, resolveEpubBoundaryHandoffStartIndex, resolveEpubReplaySuppressionAction, shouldSuppressCompletedEpubBoundaryReplay, } from '../../src/lib/client/epub/tts-epub-handoff'; import type { CanonicalTtsSegment } from '../../src/lib/shared/tts-segment-plan'; const segment = ( text: string, overrides: Partial = {}, ): CanonicalTtsSegment => ({ key: `segment:${text}`, ordinal: 0, text, ownerSourceKey: 'str:page-a', ownerLocator: { location: 'page-a', readerType: 'epub' }, startAnchor: { sourceKey: 'str:page-a', offset: 0 }, endAnchor: { sourceKey: 'str:page-a', offset: text.length }, spansSourceBoundary: false, ...overrides, }); test.describe('EPUB boundary handoff', () => { test('records only completed segments that span a source boundary', () => { expect(completedEpubBoundarySegment(segment('same page'))).toBeNull(); const completed = completedEpubBoundarySegment(segment('cross page', { spansSourceBoundary: true, endAnchor: { sourceKey: 'str:page-b', offset: 5 }, }), 100); expect(completed).toMatchObject({ key: 'segment:cross page', fingerprint: 'cross page', completedAt: 100, }); }); test('skips a leading replay of the completed boundary segment', () => { const completed = completedEpubBoundarySegment(segment('The same audio crosses the page.', { key: 'old-key', spansSourceBoundary: true, endAnchor: { sourceKey: 'str:page-b', offset: 10 }, }), 1000); const startIndex = resolveEpubBoundaryHandoffStartIndex([ segment('The same audio crosses the page.', { key: 'new-cfi-key' }), segment('Fresh audio on the next page.'), ], completed, 1100); expect(startIndex).toBe(1); }); test('does not skip stale or non-leading matches', () => { const completed = completedEpubBoundarySegment(segment('Repeated later.', { spansSourceBoundary: true, endAnchor: { sourceKey: 'str:page-b', offset: 10 }, }), 1000); expect(resolveEpubBoundaryHandoffStartIndex([ segment('Fresh start.'), segment('Repeated later.'), ], completed, 1100)).toBe(0); expect(resolveEpubBoundaryHandoffStartIndex([ segment('Repeated later.'), ], completed, 200_000)).toBe(0); }); test('suppresses a completed boundary segment at playback time even if setText handoff missed it', () => { const completed = completedEpubBoundarySegment(segment('Boundary audio that already played.', { key: 'completed-key', spansSourceBoundary: true, endAnchor: { sourceKey: 'str:page-b', offset: 10 }, }), 1000); expect(shouldSuppressCompletedEpubBoundaryReplay( segment('Boundary audio that already played.', { key: 'different-render-key' }), completed, 1500, )).toBe(true); expect(shouldSuppressCompletedEpubBoundaryReplay( segment('Actually fresh audio.'), completed, 1500, )).toBe(false); }); test('suppression skips only within the current rendered segment list', () => { const completed = completedEpubBoundarySegment(segment('Boundary audio that already played.', { spansSourceBoundary: true, endAnchor: { sourceKey: 'str:page-b', offset: 10 }, }), 1000); expect(resolveEpubReplaySuppressionAction([ segment('Boundary audio that already played.', { key: 'rerendered-key' }), segment('Fresh local audio.'), ], 0, completed, 1100)).toEqual({ kind: 'skip-to-index', index: 1 }); }); test('suppression pauses instead of requesting another page turn when no fresh local segment exists', () => { const completed = completedEpubBoundarySegment(segment('Boundary audio that already played.', { spansSourceBoundary: true, endAnchor: { sourceKey: 'str:page-b', offset: 10 }, }), 1000); expect(resolveEpubReplaySuppressionAction([ segment('Boundary audio that already played.', { key: 'rerendered-key' }), ], 0, completed, 1100)).toEqual({ kind: 'pause' }); }); });