Files
actual/packages/api/test/setup.browser.ts
github-actions[bot] e161eefc02 [AI] api: run integration smoke test under browser jsdom
Adds setup.browser.ts with fake-indexeddb, a fetch polyfill that points
sql.js WASM and loot-core's data-file-index fetches at on-disk files,
and wires the browser Vite config to use jsdom. The shared integration
spec now gates the full CRUD roundtrip behind __API_FULL_SUITE__ (set
only in Node) because absurd-sql's worker + SharedArrayBuffer
requirement is not met under jsdom; the browser smoke test verifies
that init returns a usable handle. Full-flow browser coverage moves
to the playground app in the next phase.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 22:02:41 +01:00

95 lines
3.0 KiB
TypeScript

import * as fs from 'fs/promises';
import * as path from 'path';
// fake-indexeddb must be the first import so IDBRequest/IDBDatabase/etc. are
// on globalThis before any browser-platform module that references them.
import 'fake-indexeddb/auto';
// Vitest defaults NODE_ENV to 'test'; loot-core's browser fs short-circuits
// its init in that case, which skips populating the migrations / default-db
// that the api needs. Force 'development' so the real init path runs.
process.env.NODE_ENV = 'development';
// sqlite init reads PUBLIC_URL as the base for sql.js WASM locateFile; under
// jsdom there's no webpack-injected value. Seed one, then resolve the
// resulting paths to disk via the fetch polyfill below.
process.env.PUBLIC_URL = '/';
if (typeof globalThis.structuredClone !== 'function') {
globalThis.structuredClone = (value: unknown) =>
JSON.parse(JSON.stringify(value));
}
const lootCoreRoot = path.join(__dirname, '..', '..', 'loot-core');
const sqlJsDist = path.join(
__dirname,
'..',
'..',
'..',
'node_modules',
'@jlongster',
'sql.js',
'dist',
);
async function dataFileIndex(): Promise<string> {
const lines: string[] = ['default-db.sqlite'];
const migDir = path.join(lootCoreRoot, 'migrations');
const entries = await fs.readdir(migDir);
for (const name of entries.sort()) {
lines.push('migrations/' + name);
}
return lines.join('\n') + '\n';
}
const originalFetch = globalThis.fetch;
globalThis.fetch = (async (
input: RequestInfo | URL,
init?: RequestInit,
): Promise<Response> => {
const urlStr =
typeof input === 'string'
? input
: input instanceof URL
? input.toString()
: input.url;
const pathname = urlStr
.replace(/^https?:\/\/[^/]+/, '')
.replace(/^file:\/\//, '');
if (pathname === '/data-file-index.txt') {
return new Response(await dataFileIndex(), { status: 200 });
}
let diskPath: string | null = null;
if (
pathname === '/data/default-db.sqlite' ||
pathname === '/default-db.sqlite'
) {
diskPath = path.join(lootCoreRoot, 'default-db.sqlite');
} else if (pathname.startsWith('/data/migrations/')) {
diskPath = path.join(lootCoreRoot, pathname.replace(/^\/data\//, ''));
} else if (pathname.startsWith('/migrations/')) {
diskPath = path.join(lootCoreRoot, pathname);
} else if (pathname.endsWith('.wasm')) {
diskPath = path.join(sqlJsDist, path.basename(pathname));
}
if (diskPath) {
const buf = await fs.readFile(diskPath);
const headers: Record<string, string> = {};
if (pathname.endsWith('.wasm'))
{headers['Content-Type'] = 'application/wasm';}
return new Response(new Uint8Array(buf), { status: 200, headers });
}
return originalFetch(input as RequestInfo | URL, init);
}) as typeof fetch;
// /documents exists in the Emscripten virtual FS after fs.init(); /blobs or
// any other top-level dir would fail the non-recursive FS.mkdir in createBudget.
globalThis.__API_DATA_DIR__ = '/documents';
global.IS_TESTING = true;