Initial Commit
This commit is contained in:
@@ -0,0 +1,234 @@
|
||||
import { IDBFactory } from "fake-indexeddb";
|
||||
import type {
|
||||
IdbDatabaseLike,
|
||||
IdbFactoryLike,
|
||||
IdbObjectStoreLike,
|
||||
IdbOpenDbRequestLike,
|
||||
IdbRequestLike,
|
||||
IdbTransactionLike,
|
||||
} from "../src/types/idb-like.types.js";
|
||||
|
||||
let dbCounter = 0;
|
||||
|
||||
/**
|
||||
* Wrap a `fake-indexeddb` IDBFactory in our structural surface.
|
||||
*/
|
||||
export function fakeFactory(): IdbFactoryLike {
|
||||
const real = new IDBFactory();
|
||||
return {
|
||||
open: (n, v) => real.open(n, v) as unknown as IdbOpenDbRequestLike,
|
||||
};
|
||||
}
|
||||
|
||||
export function uniqueDbName(prefix = "idb"): string {
|
||||
dbCounter++;
|
||||
return `${prefix}-${dbCounter}-${Date.now()}-${Math.random()}`;
|
||||
}
|
||||
|
||||
interface InjectOptions {
|
||||
failPutOn?: string;
|
||||
failClearWith?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously deliver a failure to a request via its onerror handler.
|
||||
* Used by the synthetic IDB factory below — we wait one macrotask so the
|
||||
* caller has time to install onsuccess/onerror handlers before we trigger
|
||||
* the failure.
|
||||
*/
|
||||
function deliverError(req: IdbRequestLike, errorName: string): void {
|
||||
setTimeout(() => {
|
||||
(req as unknown as { error: DOMException }).error = Object.assign(
|
||||
new Error("induced"),
|
||||
{ name: errorName }
|
||||
) as unknown as DOMException;
|
||||
req.onerror?.(new Event("error"));
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function deliverSuccess(req: IdbRequestLike, result: unknown): void {
|
||||
setTimeout(() => {
|
||||
(req as unknown as { result: unknown }).result = result;
|
||||
req.onsuccess?.(new Event("success"));
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a fully synthetic IdbFactoryLike that supports get/openCursor
|
||||
* over an in-memory Map but fails put / clear with a configurable
|
||||
* DOMException name. This is a REAL implementation of the structural
|
||||
* IdbFactoryLike interface — no mocks, no monkey-patching.
|
||||
*/
|
||||
export function injectingFactory(options: InjectOptions): IdbFactoryLike {
|
||||
const data = new Map<string, unknown>();
|
||||
|
||||
function makeStore(): IdbObjectStoreLike {
|
||||
return {
|
||||
put(value) {
|
||||
const req: IdbRequestLike = {
|
||||
result: undefined,
|
||||
error: null,
|
||||
onsuccess: null,
|
||||
onerror: null,
|
||||
};
|
||||
if (options.failPutOn !== undefined) {
|
||||
deliverError(req, options.failPutOn);
|
||||
} else {
|
||||
const v = value as { key: string };
|
||||
data.set(v.key, v);
|
||||
deliverSuccess(req, undefined);
|
||||
}
|
||||
return req;
|
||||
},
|
||||
get(key) {
|
||||
const req: IdbRequestLike = {
|
||||
result: undefined,
|
||||
error: null,
|
||||
onsuccess: null,
|
||||
onerror: null,
|
||||
};
|
||||
deliverSuccess(req, data.get(key as string));
|
||||
return req;
|
||||
},
|
||||
delete(key) {
|
||||
const req: IdbRequestLike = {
|
||||
result: undefined,
|
||||
error: null,
|
||||
onsuccess: null,
|
||||
onerror: null,
|
||||
};
|
||||
data.delete(key as string);
|
||||
deliverSuccess(req, undefined);
|
||||
return req;
|
||||
},
|
||||
clear() {
|
||||
const req: IdbRequestLike = {
|
||||
result: undefined,
|
||||
error: null,
|
||||
onsuccess: null,
|
||||
onerror: null,
|
||||
};
|
||||
if (options.failClearWith !== undefined) {
|
||||
deliverError(req, options.failClearWith);
|
||||
} else {
|
||||
data.clear();
|
||||
deliverSuccess(req, undefined);
|
||||
}
|
||||
return req;
|
||||
},
|
||||
count() {
|
||||
const req: IdbRequestLike = {
|
||||
result: data.size,
|
||||
error: null,
|
||||
onsuccess: null,
|
||||
onerror: null,
|
||||
};
|
||||
deliverSuccess(req, data.size);
|
||||
return req;
|
||||
},
|
||||
openCursor() {
|
||||
const req: IdbRequestLike = {
|
||||
result: null,
|
||||
error: null,
|
||||
onsuccess: null,
|
||||
onerror: null,
|
||||
};
|
||||
// Emit one cursor event per entry, then a final null.
|
||||
const entries = [...data.entries()];
|
||||
setTimeout(() => {
|
||||
let i = 0;
|
||||
const step = (): void => {
|
||||
if (i >= entries.length) {
|
||||
(req as unknown as { result: unknown }).result =
|
||||
null;
|
||||
req.onsuccess?.(new Event("success"));
|
||||
return;
|
||||
}
|
||||
const [_key, val] = entries[i] as [string, unknown];
|
||||
i++;
|
||||
const cursor = {
|
||||
value: val,
|
||||
key: _key,
|
||||
continue: () => step(),
|
||||
};
|
||||
(req as unknown as { result: unknown }).result = cursor;
|
||||
req.onsuccess?.(new Event("success"));
|
||||
};
|
||||
step();
|
||||
}, 0);
|
||||
return req;
|
||||
},
|
||||
createIndex() {
|
||||
return undefined;
|
||||
},
|
||||
index() {
|
||||
return undefined;
|
||||
},
|
||||
} satisfies IdbObjectStoreLike;
|
||||
}
|
||||
|
||||
function makeTransaction(): IdbTransactionLike {
|
||||
const store = makeStore();
|
||||
const tx: IdbTransactionLike = {
|
||||
objectStore() {
|
||||
return store;
|
||||
},
|
||||
oncomplete: null,
|
||||
onerror: null,
|
||||
onabort: null,
|
||||
abort() {
|
||||
/* no-op */
|
||||
},
|
||||
error: null,
|
||||
};
|
||||
// Auto-complete after all queued microtasks settle.
|
||||
setTimeout(() => tx.oncomplete?.(new Event("complete")), 5);
|
||||
return tx;
|
||||
}
|
||||
|
||||
function makeDb(): IdbDatabaseLike {
|
||||
return {
|
||||
name: "synthetic",
|
||||
version: 1,
|
||||
objectStoreNames: { contains: () => true },
|
||||
close() {
|
||||
/* no-op */
|
||||
},
|
||||
transaction() {
|
||||
return makeTransaction();
|
||||
},
|
||||
createObjectStore() {
|
||||
return makeStore();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
open() {
|
||||
const req: IdbOpenDbRequestLike = {
|
||||
result: makeDb(),
|
||||
error: null,
|
||||
onsuccess: null,
|
||||
onerror: null,
|
||||
onupgradeneeded: null,
|
||||
onblocked: null,
|
||||
};
|
||||
setTimeout(() => req.onsuccess?.(new Event("success")), 0);
|
||||
return req;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an `IdbFactoryLike` whose `open` method throws synchronously
|
||||
* with a controlled DOMException name.
|
||||
*/
|
||||
export function syncThrowingFactory(name: string): IdbFactoryLike {
|
||||
return {
|
||||
open() {
|
||||
throw Object.assign(new Error("induced"), { name });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export type { IdbFactoryLike, IdbRequestLike };
|
||||
@@ -0,0 +1,4 @@
|
||||
// Polyfills for the test environment. The injectable IDBFactory adapter in
|
||||
// `idb-adapter` means the package itself never reaches for `globalThis.indexedDB`
|
||||
// during tests — tests pass a real `fake-indexeddb` factory directly to the
|
||||
// constructor. No globals are mutated here.
|
||||
Reference in New Issue
Block a user