summaryrefslogtreecommitdiff
path: root/packages/idb-bridge/src/backends.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/idb-bridge/src/backends.test.ts')
-rw-r--r--packages/idb-bridge/src/backends.test.ts740
1 files changed, 740 insertions, 0 deletions
diff --git a/packages/idb-bridge/src/backends.test.ts b/packages/idb-bridge/src/backends.test.ts
new file mode 100644
index 000000000..684358eac
--- /dev/null
+++ b/packages/idb-bridge/src/backends.test.ts
@@ -0,0 +1,740 @@
+/*
+ Copyright 2019 Florian Dold
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ or implied. See the License for the specific language governing
+ permissions and limitations under the License.
+ */
+
+/**
+ * Tests that are backend-generic.
+ * See testingdb.ts for the backend selection in test runs.
+ */
+
+/**
+ * Imports.
+ */
+import test from "ava";
+import {
+ BridgeIDBCursorWithValue,
+ BridgeIDBDatabase,
+ BridgeIDBFactory,
+ BridgeIDBKeyRange,
+ BridgeIDBTransaction,
+} from "./bridge-idb.js";
+import {
+ IDBCursorDirection,
+ IDBCursorWithValue,
+ IDBDatabase,
+ IDBKeyRange,
+ IDBRequest,
+ IDBValidKey,
+} from "./idbtypes.js";
+import { initTestIndexedDB, useTestIndexedDb } from "./testingdb.js";
+import { MemoryBackend } from "./MemoryBackend.js";
+import { promiseFromRequest, promiseFromTransaction } from "./idbpromutil.js";
+
+test.before("test DB initialization", initTestIndexedDB);
+
+test("Spec: Example 1 Part 1", async (t) => {
+ const idb = useTestIndexedDb();
+
+ const dbname = "library-" + new Date().getTime() + Math.random();
+
+ const request = idb.open(dbname);
+ request.onupgradeneeded = () => {
+ const db = request.result as BridgeIDBDatabase;
+ const store = db.createObjectStore("books", { keyPath: "isbn" });
+ const titleIndex = store.createIndex("by_title", "title", { unique: true });
+ const authorIndex = store.createIndex("by_author", "author");
+
+ // Populate with initial data.
+ store.put({ title: "Quarry Memories", author: "Fred", isbn: 123456 });
+ store.put({ title: "Water Buffaloes", author: "Fred", isbn: 234567 });
+ store.put({ title: "Bedrock Nights", author: "Barney", isbn: 345678 });
+ };
+
+ await promiseFromRequest(request);
+ t.pass();
+});
+
+test("Spec: Example 1 Part 2", async (t) => {
+ const idb = useTestIndexedDb();
+
+ const dbname = "library-" + new Date().getTime() + Math.random();
+
+ const request = idb.open(dbname);
+ request.onupgradeneeded = () => {
+ const db = request.result;
+ const store = db.createObjectStore("books", { keyPath: "isbn" });
+ const titleIndex = store.createIndex("by_title", "title", { unique: true });
+ const authorIndex = store.createIndex("by_author", "author");
+ };
+
+ const db: BridgeIDBDatabase = await promiseFromRequest(request);
+
+ t.is(db.name, dbname);
+
+ const tx = db.transaction("books", "readwrite");
+ tx.oncomplete = () => {
+ console.log("oncomplete called");
+ };
+
+ const store = tx.objectStore("books");
+
+ store.put({ title: "Quarry Memories", author: "Fred", isbn: 123456 });
+ store.put({ title: "Water Buffaloes", author: "Fred", isbn: 234567 });
+ store.put({ title: "Bedrock Nights", author: "Barney", isbn: 345678 });
+
+ await promiseFromTransaction(tx);
+
+ t.pass();
+});
+
+test("duplicate index insertion", async (t) => {
+ const idb = useTestIndexedDb();
+
+ const dbname = "library-" + new Date().getTime() + Math.random();
+
+ const request = idb.open(dbname);
+ request.onupgradeneeded = () => {
+ const db = request.result;
+ const store = db.createObjectStore("books", { keyPath: "isbn" });
+ const titleIndex = store.createIndex("by_title", "title", { unique: true });
+ const authorIndex = store.createIndex("by_author", "author");
+ };
+
+ const db: BridgeIDBDatabase = await promiseFromRequest(request);
+
+ t.is(db.name, dbname);
+
+ const tx = db.transaction("books", "readwrite");
+ tx.oncomplete = () => {
+ console.log("oncomplete called");
+ };
+
+ const store = tx.objectStore("books");
+
+ store.put({ title: "Quarry Memories", author: "Fred", isbn: 123456 });
+
+ // Change the index key, keep primary key (isbn) the same.
+ store.put({ title: "Water Buffaloes", author: "Bla", isbn: 234567 });
+ store.put({ title: "Water Buffaloes", author: "Fred", isbn: 234567 });
+
+ store.put({ title: "Bedrock Nights", author: "Barney", isbn: 345678 });
+
+ await promiseFromTransaction(tx);
+
+ const tx3 = db.transaction(["books"], "readonly");
+ const store3 = tx3.objectStore("books");
+ const index3 = store3.index("by_author");
+ const request3 = index3.openCursor();
+
+ const authorList: string[] = [];
+
+ await promiseFromRequest(request3);
+ while (request3.result != null) {
+ const cursor: IDBCursorWithValue = request3.result;
+ authorList.push(cursor.value.author);
+ cursor.continue();
+ await promiseFromRequest(request3);
+ }
+
+ t.deepEqual(authorList, ["Barney", "Fred", "Fred"]);
+
+ t.pass();
+});
+
+test("simple index iteration", async (t) => {
+ const idb = useTestIndexedDb();
+ const dbname = "library-" + new Date().getTime() + Math.random();
+ const request = idb.open(dbname);
+ request.onupgradeneeded = () => {
+ const db = request.result;
+ const store = db.createObjectStore("books", { keyPath: "isbn" });
+ const titleIndex = store.createIndex("by_title", "title", { unique: true });
+ const authorIndex = store.createIndex("by_author", "author");
+ };
+
+ const db: BridgeIDBDatabase = await promiseFromRequest(request);
+ const tx = db.transaction("books", "readwrite");
+ const store = tx.objectStore("books");
+
+ store.put({ title: "Bedrock Nights", author: "Barney", isbn: 345678 });
+ store.put({ title: "Quarry Memories", author: "Fred", isbn: 123456 });
+ store.put({ title: "Water Buffaloes", author: "Fred", isbn: 234567 });
+
+ await promiseFromTransaction(tx);
+
+ const tx3 = db.transaction(["books"], "readonly");
+ const store3 = tx3.objectStore("books");
+ const index3 = store3.index("by_author");
+ const request3 = index3.openCursor(BridgeIDBKeyRange.only("Fred"));
+
+ await promiseFromRequest(request3);
+
+ let cursor: BridgeIDBCursorWithValue | null;
+ cursor = request3.result as BridgeIDBCursorWithValue;
+ t.is(cursor.value.author, "Fred");
+ t.is(cursor.value.isbn, 123456);
+
+ cursor.continue();
+
+ await promiseFromRequest(request3);
+
+ t.is(cursor.value.author, "Fred");
+ t.is(cursor.value.isbn, 234567);
+
+ cursor.continue();
+
+ await promiseFromRequest(request3);
+
+ t.is(cursor.value, undefined);
+});
+
+test("Spec: Example 1 Part 3", async (t) => {
+ const idb = useTestIndexedDb();
+ const dbname = "library-" + new Date().getTime() + Math.random();
+ const request = idb.open(dbname);
+ request.onupgradeneeded = () => {
+ const db = request.result;
+ const store = db.createObjectStore("books", { keyPath: "isbn" });
+ const titleIndex = store.createIndex("by_title", "title", { unique: true });
+ const authorIndex = store.createIndex("by_author", "author");
+ };
+
+ const db: BridgeIDBDatabase = await promiseFromRequest(request);
+
+ t.is(db.name, dbname);
+
+ const tx = db.transaction("books", "readwrite");
+
+ const store = tx.objectStore("books");
+
+ store.put({ title: "Bedrock Nights", author: "Barney", isbn: 345678 });
+ store.put({ title: "Quarry Memories", author: "Fred", isbn: 123456 });
+ store.put({ title: "Water Buffaloes", author: "Fred", isbn: 234567 });
+
+ await promiseFromTransaction(tx);
+
+ const tx2 = db.transaction("books", "readonly");
+ const store2 = tx2.objectStore("books");
+ var index2 = store2.index("by_title");
+ const request2 = index2.get("Bedrock Nights");
+ const result2: any = await promiseFromRequest(request2);
+
+ t.is(result2.author, "Barney");
+
+ const tx3 = db.transaction(["books"], "readonly");
+ const store3 = tx3.objectStore("books");
+ const index3 = store3.index("by_author");
+ const request3 = index3.openCursor(BridgeIDBKeyRange.only("Fred"));
+
+ await promiseFromRequest(request3);
+
+ let cursor: BridgeIDBCursorWithValue | null;
+ cursor = request3.result as BridgeIDBCursorWithValue;
+ t.is(cursor.value.author, "Fred");
+ t.is(cursor.value.isbn, 123456);
+
+ cursor.continue();
+
+ await promiseFromRequest(request3);
+
+ cursor = request3.result as BridgeIDBCursorWithValue;
+ t.is(cursor.value.author, "Fred");
+ t.is(cursor.value.isbn, 234567);
+
+ await promiseFromTransaction(tx3);
+
+ const tx4 = db.transaction("books", "readonly");
+ const store4 = tx4.objectStore("books");
+ const request4 = store4.openCursor();
+
+ await promiseFromRequest(request4);
+
+ cursor = request4.result;
+ if (!cursor) {
+ throw new Error();
+ }
+ t.is(cursor.value.isbn, 123456);
+
+ cursor.continue();
+
+ await promiseFromRequest(request4);
+
+ cursor = request4.result;
+ if (!cursor) {
+ throw new Error();
+ }
+ t.is(cursor.value.isbn, 234567);
+
+ cursor.continue();
+
+ await promiseFromRequest(request4);
+
+ cursor = request4.result;
+ if (!cursor) {
+ throw new Error();
+ }
+ t.is(cursor.value.isbn, 345678);
+
+ cursor.continue();
+ await promiseFromRequest(request4);
+
+ cursor = request4.result;
+
+ t.is(cursor, null);
+
+ const tx5 = db.transaction("books", "readonly");
+ const store5 = tx5.objectStore("books");
+ const index5 = store5.index("by_author");
+
+ const request5 = index5.openCursor(null, "next");
+
+ await promiseFromRequest(request5);
+ cursor = request5.result;
+ if (!cursor) {
+ throw new Error();
+ }
+ t.is(cursor.value.author, "Barney");
+ cursor.continue();
+
+ await promiseFromRequest(request5);
+ cursor = request5.result;
+ if (!cursor) {
+ throw new Error();
+ }
+ t.is(cursor.value.author, "Fred");
+ cursor.continue();
+
+ await promiseFromRequest(request5);
+ cursor = request5.result;
+ if (!cursor) {
+ throw new Error();
+ }
+ t.is(cursor.value.author, "Fred");
+ cursor.continue();
+
+ await promiseFromRequest(request5);
+ cursor = request5.result;
+ t.is(cursor, null);
+
+ const request6 = index5.openCursor(null, "nextunique");
+
+ await promiseFromRequest(request6);
+ cursor = request6.result;
+ if (!cursor) {
+ throw new Error();
+ }
+ t.is(cursor.value.author, "Barney");
+ cursor.continue();
+
+ await promiseFromRequest(request6);
+ cursor = request6.result;
+ if (!cursor) {
+ throw new Error();
+ }
+ t.is(cursor.value.author, "Fred");
+ t.is(cursor.value.isbn, 123456);
+ cursor.continue();
+
+ await promiseFromRequest(request6);
+ cursor = request6.result;
+ t.is(cursor, null);
+
+ console.log("---------------------------");
+
+ const request7 = index5.openCursor(null, "prevunique");
+ await promiseFromRequest(request7);
+ cursor = request7.result;
+ if (!cursor) {
+ throw new Error();
+ }
+ t.is(cursor.value.author, "Fred");
+ t.is(cursor.value.isbn, 123456);
+ cursor.continue();
+
+ await promiseFromRequest(request7);
+ cursor = request7.result;
+ if (!cursor) {
+ throw new Error();
+ }
+ t.is(cursor.value.author, "Barney");
+ cursor.continue();
+
+ await promiseFromRequest(request7);
+ cursor = request7.result;
+ t.is(cursor, null);
+
+ db.close();
+
+ t.pass();
+});
+
+test("simple deletion", async (t) => {
+ const idb = useTestIndexedDb();
+ const dbname = "library-" + new Date().getTime() + Math.random();
+ const request = idb.open(dbname);
+ request.onupgradeneeded = () => {
+ const db = request.result;
+ const store = db.createObjectStore("books", { keyPath: "isbn" });
+ const titleIndex = store.createIndex("by_title", "title", { unique: true });
+ const authorIndex = store.createIndex("by_author", "author");
+ };
+
+ const db: BridgeIDBDatabase = await promiseFromRequest(request);
+ const tx = db.transaction("books", "readwrite");
+ tx.oncomplete = () => {
+ console.log("oncomplete called");
+ };
+
+ const store = tx.objectStore("books");
+
+ store.put({ title: "Quarry Memories", author: "Fred", isbn: 123456 });
+ store.put({ title: "Water Buffaloes", author: "Fred", isbn: 234567 });
+ store.put({ title: "Bedrock Nights", author: "Barney", isbn: 345678 });
+
+ await promiseFromTransaction(tx);
+
+ const tx2 = db.transaction("books", "readwrite");
+
+ const store2 = tx2.objectStore("books");
+
+ const req1 = store2.get(234567);
+ await promiseFromRequest(req1);
+ t.is(req1.readyState, "done");
+ t.is(req1.result.author, "Fred");
+
+ store2.delete(123456);
+
+ const req2 = store2.get(123456);
+ await promiseFromRequest(req2);
+ t.is(req2.readyState, "done");
+ t.is(req2.result, undefined);
+
+ const req3 = store2.get(234567);
+ await promiseFromRequest(req3);
+ t.is(req3.readyState, "done");
+ t.is(req3.result.author, "Fred");
+
+ await promiseFromTransaction(tx2);
+
+ t.pass();
+});
+
+test("export", async (t) => {
+ const backend = new MemoryBackend();
+ const idb = new BridgeIDBFactory(backend);
+ const dbname = "library-" + new Date().getTime() + Math.random();
+ const request = idb.open(dbname, 42);
+ request.onupgradeneeded = () => {
+ const db = request.result;
+ const store = db.createObjectStore("books", { keyPath: "isbn" });
+ const titleIndex = store.createIndex("by_title", "title", { unique: true });
+ const authorIndex = store.createIndex("by_author", "author");
+ };
+
+ const db: BridgeIDBDatabase = await promiseFromRequest(request);
+
+ const tx = db.transaction("books", "readwrite");
+ tx.oncomplete = () => {
+ console.log("oncomplete called");
+ };
+
+ const store = tx.objectStore("books");
+
+ store.put({ title: "Quarry Memories", author: "Fred", isbn: 123456 });
+ store.put({ title: "Water Buffaloes", author: "Fred", isbn: 234567 });
+ store.put({ title: "Bedrock Nights", author: "Barney", isbn: 345678 });
+
+ await promiseFromTransaction(tx);
+
+ const exportedData = backend.exportDump();
+ const backend2 = new MemoryBackend();
+ backend2.importDump(exportedData);
+ const exportedData2 = backend2.exportDump();
+
+ t.assert(
+ exportedData.databases[dbname].objectStores["books"].records.length ===
+ 3,
+ );
+ t.deepEqual(exportedData, exportedData2);
+
+ t.is(exportedData.databases[dbname].schema.databaseVersion, 42);
+ t.is(exportedData2.databases[dbname].schema.databaseVersion, 42);
+ t.pass();
+});
+
+test("update with non-existent index values", async (t) => {
+ const idb = useTestIndexedDb();
+ const dbname = "mydb-" + new Date().getTime() + Math.random();
+ const request = idb.open(dbname);
+ request.onupgradeneeded = () => {
+ const db = request.result;
+ const store = db.createObjectStore("bla", { keyPath: "x" });
+ store.createIndex("by_y", "y");
+ store.createIndex("by_z", "z");
+ };
+
+ const db: BridgeIDBDatabase = await promiseFromRequest(request);
+
+ t.is(db.name, dbname);
+
+ {
+ const tx = db.transaction("bla", "readwrite");
+ const store = tx.objectStore("bla");
+ store.put({ x: 0, y: "a", z: 42 });
+ const index = store.index("by_z");
+ const indRes = await promiseFromRequest(index.get(42));
+ t.is(indRes.x, 0);
+ const res = await promiseFromRequest(store.get(0));
+ t.is(res.z, 42);
+ await promiseFromTransaction(tx);
+ }
+
+ {
+ const tx = db.transaction("bla", "readwrite");
+ const store = tx.objectStore("bla");
+ store.put({ x: 0, y: "a" });
+ const res = await promiseFromRequest(store.get(0));
+ t.is(res.z, undefined);
+ await promiseFromTransaction(tx);
+ }
+
+ {
+ const tx = db.transaction("bla", "readwrite");
+ const store = tx.objectStore("bla");
+ const index = store.index("by_z");
+ {
+ const indRes = await promiseFromRequest(index.get(42));
+ t.is(indRes, undefined);
+ }
+ const res = await promiseFromRequest(store.get(0));
+ t.is(res.z, undefined);
+ await promiseFromTransaction(tx);
+ }
+
+ t.pass();
+});
+
+test("delete from unique index", async (t) => {
+ const idb = useTestIndexedDb();
+ const dbname = "mydb-" + new Date().getTime() + Math.random();
+ const request = idb.open(dbname);
+ request.onupgradeneeded = () => {
+ const db = request.result as IDBDatabase;
+ const store = db.createObjectStore("bla", { keyPath: "x" });
+ store.createIndex("by_yz", ["y", "z"], {
+ unique: true,
+ });
+ };
+
+ const db: BridgeIDBDatabase = await promiseFromRequest(request);
+
+ t.is(db.name, dbname);
+
+ {
+ const tx = db.transaction("bla", "readwrite");
+ const store = tx.objectStore("bla");
+ store.put({ x: 0, y: "a", z: 42 });
+ const index = store.index("by_yz");
+ const indRes = await promiseFromRequest(index.get(["a", 42]));
+ t.is(indRes.x, 0);
+ const res = await promiseFromRequest(store.get(0));
+ t.is(res.z, 42);
+ await promiseFromTransaction(tx);
+ }
+
+ {
+ const tx = db.transaction("bla", "readwrite");
+ const store = tx.objectStore("bla");
+ store.put({ x: 0, y: "a", z: 42, extra: 123 });
+ await promiseFromTransaction(tx);
+ }
+
+ t.pass();
+});
+
+test("range queries", async (t) => {
+ const idb = useTestIndexedDb();
+ const dbname = "mydb-" + new Date().getTime() + Math.random();
+ const request = idb.open(dbname);
+ request.onupgradeneeded = () => {
+ const db = request.result;
+ const store = db.createObjectStore("bla", { keyPath: "x" });
+ store.createIndex("by_y", "y");
+ store.createIndex("by_z", "z");
+ };
+
+ const db: BridgeIDBDatabase = await promiseFromRequest(request);
+ const tx = db.transaction("bla", "readwrite");
+ const store = tx.objectStore("bla");
+
+ store.put({ x: 0, y: "a" });
+ store.put({ x: 2, y: "a" });
+ store.put({ x: 4, y: "b" });
+ store.put({ x: 8, y: "b" });
+ store.put({ x: 10, y: "c" });
+ store.put({ x: 12, y: "c" });
+
+ await promiseFromTransaction(tx);
+
+ async function doCursorStoreQuery(
+ range: IDBKeyRange | IDBValidKey | undefined,
+ direction: IDBCursorDirection | undefined,
+ expected: any[],
+ ): Promise<void> {
+ const tx = db.transaction("bla", "readwrite");
+ const store = tx.objectStore("bla");
+ const vals: any[] = [];
+
+ const req = store.openCursor(range, direction);
+ while (1) {
+ await promiseFromRequest(req);
+ const cursor: IDBCursorWithValue = req.result;
+ if (!cursor) {
+ break;
+ }
+ cursor.continue();
+ vals.push(cursor.value);
+ }
+
+ await promiseFromTransaction(tx);
+
+ t.deepEqual(vals, expected);
+ }
+
+ async function doCursorIndexQuery(
+ range: IDBKeyRange | IDBValidKey | undefined,
+ direction: IDBCursorDirection | undefined,
+ expected: any[],
+ ): Promise<void> {
+ const tx = db.transaction("bla", "readwrite");
+ const store = tx.objectStore("bla");
+ const index = store.index("by_y");
+ const vals: any[] = [];
+
+ const req = index.openCursor(range, direction);
+ while (1) {
+ await promiseFromRequest(req);
+ const cursor: IDBCursorWithValue = req.result;
+ if (!cursor) {
+ break;
+ }
+ cursor.continue();
+ vals.push(cursor.value);
+ }
+
+ await promiseFromTransaction(tx);
+
+ t.deepEqual(vals, expected);
+ }
+
+ await doCursorStoreQuery(undefined, undefined, [
+ {
+ x: 0,
+ y: "a",
+ },
+ {
+ x: 2,
+ y: "a",
+ },
+ {
+ x: 4,
+ y: "b",
+ },
+ {
+ x: 8,
+ y: "b",
+ },
+ {
+ x: 10,
+ y: "c",
+ },
+ {
+ x: 12,
+ y: "c",
+ },
+ ]);
+
+ await doCursorStoreQuery(
+ BridgeIDBKeyRange.bound(0, 12, true, true),
+ undefined,
+ [
+ {
+ x: 2,
+ y: "a",
+ },
+ {
+ x: 4,
+ y: "b",
+ },
+ {
+ x: 8,
+ y: "b",
+ },
+ {
+ x: 10,
+ y: "c",
+ },
+ ],
+ );
+
+ await doCursorIndexQuery(
+ BridgeIDBKeyRange.bound("a", "c", true, true),
+ undefined,
+ [
+ {
+ x: 4,
+ y: "b",
+ },
+ {
+ x: 8,
+ y: "b",
+ },
+ ],
+ );
+
+ await doCursorIndexQuery(undefined, "nextunique", [
+ {
+ x: 0,
+ y: "a",
+ },
+ {
+ x: 4,
+ y: "b",
+ },
+ {
+ x: 10,
+ y: "c",
+ },
+ ]);
+
+ await doCursorIndexQuery(undefined, "prevunique", [
+ {
+ x: 10,
+ y: "c",
+ },
+ {
+ x: 4,
+ y: "b",
+ },
+ {
+ x: 0,
+ y: "a",
+ },
+ ]);
+
+ db.close();
+
+ t.pass();
+});