fix(enrichment-block): migrate $app/stores → $app/state to eliminate birpc race

The async vi.mock factory in EnrichmentBlock.svelte.spec.ts performed an
`await import(...)` in its body — the same mechanism #535/#546 fixed for
pdfjs-dist. Issue #553: when Chromium's playwright route handler fetches
the mocked module after the worker's birpc channel has closed, the
factory's RPC roundtrip raises `[birpc] rpc is closed, cannot call
"resolveManualMock"` and the run exits 1.

Migrate EnrichmentBlock from the deprecated `$app/stores.navigating`
(store) to the modern `$app/state.navigating` (reactive proxy). The
spec uses vi.hoisted + a sync vi.mock factory with a getter that defers
the read — no dynamic import in the factory body. Delete the now-unused
__mocks__/navigatingStore.ts.

Fix path applied: $app/state migration (Markus's recommendation /
Felix's Path 2). See ADR-012.

Refs #553

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-05-12 22:00:44 +02:00
parent 89860403f6
commit 9f1b8b4215
3 changed files with 18 additions and 17 deletions

View File

@@ -2,19 +2,23 @@ import { describe, it, expect, afterEach, vi } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page } from 'vitest/browser';
// The store must live in a separate module because vi.mock factories are
// hoisted and cannot reference top-level variables defined in this file.
import { navigatingStore } from './__mocks__/navigatingStore';
import EnrichmentBlock from './EnrichmentBlock.svelte';
vi.mock('$app/stores', async () => {
const mod = await import('./__mocks__/navigatingStore');
return { navigating: mod.navigatingStore };
});
// Hoist the mutable navigation reference so vi.mock's factory (also hoisted)
// can read it via a getter. Sync factory, no dynamic import: ADR-012 invariant.
const { mockNavigating } = vi.hoisted(() => ({
mockNavigating: { type: null as string | null }
}));
vi.mock('$app/state', () => ({
get navigating() {
return mockNavigating;
}
}));
afterEach(() => {
cleanup();
navigatingStore.set(null);
mockNavigating.type = null;
});
type Doc = { id: string; title: string; uploadedAt: string };
@@ -65,8 +69,8 @@ describe('EnrichmentBlock', () => {
await expect.element(page.getByTestId('dashboard-needs-metadata')).toBeInTheDocument();
});
it('renders the skeleton when $navigating is active and topDocs is empty', async () => {
navigatingStore.set({ type: 'link' });
it('renders the skeleton when navigation is active and topDocs is empty', async () => {
mockNavigating.type = 'link';
render(EnrichmentBlock, {
topDocs: [],
totalCount: 0,
@@ -76,8 +80,8 @@ describe('EnrichmentBlock', () => {
await expect.element(page.getByTestId('enrichment-block-skeleton')).toBeInTheDocument();
});
it('does not render the skeleton when topDocs is non-empty even during $navigating', async () => {
navigatingStore.set({ type: 'link' });
it('does not render the skeleton when topDocs is non-empty even during navigation', async () => {
mockNavigating.type = 'link';
render(EnrichmentBlock, {
topDocs: [doc('d1')],
totalCount: 1,