mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-09-10 15:05:40 +00:00
b759898d5b
The localIndex IndexedDB persistence layer ran as a no-op under vitest — there is no IndexedDB in the node test environment, so `isSupported()` returned false and every persist/hydrate call short-circuited. The entire layer, and BUG-2609's seq-guard fix, shipped on live-browser evidence runs only (the #1148 finding). This adds the harness that makes it testable, the prerequisite for unit 2's order-and-merge regression matrix. - fake-indexeddb dev dependency (exact-pinned). - A dedicated `idb` vitest project (glob `*.idb.test.ts`, node env) whose setup installs fake-indexeddb's globals and a fresh IDBFactory per test. It self-disables when the dep can't be resolved — mirroring the jsdom project — so a symlinked worktree without it keeps `npm run test` green, and CI activates it once installed. The idb glob is excluded from the node project so the persistence layer can't no-op there and pass vacuously. - Harness helpers unit 2 builds on: a second cross-tab connection to the same database (2635), a v1-database seed + higher-format-version reopen + downgrade VersionError (the v1→v2 migration exercise), a fresh-module loader that clears the connection cache, and raw ground-truth reads. `harnessDbName` mirrors the module's `dbName` exactly, pinned by a test so a drift can't make assertions read an empty sibling database. - BUG-2609's evidence run is ported as a deterministic sequential regression: a newer delta commits its atomic rows+cursor transaction, then a stale older-seq snapshot lands last and is refused (IDB serializes overlapping transactions, so no interleaving control is needed). Plus a raw serialization characterization pinning that platform guarantee. No production code changes. Claude-Session: https://claude.ai/code/session_017jD6t1zjxGSq47SQpZfp1V
155 lines
6.2 KiB
TypeScript
155 lines
6.2 KiB
TypeScript
import { defineConfig } from 'vitest/config';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { createRequire } from 'node:module';
|
|
import { realpathSync } from 'node:fs';
|
|
|
|
// Two-project vitest setup (TASK-2081 / PLAN-1984):
|
|
//
|
|
// - `node` — the existing pure-TS unit suite. Plain node environment, no
|
|
// Svelte plugin (fast; matches the pre-TASK-2081 behavior).
|
|
// - `jsdom` — `.svelte` component + `.svelte.ts` rune-module tests. Runs in a
|
|
// browser-like DOM with the Svelte plugin so runes/components
|
|
// compile, and aliases `$app/environment` to a browser=true mock.
|
|
//
|
|
// Split by filename: `*.svelte.test.ts` routes to jsdom, everything else
|
|
// (`*.test.ts`) stays on node. Keeping the node suite out of jsdom avoids
|
|
// slowing/altering the pure-logic tests.
|
|
//
|
|
// The jsdom project's deps (`jsdom`, `@testing-library/svelte`,
|
|
// `@testing-library/jest-dom`) are declared in package.json but may be absent
|
|
// until `npm install` runs (worktrees share a read-only node_modules). When
|
|
// they're missing we register ONLY the node project, so `npm run test` keeps
|
|
// the existing suite green; once installed, the jsdom project activates
|
|
// automatically and `npm run test` runs BOTH.
|
|
|
|
const require = createRequire(import.meta.url);
|
|
function canResolve(id: string): boolean {
|
|
try {
|
|
require.resolve(id);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const projectRoot = fileURLToPath(new URL('.', import.meta.url));
|
|
const $lib = fileURLToPath(new URL('./src/lib', import.meta.url));
|
|
const appEnvironmentMock = fileURLToPath(new URL('./src/test/mocks/app-environment.ts', import.meta.url));
|
|
const appStateMock = fileURLToPath(new URL('./src/test/mocks/app-state.ts', import.meta.url));
|
|
const appNavigationMock = fileURLToPath(new URL('./src/test/mocks/app-navigation.ts', import.meta.url));
|
|
|
|
// Agent worktrees symlink `web/node_modules` to the main checkout's
|
|
// node_modules rather than `npm ci`-ing a copy (running npm ci THROUGH the
|
|
// symlink deletes the shared tree — see CLAUDE.md's "Working in a git
|
|
// worktree" section, which also covers the svelte-kit sync prerequisite).
|
|
// Vite's dev-server fs-access guard checks a
|
|
// requested file's REALPATH against `server.fs.allow`, which defaults to the
|
|
// project root and its ancestors — a node_modules symlink that resolves
|
|
// outside that root (the worktree lives under a sibling directory tree) gets
|
|
// denied, breaking every jsdom-project test that imports a real package
|
|
// (e.g. `@testing-library/svelte/vitest`) with a confusing "does the file
|
|
// exist?" error even though it does. Explicitly allowing the resolved
|
|
// realpath fixes worktrees without changing behavior for a normal checkout,
|
|
// where the realpath is just `<project>/node_modules` — already inside the
|
|
// default allow-list. `projectRoot` MUST stay in the allow list alongside
|
|
// it — setting `server.fs.allow` REPLACES Vite's default (project root +
|
|
// ancestors), so omitting the root here would deny access to ordinary
|
|
// project source files (Codex review).
|
|
const nodeModulesRealPath = (() => {
|
|
try {
|
|
return realpathSync(fileURLToPath(new URL('./node_modules', import.meta.url)));
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
})();
|
|
|
|
const browserTestDepsInstalled =
|
|
canResolve('jsdom') &&
|
|
canResolve('@testing-library/svelte') &&
|
|
canResolve('@testing-library/jest-dom') &&
|
|
canResolve('@sveltejs/vite-plugin-svelte');
|
|
|
|
// The `idb` project needs fake-indexeddb (PLAN-2636 unit 1). Like the jsdom
|
|
// deps it's declared in package.json but absent in a symlinked worktree until
|
|
// npm install runs; when it can't be resolved we register only the other
|
|
// projects so `npm run test` stays green, and the idb suite activates
|
|
// automatically once the dep is present (mirrors the jsdom self-disable).
|
|
const idbTestDepInstalled = canResolve('fake-indexeddb');
|
|
|
|
const BROWSER_TEST_GLOB = 'src/**/*.svelte.test.ts';
|
|
// IDB-backed persistence tests. The `.idb.test.ts` suffix routes them to the
|
|
// dedicated `idb` project; they must be excluded from the node project (which
|
|
// has no indexedDB — the persistence layer would silently no-op there and the
|
|
// test would pass vacuously) the same way the svelte glob is.
|
|
const IDB_TEST_GLOB = 'src/**/*.idb.test.ts';
|
|
|
|
const nodeProject = {
|
|
resolve: { alias: { $lib } },
|
|
test: {
|
|
name: 'node',
|
|
environment: 'node',
|
|
include: ['src/**/*.test.ts'],
|
|
// The jsdom / idb projects own these; they'd blow up or no-op in the
|
|
// plain node env.
|
|
exclude: [BROWSER_TEST_GLOB, IDB_TEST_GLOB],
|
|
},
|
|
};
|
|
|
|
const idbProject = {
|
|
resolve: { alias: { $lib } },
|
|
server: nodeModulesRealPath
|
|
? { fs: { allow: [projectRoot, nodeModulesRealPath] } }
|
|
: undefined,
|
|
test: {
|
|
name: 'idb',
|
|
// fake-indexeddb runs in plain node — no DOM needed. Its setup installs
|
|
// a fresh in-memory IndexedDB per test.
|
|
environment: 'node',
|
|
include: [IDB_TEST_GLOB],
|
|
setupFiles: ['./src/test/setup-idb.ts'],
|
|
},
|
|
};
|
|
|
|
export default defineConfig(async () => {
|
|
const projects: Record<string, unknown>[] = [nodeProject];
|
|
|
|
if (idbTestDepInstalled) {
|
|
projects.push(idbProject);
|
|
}
|
|
|
|
if (browserTestDepsInstalled) {
|
|
// Dynamic import so a missing plugin can never crash config loading.
|
|
const { svelte } = await import('@sveltejs/vite-plugin-svelte');
|
|
const { svelteTesting } = await import('@testing-library/svelte/vite');
|
|
projects.push({
|
|
plugins: [svelte(), svelteTesting()],
|
|
resolve: {
|
|
alias: {
|
|
$lib,
|
|
// No SvelteKit plugin in this project, so provide `$app/environment`
|
|
// and `$app/state` — without a provider these don't just come back
|
|
// undefined, they fail to RESOLVE, which is a load-time error for
|
|
// any component that imports them (and one `vi.mock` can't rescue,
|
|
// since resolution happens first).
|
|
'$app/environment': appEnvironmentMock,
|
|
'$app/state': appStateMock,
|
|
// `$app/navigation` for the same reason (TASK-2430) — without it
|
|
// Sidebar / TopBar / PaneHost can't even be IMPORTED under jsdom.
|
|
'$app/navigation': appNavigationMock,
|
|
},
|
|
},
|
|
server: nodeModulesRealPath
|
|
? { fs: { allow: [projectRoot, nodeModulesRealPath] } }
|
|
: undefined,
|
|
test: {
|
|
name: 'jsdom',
|
|
environment: 'jsdom',
|
|
include: [BROWSER_TEST_GLOB],
|
|
setupFiles: ['./src/test/setup-jsdom.ts'],
|
|
},
|
|
});
|
|
}
|
|
|
|
return { test: { projects } };
|
|
});
|