aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/idb-bridge/src/idb-wpt-ported/idbcursor-reused.ts76
-rw-r--r--packages/idb-bridge/src/idb-wpt-ported/idbobjectstore-rename-store.test.ts504
-rw-r--r--packages/idb-bridge/src/idb-wpt-ported/wptsupport.ts265
3 files changed, 843 insertions, 2 deletions
diff --git a/packages/idb-bridge/src/idb-wpt-ported/idbcursor-reused.ts b/packages/idb-bridge/src/idb-wpt-ported/idbcursor-reused.ts
new file mode 100644
index 000000000..44a647dc8
--- /dev/null
+++ b/packages/idb-bridge/src/idb-wpt-ported/idbcursor-reused.ts
@@ -0,0 +1,76 @@
+import test from "ava";
+import { createdb } from "./wptsupport";
+
+test("WPT idbcursor-reused.htm", async (t) => {
+ await new Promise<void>((resolve, reject) => {
+ var db: any;
+ var open_rq = createdb(t);
+
+ open_rq.onupgradeneeded = function (e: any) {
+ db = e.target.result;
+ var os = db.createObjectStore("test");
+
+ os.add("data", "k");
+ os.add("data2", "k2");
+ };
+
+ open_rq.onsuccess = function (e) {
+ var cursor: any;
+ var count = 0;
+ var rq = db.transaction("test").objectStore("test").openCursor();
+
+ rq.onsuccess = function (e: any) {
+ switch (count) {
+ case 0:
+ cursor = e.target.result;
+
+ t.deepEqual(cursor.value, "data", "prequisite cursor.value");
+ cursor.custom_cursor_value = 1;
+ e.target.custom_request_value = 2;
+
+ cursor.continue();
+ break;
+
+ case 1:
+ t.deepEqual(cursor.value, "data2", "prequisite cursor.value");
+ t.deepEqual(cursor.custom_cursor_value, 1, "custom cursor value");
+ t.deepEqual(
+ e.target.custom_request_value,
+ 2,
+ "custom request value",
+ );
+
+ cursor.advance(1);
+ break;
+
+ case 2:
+ t.false(!!e.target.result, "got cursor");
+ t.deepEqual(cursor.custom_cursor_value, 1, "custom cursor value");
+ t.deepEqual(
+ e.target.custom_request_value,
+ 2,
+ "custom request value",
+ );
+ break;
+ }
+ count++;
+ };
+
+ rq.transaction.oncomplete = function () {
+ t.deepEqual(count, 3, "cursor callback runs");
+ t.deepEqual(
+ rq.custom_request_value,
+ 2,
+ "variable placed on old IDBRequest",
+ );
+ t.deepEqual(
+ cursor.custom_cursor_value,
+ 1,
+ "custom cursor value (transaction.complete)",
+ );
+ resolve();
+ };
+ };
+ });
+ t.pass();
+});
diff --git a/packages/idb-bridge/src/idb-wpt-ported/idbobjectstore-rename-store.test.ts b/packages/idb-bridge/src/idb-wpt-ported/idbobjectstore-rename-store.test.ts
new file mode 100644
index 000000000..0f872fa51
--- /dev/null
+++ b/packages/idb-bridge/src/idb-wpt-ported/idbobjectstore-rename-store.test.ts
@@ -0,0 +1,504 @@
+import test, { ExecutionContext } from "ava";
+import { BridgeIDBRequest } from "..";
+import { EventTarget, IDBDatabase } from "../idbtypes";
+import {
+ checkStoreContents,
+ checkStoreGenerator,
+ checkStoreIndexes,
+ createBooksStore,
+ createDatabase,
+ createdb,
+ createNotBooksStore,
+ migrateDatabase,
+} from "./wptsupport";
+
+// IndexedDB: object store renaming support
+// IndexedDB object store rename in new transaction
+test("WPT idbobjectstore-rename-store.html (subtest 1)", async (t) => {
+ await new Promise<void>((resolve, reject) => {
+ let bookStore: any = null;
+ let bookStore2: any = null;
+ let renamedBookStore: any = null;
+ let renamedBookStore2: any = null;
+
+ return createDatabase(t, (database, transaction) => {
+ bookStore = createBooksStore(t, database);
+ })
+ .then((database) => {
+ t.deepEqual(
+ database.objectStoreNames as any,
+ ["books"],
+ 'Test setup should have created a "books" object store',
+ );
+ const transaction = database.transaction("books", "readonly");
+ bookStore2 = transaction.objectStore("books");
+ return checkStoreContents(
+ t,
+ bookStore2,
+ "The store should have the expected contents before any renaming",
+ ).then(() => database.close());
+ })
+ .then(() =>
+ migrateDatabase(t, 2, (database, transaction) => {
+ renamedBookStore = transaction.objectStore("books");
+ renamedBookStore.name = "renamed_books";
+
+ t.deepEqual(
+ renamedBookStore.name,
+ "renamed_books",
+ "IDBObjectStore name should change immediately after a rename",
+ );
+ t.deepEqual(
+ database.objectStoreNames as any,
+ ["renamed_books"],
+ "IDBDatabase.objectStoreNames should immediately reflect the " +
+ "rename",
+ );
+ t.deepEqual(
+ transaction.objectStoreNames as any,
+ ["renamed_books"],
+ "IDBTransaction.objectStoreNames should immediately reflect the " +
+ "rename",
+ );
+ t.deepEqual(
+ transaction.objectStore("renamed_books"),
+ renamedBookStore,
+ "IDBTransaction.objectStore should return the renamed object " +
+ "store when queried using the new name immediately after the " +
+ "rename",
+ );
+ t.throws(
+ () => transaction.objectStore("books"),
+ { name: "NotFoundError" },
+ "IDBTransaction.objectStore should throw when queried using the " +
+ "renamed object store's old name immediately after the rename",
+ );
+ }),
+ )
+ .then((database) => {
+ t.deepEqual(
+ database.objectStoreNames as any,
+ ["renamed_books"],
+ "IDBDatabase.objectStoreNames should still reflect the rename " +
+ "after the versionchange transaction commits",
+ );
+ const transaction = database.transaction("renamed_books", "readonly");
+ renamedBookStore2 = transaction.objectStore("renamed_books");
+ return checkStoreContents(
+ t,
+ renamedBookStore2,
+ "Renaming an object store should not change its records",
+ ).then(() => database.close());
+ })
+ .then(() => {
+ t.deepEqual(
+ bookStore.name,
+ "books",
+ "IDBObjectStore obtained before the rename transaction should " +
+ "not reflect the rename",
+ );
+ t.deepEqual(
+ bookStore2.name,
+ "books",
+ "IDBObjectStore obtained before the rename transaction should " +
+ "not reflect the rename",
+ );
+ t.deepEqual(
+ renamedBookStore.name,
+ "renamed_books",
+ "IDBObjectStore used in the rename transaction should keep " +
+ "reflecting the new name after the transaction is committed",
+ );
+ t.deepEqual(
+ renamedBookStore2.name,
+ "renamed_books",
+ "IDBObjectStore obtained after the rename transaction should " +
+ "reflect the new name",
+ );
+ });
+ });
+ t.pass();
+});
+
+// IndexedDB: object store renaming support
+// IndexedDB object store rename in the transaction where it is created
+test("WPT idbobjectstore-rename-store.html (subtest 2)", async (t) => {
+ await new Promise<void>((resolve, reject) => {
+ let renamedBookStore: any = null,
+ renamedBookStore2: any = null;
+ return createDatabase(t, (database, transaction) => {
+ renamedBookStore = createBooksStore(t, database);
+ renamedBookStore.name = "renamed_books";
+
+ t.deepEqual(
+ renamedBookStore.name,
+ "renamed_books",
+ "IDBObjectStore name should change immediately after a rename",
+ );
+ t.deepEqual(
+ database.objectStoreNames as any,
+ ["renamed_books"],
+ "IDBDatabase.objectStoreNames should immediately reflect the " +
+ "rename",
+ );
+ t.deepEqual(
+ transaction.objectStoreNames as any,
+ ["renamed_books"],
+ "IDBTransaction.objectStoreNames should immediately reflect the " +
+ "rename",
+ );
+ t.deepEqual(
+ transaction.objectStore("renamed_books"),
+ renamedBookStore,
+ "IDBTransaction.objectStore should return the renamed object " +
+ "store when queried using the new name immediately after the " +
+ "rename",
+ );
+ t.throws(
+ () => transaction.objectStore("books"),
+ { name: "NotFoundError" },
+ "IDBTransaction.objectStore should throw when queried using the " +
+ "renamed object store's old name immediately after the rename",
+ );
+ })
+ .then((database) => {
+ t.deepEqual(
+ database.objectStoreNames as any,
+ ["renamed_books"],
+ "IDBDatabase.objectStoreNames should still reflect the rename " +
+ "after the versionchange transaction commits",
+ );
+ const transaction = database.transaction("renamed_books", "readonly");
+ renamedBookStore2 = transaction.objectStore("renamed_books");
+ return checkStoreContents(
+ t,
+ renamedBookStore2,
+ "Renaming an object store should not change its records",
+ ).then(() => database.close());
+ })
+ .then(() => {
+ t.deepEqual(
+ renamedBookStore.name,
+ "renamed_books",
+ "IDBObjectStore used in the rename transaction should keep " +
+ "reflecting the new name after the transaction is committed",
+ );
+ t.deepEqual(
+ renamedBookStore2.name,
+ "renamed_books",
+ "IDBObjectStore obtained after the rename transaction should " +
+ "reflect the new name",
+ );
+ });
+ });
+ t.pass();
+});
+
+// Renames the 'books' store to 'renamed_books'.
+//
+// Returns a promise that resolves to an IndexedDB database. The caller must
+// close the database.
+const renameBooksStore = (testCase: ExecutionContext) => {
+ return migrateDatabase(testCase, 2, (database, transaction) => {
+ const store = transaction.objectStore("books");
+ store.name = "renamed_books";
+ });
+};
+
+// IndexedDB: object store renaming support
+// IndexedDB object store rename covers index
+test("WPT idbobjectstore-rename-store.html (subtest 3)", async (t) => {
+ await createDatabase(t, (database, transaction) => {
+ createBooksStore(t, database);
+ })
+ .then(async (database) => {
+ const transaction = database.transaction("books", "readonly");
+ const store = transaction.objectStore("books");
+ await checkStoreIndexes(
+ t,
+ store,
+ "The object store index should have the expected contens before " +
+ "any renaming",
+ );
+ return database.close();
+ })
+ .then(() => renameBooksStore(t))
+ .then(async (database) => {
+ const transaction = database.transaction("renamed_books", "readonly");
+ const store = transaction.objectStore("renamed_books");
+ await checkStoreIndexes(
+ t,
+ store,
+ "Renaming an object store should not change its indexes",
+ );
+ return database.close();
+ });
+ t.pass();
+});
+
+// IndexedDB: object store renaming support
+// IndexedDB object store rename covers key generator
+test("WPT idbobjectstore-rename-store.html (subtest 4)", async (t) => {
+ await createDatabase(t, (database, transaction) => {
+ createBooksStore(t, database);
+ })
+ .then((database) => {
+ const transaction = database.transaction("books", "readwrite");
+ const store = transaction.objectStore("books");
+ return checkStoreGenerator(
+ t,
+ store,
+ 345679,
+ "The object store key generator should have the expected state " +
+ "before any renaming",
+ ).then(() => database.close());
+ })
+ .then(() => renameBooksStore(t))
+ .then((database) => {
+ const transaction = database.transaction("renamed_books", "readwrite");
+ const store = transaction.objectStore("renamed_books");
+ return checkStoreGenerator(
+ t,
+ store,
+ 345680,
+ "Renaming an object store should not change the state of its key " +
+ "generator",
+ ).then(() => database.close());
+ });
+ t.pass();
+});
+
+// IndexedDB: object store renaming support
+// IndexedDB object store rename to the name of a deleted store succeeds
+test("WPT idbobjectstore-rename-store.html (subtest 5)", async (t) => {
+ await createDatabase(t, (database, transaction) => {
+ createBooksStore(t, database);
+ createNotBooksStore(t, database);
+ })
+ .then((database) => {
+ database.close();
+ })
+ .then(() =>
+ migrateDatabase(t, 2, (database, transaction) => {
+ const store = transaction.objectStore("books");
+ database.deleteObjectStore("not_books");
+ store.name = "not_books";
+ t.deepEqual(
+ database.objectStoreNames as any,
+ ["not_books"],
+ "IDBDatabase.objectStoreNames should immediately reflect the " +
+ "rename",
+ );
+ }),
+ )
+ .then((database) => {
+ t.deepEqual(
+ database.objectStoreNames as any,
+ ["not_books"],
+ "IDBDatabase.objectStoreNames should still reflect the rename " +
+ "after the versionchange transaction commits",
+ );
+ const transaction = database.transaction("not_books", "readonly");
+ const store = transaction.objectStore("not_books");
+ return checkStoreContents(
+ t,
+ store,
+ "Renaming an object store should not change its records",
+ ).then(() => database.close());
+ });
+ t.pass();
+});
+
+// IndexedDB: object store renaming support
+test("WPT idbobjectstore-rename-store.html (IndexedDB object store swapping via renames succeeds)", async (t) => {
+ await createDatabase(t, (database, transaction) => {
+ createBooksStore(t, database);
+ createNotBooksStore(t, database);
+ })
+ .then((database) => {
+ database.close();
+ })
+ .then(() =>
+ migrateDatabase(t, 2, (database, transaction) => {
+ const bookStore = transaction.objectStore("books");
+ const notBookStore = transaction.objectStore("not_books");
+
+ transaction.objectStore("books").name = "tmp";
+ transaction.objectStore("not_books").name = "books";
+ transaction.objectStore("tmp").name = "not_books";
+
+ t.deepEqual(
+ database.objectStoreNames as any,
+ ["books", "not_books"],
+ "IDBDatabase.objectStoreNames should immediately reflect the swap",
+ );
+
+ t.deepEqual(
+ transaction.objectStore("books"),
+ notBookStore,
+ 'IDBTransaction.objectStore should return the original "books" ' +
+ 'store when queried with "not_books" after the swap',
+ );
+ t.deepEqual(
+ transaction.objectStore("not_books"),
+ bookStore,
+ "IDBTransaction.objectStore should return the original " +
+ '"not_books" store when queried with "books" after the swap',
+ );
+ }),
+ )
+ .then((database) => {
+ t.deepEqual(
+ database.objectStoreNames as any,
+ ["books", "not_books"],
+ "IDBDatabase.objectStoreNames should still reflect the swap " +
+ "after the versionchange transaction commits",
+ );
+ const transaction = database.transaction("not_books", "readonly");
+ const store = transaction.objectStore("not_books");
+ t.deepEqual(
+ store.indexNames as any,
+ ["by_author", "by_title"],
+ '"not_books" index names should still reflect the swap after the ' +
+ "versionchange transaction commits",
+ );
+ return checkStoreContents(
+ t,
+ store,
+ "Swapping two object stores should not change their records",
+ ).then(() => database.close());
+ });
+ t.pass();
+});
+
+// IndexedDB: object store renaming support
+test("WPT idbobjectstore-rename-store.html (IndexedDB object store rename stringifies non-string names)", async (t) => {
+ await createDatabase(t, (database, transaction) => {
+ createBooksStore(t, database);
+ })
+ .then((database) => {
+ database.close();
+ })
+ .then(() =>
+ migrateDatabase(t, 2, (database, transaction) => {
+ const store = transaction.objectStore("books");
+ // @ts-expect-error
+ store.name = 42;
+ t.deepEqual(
+ store.name,
+ "42",
+ "IDBObjectStore name should change immediately after a " +
+ "rename to a number",
+ );
+ t.deepEqual(
+ database.objectStoreNames as any,
+ ["42"],
+ "IDBDatabase.objectStoreNames should immediately reflect the " +
+ "stringifying rename",
+ );
+
+ // @ts-expect-error
+ store.name = true;
+ t.deepEqual(
+ store.name,
+ "true",
+ "IDBObjectStore name should change immediately after a " +
+ "rename to a boolean",
+ );
+
+ // @ts-expect-error
+ store.name = {};
+ t.deepEqual(
+ store.name,
+ "[object Object]",
+ "IDBObjectStore name should change immediately after a " +
+ "rename to an object",
+ );
+
+ // @ts-expect-error
+ store.name = () => null;
+ t.deepEqual(
+ store.name,
+ "() => null",
+ "IDBObjectStore name should change immediately after a " +
+ "rename to a function",
+ );
+
+ // @ts-expect-error
+ store.name = undefined;
+ t.deepEqual(
+ store.name,
+ "undefined",
+ "IDBObjectStore name should change immediately after a " +
+ "rename to undefined",
+ );
+ }),
+ )
+ .then((database) => {
+ t.deepEqual(
+ database.objectStoreNames as any,
+ ["undefined"],
+ "IDBDatabase.objectStoreNames should reflect the last rename " +
+ "after the versionchange transaction commits",
+ );
+ const transaction = database.transaction("undefined", "readonly");
+ const store = transaction.objectStore("undefined");
+ return checkStoreContents(
+ t,
+ store,
+ "Renaming an object store should not change its records",
+ ).then(() => database.close());
+ });
+ t.pass();
+});
+
+function rename_test_macro(t: ExecutionContext, escapedName: string) {
+ const name = JSON.parse('"' + escapedName + '"');
+ createDatabase(t, (database, transaction) => {
+ createBooksStore(t, database);
+ })
+ .then((database) => {
+ database.close();
+ })
+ .then(() =>
+ migrateDatabase(t, 2, (database, transaction) => {
+ const store = transaction.objectStore("books");
+
+ store.name = name;
+ t.deepEqual(
+ store.name,
+ name,
+ "IDBObjectStore name should change immediately after the " + "rename",
+ );
+ t.deepEqual(
+ database.objectStoreNames as any,
+ [name],
+ "IDBDatabase.objectStoreNames should immediately reflect the " +
+ "rename",
+ );
+ }),
+ )
+ .then((database) => {
+ t.deepEqual(
+ database.objectStoreNames as any,
+ [name],
+ "IDBDatabase.objectStoreNames should reflect the rename " +
+ "after the versionchange transaction commits",
+ );
+ const transaction = database.transaction(name, "readonly");
+ const store = transaction.objectStore(name);
+ return checkStoreContents(
+ t,
+ store,
+ "Renaming an object store should not change its records",
+ ).then(() => database.close());
+ });
+}
+
+for (let escapedName of ["", "\\u0000", "\\uDC00\\uD800"]) {
+ test(
+ 'IndexedDB object store can be renamed to "' + escapedName + '"',
+ rename_test_macro,
+ escapedName,
+ );
+}
diff --git a/packages/idb-bridge/src/idb-wpt-ported/wptsupport.ts b/packages/idb-bridge/src/idb-wpt-ported/wptsupport.ts
index 5716a7ae5..1c25bb8e3 100644
--- a/packages/idb-bridge/src/idb-wpt-ported/wptsupport.ts
+++ b/packages/idb-bridge/src/idb-wpt-ported/wptsupport.ts
@@ -1,6 +1,14 @@
-import { ExecutionContext } from "ava";
+import test, { ExecutionContext } from "ava";
import { BridgeIDBFactory } from "..";
-import { IDBOpenDBRequest } from "../idbtypes";
+import {
+ IDBDatabase,
+ IDBIndex,
+ IDBObjectStore,
+ IDBOpenDBRequest,
+ IDBRequest,
+ IDBTransaction,
+ IDBTransactionMode,
+} from "../idbtypes";
import { MemoryBackend } from "../MemoryBackend";
import { compareKeys } from "../util/cmp";
@@ -40,3 +48,256 @@ export function assert_equals(actual: any, expected: any) {
throw Error("assert_equals failed");
}
}
+
+function makeDatabaseName(testCase: string): string {
+ return "db-" + testCase;
+}
+
+// Promise that resolves with an IDBRequest's result.
+//
+// The promise only resolves if IDBRequest receives the "success" event. Any
+// other event causes the promise to reject with an error. This is correct in
+// most cases, but insufficient for indexedDB.open(), which issues
+// "upgradeneded" events under normal operation.
+function promiseForRequest<T = any>(
+ t: ExecutionContext,
+ request: IDBRequest<T>,
+): Promise<T> {
+ return new Promise<T>((resolve, reject) => {
+ request.addEventListener("success", (evt: any) => {
+ resolve(evt.target.result);
+ });
+ request.addEventListener("blocked", (evt: any) => reject(evt.target.error));
+ request.addEventListener("error", (evt: any) => reject(evt.target.error));
+ request.addEventListener("upgradeneeded", (evt: any) =>
+ reject(evt.target.error),
+ );
+ });
+}
+
+type MigrationCallback = (
+ db: IDBDatabase,
+ tx: IDBTransaction,
+ req: IDBOpenDBRequest,
+) => void;
+
+export async function migrateDatabase(
+ t: ExecutionContext,
+ newVersion: number,
+ migrationCallback: MigrationCallback,
+): Promise<IDBDatabase> {
+ return migrateNamedDatabase(
+ t,
+ makeDatabaseName(t.title),
+ newVersion,
+ migrationCallback,
+ );
+}
+
+export async function migrateNamedDatabase(
+ t: ExecutionContext,
+ databaseName: string,
+ newVersion: number,
+ migrationCallback: MigrationCallback,
+): Promise<IDBDatabase> {
+ return new Promise<IDBDatabase>((resolve, reject) => {
+ const request = self.indexedDB.open(databaseName, newVersion);
+ request.onupgradeneeded = (event: any) => {
+ const database = event.target.result;
+ const transaction = event.target.transaction;
+ let shouldBeAborted = false;
+ let requestEventPromise: any = null;
+
+ // We wrap IDBTransaction.abort so we can set up the correct event
+ // listeners and expectations if the test chooses to abort the
+ // versionchange transaction.
+ const transactionAbort = transaction.abort.bind(transaction);
+ transaction.abort = () => {
+ transaction._willBeAborted();
+ transactionAbort();
+ };
+ transaction._willBeAborted = () => {
+ requestEventPromise = new Promise((resolve, reject) => {
+ request.onerror = (event: any) => {
+ event.preventDefault();
+ resolve(event.target.error);
+ };
+ request.onsuccess = () =>
+ reject(
+ new Error(
+ "indexedDB.open should not succeed for an aborted " +
+ "versionchange transaction",
+ ),
+ );
+ });
+ shouldBeAborted = true;
+ };
+
+ // If migration callback returns a promise, we'll wait for it to resolve.
+ // This simplifies some tests.
+ const callbackResult = migrationCallback(database, transaction, request);
+ if (!shouldBeAborted) {
+ request.onerror = null;
+ request.onsuccess = null;
+ requestEventPromise = promiseForRequest(t, request);
+ }
+
+ // requestEventPromise needs to be the last promise in the chain, because
+ // we want the event that it resolves to.
+ resolve(Promise.resolve(callbackResult).then(() => requestEventPromise));
+ };
+ request.onerror = (event: any) => reject(event.target.error);
+ request.onsuccess = () => {
+ const database = request.result;
+ t.teardown(() => database.close());
+ reject(
+ new Error(
+ "indexedDB.open should not succeed without creating a " +
+ "versionchange transaction",
+ ),
+ );
+ };
+ });
+}
+
+export async function createDatabase(
+ t: ExecutionContext,
+ setupCallback: MigrationCallback,
+): Promise<IDBDatabase> {
+ const databaseName = makeDatabaseName(t.title);
+ const request = self.indexedDB.deleteDatabase(databaseName);
+ return migrateNamedDatabase(t, databaseName, 1, setupCallback);
+}
+
+// The data in the 'books' object store records in the first example of the
+// IndexedDB specification.
+const BOOKS_RECORD_DATA = [
+ { title: "Quarry Memories", author: "Fred", isbn: 123456 },
+ { title: "Water Buffaloes", author: "Fred", isbn: 234567 },
+ { title: "Bedrock Nights", author: "Barney", isbn: 345678 },
+];
+
+// Creates a 'books' object store whose contents closely resembles the first
+// example in the IndexedDB specification.
+export const createBooksStore = (
+ testCase: ExecutionContext,
+ database: IDBDatabase,
+) => {
+ const store = database.createObjectStore("books", {
+ keyPath: "isbn",
+ autoIncrement: true,
+ });
+ store.createIndex("by_author", "author");
+ store.createIndex("by_title", "title", { unique: true });
+ for (const record of BOOKS_RECORD_DATA) store.put(record);
+ return store;
+};
+
+// Verifies that an object store's contents matches the contents used to create
+// the books store in the test database's version 1.
+//
+// The errorMessage is used if the assertions fail. It can state that the
+// IndexedDB implementation being tested is incorrect, or that the testing code
+// is using it incorrectly.
+export async function checkStoreContents(
+ testCase: ExecutionContext,
+ store: IDBObjectStore,
+ errorMessage: string,
+) {
+ const request = store.get(123456);
+ const result = await promiseForRequest(testCase, request);
+ testCase.deepEqual(result.isbn, BOOKS_RECORD_DATA[0].isbn, errorMessage);
+ testCase.deepEqual(result.author, BOOKS_RECORD_DATA[0].author, errorMessage);
+ testCase.deepEqual(result.title, BOOKS_RECORD_DATA[0].title, errorMessage);
+}
+
+// Verifies that an object store's indexes match the indexes used to create the
+// books store in the test database's version 1.
+//
+// The errorMessage is used if the assertions fail. It can state that the
+// IndexedDB implementation being tested is incorrect, or that the testing code
+// is using it incorrectly.
+export function checkStoreIndexes(
+ testCase: ExecutionContext,
+ store: IDBObjectStore,
+ errorMessage: string,
+) {
+ testCase.deepEqual(
+ store.indexNames as any,
+ ["by_author", "by_title"],
+ errorMessage,
+ );
+ const authorIndex = store.index("by_author");
+ const titleIndex = store.index("by_title");
+ return Promise.all([
+ checkAuthorIndexContents(testCase, authorIndex, errorMessage),
+ checkTitleIndexContents(testCase, titleIndex, errorMessage),
+ ]);
+}
+
+// Verifies that index matches the 'by_author' index used to create the
+// by_author books store in the test database's version 1.
+//
+// The errorMessage is used if the assertions fail. It can state that the
+// IndexedDB implementation being tested is incorrect, or that the testing code
+// is using it incorrectly.
+async function checkAuthorIndexContents(
+ testCase: ExecutionContext,
+ index: IDBIndex,
+ errorMessage: string,
+) {
+ const request = index.get(BOOKS_RECORD_DATA[2].author);
+ const result = await promiseForRequest(testCase, request);
+ testCase.deepEqual(result.isbn, BOOKS_RECORD_DATA[2].isbn, errorMessage);
+ testCase.deepEqual(result.title, BOOKS_RECORD_DATA[2].title, errorMessage);
+}
+
+// Verifies that an index matches the 'by_title' index used to create the books
+// store in the test database's version 1.
+//
+// The errorMessage is used if the assertions fail. It can state that the
+// IndexedDB implementation being tested is incorrect, or that the testing code
+// is using it incorrectly.
+async function checkTitleIndexContents(
+ testCase: ExecutionContext,
+ index: IDBIndex,
+ errorMessage: string,
+) {
+ const request = index.get(BOOKS_RECORD_DATA[2].title);
+ const result = await promiseForRequest(testCase, request);
+ testCase.deepEqual(result.isbn, BOOKS_RECORD_DATA[2].isbn, errorMessage);
+ testCase.deepEqual(result.author, BOOKS_RECORD_DATA[2].author, errorMessage);
+}
+
+// Verifies that an object store's key generator is in the same state as the
+// key generator created for the books store in the test database's version 1.
+//
+// The errorMessage is used if the assertions fail. It can state that the
+// IndexedDB implementation being tested is incorrect, or that the testing code
+// is using it incorrectly.
+export function checkStoreGenerator(
+ testCase: ExecutionContext,
+ store: IDBObjectStore,
+ expectedKey: any,
+ errorMessage: string,
+) {
+ const request = store.put({
+ title: "Bedrock Nights " + expectedKey,
+ author: "Barney",
+ });
+ return promiseForRequest(testCase, request).then((result) => {
+ testCase.deepEqual(result, expectedKey, errorMessage);
+ });
+}
+
+// Creates a 'not_books' object store used to test renaming into existing or
+// deleted store names.
+export function createNotBooksStore(
+ testCase: ExecutionContext,
+ database: IDBDatabase,
+) {
+ const store = database.createObjectStore("not_books");
+ store.createIndex("not_by_author", "author");
+ store.createIndex("not_by_title", "title", { unique: true });
+ return store;
+}