test(unit): complete vitest migration for remaining unit suites

This commit is contained in:
Richard R 2026-05-30 11:17:18 -06:00
parent 22a1b5de57
commit 3307477548
38 changed files with 96 additions and 100 deletions

View file

@ -1,4 +1,4 @@
import { expect, test } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import { inArray } from 'drizzle-orm';
import { db } from '../../src/db';
import { adminProviders } from '../../src/db/schema';
@ -12,7 +12,7 @@ import {
type AdminProviderRecord,
} from '../../src/lib/server/admin/providers';
test.describe('admin provider validation', () => {
describe('admin provider validation', () => {
test('normalizes valid slugs to lowercase', () => {
expect(validateSlug('My-Provider-1')).toBe('my-provider-1');
});

View file

@ -1,8 +1,8 @@
import { expect, test } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import { resolveEffectiveTtsInstructions } from '../../src/lib/server/admin/tts-instructions';
test.describe('resolveEffectiveTtsInstructions', () => {
describe('resolveEffectiveTtsInstructions', () => {
test('uses explicit request instructions when model supports them', () => {
const out = resolveEffectiveTtsInstructions({
model: 'gpt-4o-mini-tts',

View file

@ -1,7 +1,7 @@
import { test, expect } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import { buildAllowedAudiobookUserIds, pickAudiobookOwner } from '../../src/lib/server/audiobooks/user-scope';
test.describe('audiobook scope selection', () => {
describe('audiobook scope selection', () => {
test('uses only unclaimed scope when auth is disabled', () => {
const result = buildAllowedAudiobookUserIds(false, null, 'unclaimed::ns');
expect(result.preferredUserId).toBe('unclaimed::ns');

View file

@ -1,10 +1,10 @@
import { test, expect } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import {
canonicalizeAudiobookSettingsForRuntime,
coerceAudiobookGenerationSettings,
} from '../../src/lib/server/audiobooks/settings';
test.describe('coerceAudiobookGenerationSettings', () => {
describe('coerceAudiobookGenerationSettings', () => {
test('accepts current metadata shape without migration', () => {
const result = coerceAudiobookGenerationSettings({
providerRef: 'openai',

View file

@ -1,4 +1,4 @@
import { test, expect } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import {
escapeFFMetadata,
encodeChapterTitleTag,
@ -7,7 +7,7 @@ import {
decodeChapterFileName
} from '../../src/lib/server/audiobooks/chapters';
test.describe('escapeFFMetadata', () => {
describe('escapeFFMetadata', () => {
test('escapes special characters correctly', () => {
const input = 'Title with = ; # and backslash \\';
// Expected: Equal -> \=, Semicolon -> \;, Hash -> \#, Backslash -> \\
@ -37,7 +37,7 @@ test.describe('escapeFFMetadata', () => {
});
});
test.describe('Title Tags', () => {
describe('Title Tags', () => {
test('encodeChapterTitleTag formats correctly', () => {
expect(encodeChapterTitleTag(0, 'Intro')).toBe('0001 - Intro');
expect(encodeChapterTitleTag(9, 'Chapter Ten')).toBe('0010 - Chapter Ten');
@ -63,7 +63,7 @@ test.describe('Title Tags', () => {
});
});
test.describe('Chapter File Names', () => {
describe('Chapter File Names', () => {
test('encodeChapterFileName formats correctly', () => {
expect(encodeChapterFileName(0, 'Intro', 'mp3')).toBe('0001__Intro.mp3');
expect(encodeChapterFileName(1, 'Part 2', 'm4b')).toBe('0002__Part%202.m4b');

View file

@ -1,4 +1,4 @@
import { test, expect } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import {
audiobookKey,
audiobookPrefix,
@ -14,7 +14,7 @@ function configureS3Env() {
process.env.S3_PREFIX = 'openreader-test';
}
test.describe('audiobooks-blobstore', () => {
describe('audiobooks-blobstore', () => {
test.beforeAll(() => {
configureS3Env();
});

View file

@ -1,4 +1,4 @@
import { expect, test } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import { scheduleChangelogCheck } from '../../src/lib/client/changelog-check';
@ -6,7 +6,7 @@ function wait(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
test.describe('changelog check scheduling', () => {
describe('changelog check scheduling', () => {
test('cancels throwaway mount and runs exactly once on remount', async () => {
const completedRef = { current: null as string | null };
const inFlightRef = { current: null as string | null };

View file

@ -1,4 +1,4 @@
import { expect, test } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import {
findCurrentVersionIndex,
@ -10,7 +10,7 @@ import {
type ChangelogManifestEntry,
} from '../../src/lib/shared/changelog';
test.describe('changelog utilities', () => {
describe('changelog utilities', () => {
test('normalizes version strings with and without v prefix', () => {
expect(normalizeVersion('v2.2.0')).toBe('2.2.0');
expect(normalizeVersion('2.2.0')).toBe('2.2.0');

View file

@ -1,4 +1,4 @@
import { test, expect } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import path from 'path';
import { readFile } from 'fs/promises';
import {
@ -6,7 +6,7 @@ import {
renderPdfFirstPageToJpeg,
} from '../../src/lib/server/documents/previews-render';
test.describe('document-previews-render', () => {
describe('document-previews-render', () => {
test('renders first PDF page to JPEG preview', async () => {
const pdfPath = path.join(process.cwd(), 'tests/files/sample.pdf');
const bytes = await readFile(pdfPath);

View file

@ -1,11 +1,11 @@
import { expect, test } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import {
filterNonEmptySpineTextEntries,
resolveLoadedSpineSectionDocument,
} from '../../src/hooks/epub/useEPUBAudiobook';
test.describe('EPUB audiobook hook helpers', () => {
describe('EPUB audiobook hook helpers', () => {
test('resolveLoadedSpineSectionDocument prefers ownerDocument when available', () => {
const ownerDocument = { body: { textContent: 'from ownerDocument' } } as unknown as Document;
const loaded = { ownerDocument } as unknown;

View file

@ -1,4 +1,4 @@
import { expect, test } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import {
isDirectionalEpubLocation,
@ -6,7 +6,7 @@ import {
shouldPersistEpubLocation,
} from '../../src/hooks/epub/useEPUBLocationController';
test.describe('EPUB location controller helpers', () => {
describe('EPUB location controller helpers', () => {
test('detects directional locations', () => {
expect(isDirectionalEpubLocation('next')).toBe(true);
expect(isDirectionalEpubLocation('prev')).toBe(true);

View file

@ -1,4 +1,4 @@
import { expect, test } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import type { Book } from 'epubjs';
import {
buildEpubLocator,
@ -71,7 +71,7 @@ function makeFakeBook(items: Array<{ index: number; href: string; cfiBase: strin
return { book, sections };
}
test.describe('findSegmentOffset', () => {
describe('findSegmentOffset', () => {
test('finds an offset for matching text', () => {
const spineText = 'Hello world. This is a test paragraph for offsets.';
const offset = findSegmentOffset(spineText, 'this is a test');
@ -98,7 +98,7 @@ test.describe('findSegmentOffset', () => {
});
});
test.describe('resolveSpineFromCfi', () => {
describe('resolveSpineFromCfi', () => {
test('returns spine identity for a CFI inside a known spine', () => {
const { book } = makeFakeBook([
{ index: 2, href: 'OEBPS/ch02.xhtml', cfiBase: '/6/4', text: 'chapter 2 contents' },
@ -125,7 +125,7 @@ test.describe('resolveSpineFromCfi', () => {
});
});
test.describe('buildEpubLocator', () => {
describe('buildEpubLocator', () => {
test('produces a stable locator with charOffset within the spine item', async () => {
const { book } = makeFakeBook([
{
@ -175,7 +175,7 @@ test.describe('buildEpubLocator', () => {
});
});
test.describe('buildEpubLocatorFromChunk', () => {
describe('buildEpubLocatorFromChunk', () => {
test('uses the chunk anchor offset as a hint to disambiguate repeated text', () => {
const spineText = 'foo bar foo bar foo bar';
const anchorEarly = {
@ -230,7 +230,7 @@ test.describe('buildEpubLocatorFromChunk', () => {
});
});
test.describe('buildEpubLocator anchored-search regression', () => {
describe('buildEpubLocator anchored-search regression', () => {
// Pins the contract for the server-side resolver path: when called with
// a `chunkText` argument representing the current rendered page, the
// returned locator's `charOffset` is **at or after** the chunk's position
@ -261,7 +261,7 @@ test.describe('buildEpubLocator anchored-search regression', () => {
});
});
test.describe('findSegmentOffset fallback contract', () => {
describe('findSegmentOffset fallback contract', () => {
// These tests pin the documented behavior of `findSegmentOffset`. The
// from-start fallback is correct for single-shot lookups but **wrong** in
// a monotonic per-sentence walk — `resolveMonotonicSentenceOffsets` exists
@ -278,7 +278,7 @@ test.describe('findSegmentOffset fallback contract', () => {
});
});
test.describe('resolveMonotonicSentenceOffsets', () => {
describe('resolveMonotonicSentenceOffsets', () => {
test('returns monotonically non-decreasing offsets for sentences in order', () => {
const spineText = 'The cat sat on the mat. The dog barked loudly. Then it was quiet.';
const sentences = [
@ -347,7 +347,7 @@ test.describe('resolveMonotonicSentenceOffsets', () => {
});
});
test.describe('getSpineItemPlainText shape handling', () => {
describe('getSpineItemPlainText shape handling', () => {
// Regression: epubjs's `Section.load()` resolves to the spine item's
// `<html>` Element (NOT a Document). Reading `loaded.body` directly was
// returning undefined for every spine item, so spineText came back ''

View file

@ -1,4 +1,4 @@
import { expect, test } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import {
buildMonotonicWordToTokenMap,
@ -27,7 +27,7 @@ const alignmentWords = (words: string[]): TTSSentenceAlignment['words'] =>
charEnd: word.length,
}));
test.describe('EPUB word highlight mapping', () => {
describe('EPUB word highlight mapping', () => {
test('tokenizes canonical segment words with source offsets', () => {
const tokens = tokenizeCanonicalSegment(segment('"Hello," she said.', 12));

View file

@ -1,7 +1,7 @@
import { expect, test } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import { iconsGridStyle, maxColumnsForIconGrid } from '../../src/components/doclist/views/iconsGrid';
test.describe('icons grid layout', () => {
describe('icons grid layout', () => {
test('calculates max columns from width and icon size', () => {
expect(maxColumnsForIconGrid('md', 136)).toBe(1);
expect(maxColumnsForIconGrid('md', 300)).toBe(2);

View file

@ -1,8 +1,8 @@
import { expect, test } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import { getMaxVoicesForProvider } from '../../src/lib/shared/kokoro';
test.describe('kokoro voice limits', () => {
describe('kokoro voice limits', () => {
test('keeps Replicate single-voice even for Kokoro models', () => {
expect(getMaxVoicesForProvider('replicate', 'kokoro')).toBe(1);
expect(getMaxVoicesForProvider('replicate', 'hexgrad/Kokoro-82M')).toBe(1);

View file

@ -1,4 +1,4 @@
import { test, expect } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import {
preprocessSentenceForAudio,
splitTextToTtsBlocks,
@ -18,7 +18,7 @@ const expectNormalizedBlocks = (blocks: string[], maxLen = Number.POSITIVE_INFIN
}
};
test.describe('preprocessSentenceForAudio', () => {
describe('preprocessSentenceForAudio', () => {
test('normalizes common extraction artifacts', () => {
const cases: Array<{ input: string; expected: string }> = [
{
@ -45,7 +45,7 @@ test.describe('preprocessSentenceForAudio', () => {
});
});
test.describe('splitTextToTtsBlocks (PDF-oriented)', () => {
describe('splitTextToTtsBlocks (PDF-oriented)', () => {
test('returns [] for empty input', () => {
expect(splitTextToTtsBlocks('')).toEqual([]);
expect(splitTextToTtsBlocks(' ')).toEqual([]);
@ -160,7 +160,7 @@ test.describe('splitTextToTtsBlocks (PDF-oriented)', () => {
});
});
test.describe('splitTextToTtsBlocksEPUB (highlight-friendly)', () => {
describe('splitTextToTtsBlocksEPUB (highlight-friendly)', () => {
test('returns [] for empty input', () => {
expect(splitTextToTtsBlocksEPUB('')).toEqual([]);
expect(splitTextToTtsBlocksEPUB(' ')).toEqual([]);
@ -193,7 +193,7 @@ test.describe('splitTextToTtsBlocksEPUB (highlight-friendly)', () => {
});
});
test.describe('normalizeTextForTts', () => {
describe('normalizeTextForTts', () => {
test('returns a single normalized string without newlines', () => {
const input = 'Hello.\nWorld.\n\nNext paragraph.';
const normalized = normalizeTextForTts(input);

View file

@ -1,8 +1,8 @@
import { expect, test } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import { createCoalescedAsyncRunner, resolveNextOnboardingStep } from '../../src/lib/client/onboarding-flow';
test.describe('onboarding flow resolver', () => {
describe('onboarding flow resolver', () => {
test('resolves deterministic order with privacy first', () => {
const step = resolveNextOnboardingStep({
privacyRequired: true,
@ -78,7 +78,7 @@ test.describe('onboarding flow resolver', () => {
});
});
test.describe('coalesced onboarding runner', () => {
describe('coalesced onboarding runner', () => {
test('coalesces concurrent triggers into one extra rerun', async () => {
let runs = 0;
let nestedRequested = false;

View file

@ -1,8 +1,8 @@
import { expect, test } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import { ONBOARDING_STATE_REGISTRY } from '../../src/lib/shared/onboarding-state';
import { SYNCED_PREFERENCE_KEYS } from '../../src/types/user-state';
test.describe('onboarding state storage scopes', () => {
describe('onboarding state storage scopes', () => {
test('keeps local onboarding flags out of synced server preferences', () => {
expect(ONBOARDING_STATE_REGISTRY.privacyAccepted.scope).toBe('local-dexie');
expect(ONBOARDING_STATE_REGISTRY.firstVisitSettingsOpened.scope).toBe('local-dexie');

View file

@ -1,10 +1,10 @@
import { test, expect } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import {
sha256HexSoftwareFromBytes,
sha256HexFromString
} from '../../src/lib/client/sha256';
test.describe('SHA256 Software Implementation', () => {
describe('SHA256 Software Implementation', () => {
// Known test vectors
// "" -> e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
// "abc" -> ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad

View file

@ -1,11 +1,11 @@
import { test, expect } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import Database from 'better-sqlite3';
import { drizzle } from 'drizzle-orm/better-sqlite3';
import { eq } from 'drizzle-orm';
import { documents } from '../../src/db/schema_sqlite';
import { transferUserDocuments } from '../../src/lib/server/user/claim-data';
test.describe('transferUserDocuments', () => {
describe('transferUserDocuments', () => {
test('moves document rows to new user without PK conflicts', async () => {
process.env.BASE_URL = 'http://localhost:3003';
process.env.AUTH_SECRET = 'test-secret';

View file

@ -1,4 +1,4 @@
import { expect, test } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import {
releaseWarmAudio,
upsertWarmAudioEntry,
@ -23,7 +23,7 @@ function createTrackedAudio() {
return { audio, calls };
}
test.describe('tts warm audio cache helpers', () => {
describe('tts warm audio cache helpers', () => {
test('releaseWarmAudio clears source and invokes teardown methods', () => {
const tracked = createTrackedAudio();
releaseWarmAudio(tracked.audio);

View file

@ -1,4 +1,4 @@
import { expect, test } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import {
completedEpubBoundarySegment,
@ -23,7 +23,7 @@ const segment = (
...overrides,
});
test.describe('EPUB boundary handoff', () => {
describe('EPUB boundary handoff', () => {
test('records only completed segments that span a source boundary', () => {
expect(completedEpubBoundarySegment(segment('same page'))).toBeNull();

View file

@ -1,4 +1,4 @@
import { expect, test } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import type { CanonicalTtsSourceUnit } from '../../src/lib/shared/tts-segment-plan';
import {
@ -6,7 +6,7 @@ import {
selectUpcomingWalkerItems,
} from '../../src/lib/client/epub/tts-epub-preload';
test.describe('EPUB walker preload helpers', () => {
describe('EPUB walker preload helpers', () => {
test('selectUpcomingWalkerItems honors depth as current + (depth-1) upcoming', () => {
const items = [
{ cfi: 'epubcfi(/6/2[;s=a]!/4/2)' }, // same as current after normalization

View file

@ -1,8 +1,8 @@
import { expect, test } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import { extractReplicateAudioUrl } from '../../src/lib/server/tts/generate';
test.describe('replicate output URL extraction', () => {
describe('replicate output URL extraction', () => {
test('returns direct URL string output', () => {
expect(extractReplicateAudioUrl('https://replicate.delivery/audio.mp3')).toBe(
'https://replicate.delivery/audio.mp3'

View file

@ -1,4 +1,4 @@
import { expect, test } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import {
compareSegmentLocators,
locatorGroupKey,
@ -25,7 +25,7 @@ const epubLocator = (
...(cfi ? { cfi } : {}),
});
test.describe('locatorGroupKey', () => {
describe('locatorGroupKey', () => {
test('groups stable EPUB locators by spine index and href', () => {
expect(locatorGroupKey(epubLocator('OEBPS/ch02.xhtml', 2, 0))).toBe('epub:2:OEBPS/ch02.xhtml');
// Different charOffset, same spine → same group (sidebar bucket is chapter-sized).
@ -55,7 +55,7 @@ test.describe('locatorGroupKey', () => {
});
});
test.describe('locatorIdentityKey', () => {
describe('locatorIdentityKey', () => {
test('two EPUB rows in the same chapter at different charOffsets get distinct identity keys', () => {
// This is the bug that caused page-N rows to swallow page-(N+1) rows in
// the server-side manifest aggregator: the previous code keyed by the
@ -82,7 +82,7 @@ test.describe('locatorIdentityKey', () => {
});
});
test.describe('compareSegmentLocators', () => {
describe('compareSegmentLocators', () => {
test('orders EPUB rows by spineIndex then charOffset numerically', () => {
// Spine 10 must come AFTER spine 2 — a lexicographic compare would
// wrongly place "10" before "2".
@ -137,7 +137,7 @@ test.describe('compareSegmentLocators', () => {
});
});
test.describe('type guards', () => {
describe('type guards', () => {
test('isStableEpubLocator requires all spine fields', () => {
expect(isStableEpubLocator(epubLocator('a.xhtml', 0, 0))).toBe(true);
expect(isStableEpubLocator({ readerType: 'epub', spineHref: 'a.xhtml' })).toBe(false);
@ -159,7 +159,7 @@ test.describe('type guards', () => {
});
});
test.describe('normalizeEpubLocationToken', () => {
describe('normalizeEpubLocationToken', () => {
test('strips whitespace and step markers but preserves identity', () => {
const a = normalizeEpubLocationToken('epubcfi(/6/8!/4:0)');
const b = normalizeEpubLocationToken(' epubcfi(/6/8[;s=a]!/4:0) ');

View file

@ -1,4 +1,4 @@
import { expect, test } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import {
REPLICATE_KOKORO_82M_VERSIONED_MODEL,
@ -13,7 +13,7 @@ import { resolveReplicateVoiceInputKey, resolveVoices } from '../../src/lib/serv
import { applyConfigUpdate, getVoicePreferenceKey } from '../../src/lib/client/config/updates';
import { buildSyncedPreferencePatch } from '../../src/lib/client/config/preferences';
test.describe('tts provider catalog', () => {
describe('tts provider catalog', () => {
test('resolves provider models with fixed Deepinfra catalog', () => {
expect(resolveProviderModels('openai').map((model) => model.id)).toEqual([
'tts-1',
@ -389,7 +389,7 @@ test.describe('tts provider catalog', () => {
});
});
test.describe('config helpers', () => {
describe('config helpers', () => {
test('persists voice preferences by provider/model pair', () => {
expect(getVoicePreferenceKey('openai', 'tts-1')).toBe('openai:tts-1');

View file

@ -1,10 +1,10 @@
import { expect, test } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import {
buildSegmentAudioFallbackUrl,
resolveSegmentAudioUrls,
} from '../../src/lib/server/tts/segment-audio-urls';
test.describe('tts segment audio url resolution', () => {
describe('tts segment audio url resolution', () => {
test('builds fallback URL with encoded params', () => {
const url = buildSegmentAudioFallbackUrl('doc 1', 'segment/2');
expect(url).toBe('/api/tts/segments/audio/fallback?documentId=doc%201&segmentId=segment%2F2');

View file

@ -1,8 +1,8 @@
import { expect, test } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import { planCanonicalTtsSegments } from '../../src/lib/shared/tts-segment-plan';
test.describe('planCanonicalTtsSegments leading context handling', () => {
describe('planCanonicalTtsSegments leading context handling', () => {
test('clean boundary: splits block so current source keeps its first sentence', () => {
const currentSourceKey = 'str:epubcfi(/6/10!/4/2)';
const plan = planCanonicalTtsSegments([

View file

@ -1,8 +1,8 @@
import { expect, test } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import { buildSegmentKey, buildSegmentKeyPrefix, planCanonicalTtsSegments } from '../../src/lib/shared/tts-segment-plan';
test.describe('planCanonicalTtsSegments', () => {
describe('planCanonicalTtsSegments', () => {
test('emits a cross-boundary segment once and assigns it to the source where it starts', () => {
const plan = planCanonicalTtsSegments([
{
@ -223,7 +223,7 @@ test.describe('planCanonicalTtsSegments', () => {
});
});
test.describe('buildSegmentKeyPrefix / buildSegmentKey contract', () => {
describe('buildSegmentKeyPrefix / buildSegmentKey contract', () => {
// These keys are the bridge between persistence and the sidebar's merge of
// synth rows with manifest rows. Both sides must produce identical keys for
// the same `(documentId, readerType, text)` triple, or the sidebar will

View file

@ -1,4 +1,4 @@
import { expect, test } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import {
isRetryableSegmentStatus,
@ -6,7 +6,7 @@ import {
shouldDeferSegmentRetry,
} from '../../src/lib/client/tts/segment-retry-policy';
test.describe('tts segment retry policy', () => {
describe('tts segment retry policy', () => {
test('marks pending/error statuses as retryable', () => {
expect(isRetryableSegmentStatus('pending')).toBe(true);
expect(isRetryableSegmentStatus('error')).toBe(true);

View file

@ -1,4 +1,4 @@
import { expect, test } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import {
buildSegmentAudioCacheHeaders,
normalizeAudioByteRangeHeader,
@ -6,7 +6,7 @@ import {
TTS_SEGMENT_FALLBACK_CACHE_CONTROL,
} from '../../src/lib/server/tts/segments-audio';
test.describe('tts segment audio cache headers', () => {
describe('tts segment audio cache headers', () => {
test('builds fallback cache headers', () => {
expect(buildSegmentAudioCacheHeaders()).toEqual({
'Cache-Control': TTS_SEGMENT_FALLBACK_CACHE_CONTROL,

View file

@ -1,4 +1,4 @@
import { expect, test } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import {
compareManifestSegments,
decodeManifestCursor,
@ -6,8 +6,9 @@ import {
encodeManifestCursor,
parseManifestPageSize,
} from '../../src/lib/server/tts/segments-manifest';
import { locatorGroupKey, locatorIdentityKey } from '../../src/lib/shared/tts-locator';
test.describe('tts segments manifest helpers', () => {
describe('tts segments manifest helpers', () => {
test('dedupe prefers completed variant over newer pending for same settings key', () => {
const variants = dedupeManifestVariants([
{
@ -125,8 +126,6 @@ test.describe('tts segments manifest helpers', () => {
const sorted = rows.sort(compareManifestSegments);
// Recompute groupKey using the production helper to assert the actual
// grouping behavior rather than the test's hand-labeled groupKey.
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { locatorGroupKey } = require('../../src/lib/shared/tts-locator');
const groupKeys = sorted.map((row) => locatorGroupKey(row.locator));
expect(groupKeys[0]).toBe(groupKeys[1]); // both ch02
expect(groupKeys[2]).not.toBe(groupKeys[0]); // ch03 is its own group
@ -141,9 +140,6 @@ test.describe('tts segments manifest helpers', () => {
// rows visually "disappear" when later pages were generated.
//
// The fix uses `locatorIdentityKey` which includes charOffset for EPUB.
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { locatorIdentityKey, locatorGroupKey } = require('../../src/lib/shared/tts-locator');
const page1Row0 = { readerType: 'epub' as const, spineHref: 'OEBPS/ch02.xhtml', spineIndex: 2, charOffset: 0 };
const page2Row0 = { readerType: 'epub' as const, spineHref: 'OEBPS/ch02.xhtml', spineIndex: 2, charOffset: 2048 };

View file

@ -1,4 +1,4 @@
import { expect, test } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import {
buildProportionalAlignment,
buildTtsSegmentEntryId,
@ -11,7 +11,7 @@ import {
projectSegmentLocator,
} from '../../src/lib/server/tts/segments';
test.describe('tts segment helpers', () => {
describe('tts segment helpers', () => {
test('builds stable settings hash', () => {
const a = buildTtsSegmentSettingsHash({
providerRef: 'openai',

View file

@ -1,4 +1,4 @@
import { expect, test } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import { resolveTtsSettingsViewModel } from '../../src/lib/client/settings/tts-settings';
import type { SharedProviderEntry } from '../../src/hooks/useSharedProviders';
@ -20,7 +20,7 @@ const SHARED: SharedProviderEntry[] = [
},
];
test.describe('resolveTtsSettingsViewModel (admin/shared modes)', () => {
describe('resolveTtsSettingsViewModel (admin/shared modes)', () => {
test('keeps default-openai selection when that shared provider exists', () => {
const vm = resolveTtsSettingsViewModel({
providerRef: 'default-openai',

View file

@ -1,8 +1,8 @@
import { expect, test } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import { buildWalkerThemeRules } from '../../src/lib/client/epub/walker-theme';
test.describe('walker theme rules', () => {
describe('walker theme rules', () => {
test('always includes foreground/background colors', () => {
const rules = buildWalkerThemeRules({
foreground: 'rgb(1, 2, 3)',

View file

@ -1,9 +1,9 @@
import { test, expect } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import {
mapWordsToSentenceOffsets,
} from '@openreader/compute-core';
test.describe('whisper alignment mapping', () => {
describe('whisper alignment mapping', () => {
test('maps words to sentence offsets with punctuation and repeated spaces', () => {
const aligned = mapWordsToSentenceOffsets('Hello, world again.', [
{ word: 'Hello', start: 0, end: 0.25 },

View file

@ -1,4 +1,4 @@
import { test, expect } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import { buildGoertzelCoefficients, goertzelPower } from '@openreader/compute-core';
function dftPower(samples: Float32Array, k: number): number {
@ -13,7 +13,7 @@ function dftPower(samples: Float32Array, k: number): number {
return (re * re) + (im * im);
}
test.describe('whisper spectral helpers', () => {
describe('whisper spectral helpers', () => {
test('goertzel power matches direct DFT for non-power-of-two frame size', () => {
const frameSize = 400;
const bins = 201;

View file

@ -1,11 +1,11 @@
import { test, expect } from '@playwright/test';
import { describe, expect, test } from 'vitest';
import * as ort from 'onnxruntime-node';
import {
buildWordsFromTimestampedTokens,
extractTokenStartTimestamps,
} from '@openreader/compute-core';
test.describe('whisper token timestamp alignment', () => {
describe('whisper token timestamp alignment', () => {
test('extracts monotonic token timestamps from cross-attention maps', () => {
const seqLen = 6;
const frames = 10;