From c363458374b1e5f97768c1cafc84a5dcb65921d5 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 16 Feb 2021 17:18:40 +0100 Subject: more WPTs --- .../src/idb-wpt-ported/idbfactory-open.test.ts | 406 ++++++++++++++++++++- .../idb-bridge/src/idb-wpt-ported/wptsupport.ts | 121 ++++++ 2 files changed, 520 insertions(+), 7 deletions(-) diff --git a/packages/idb-bridge/src/idb-wpt-ported/idbfactory-open.test.ts b/packages/idb-bridge/src/idb-wpt-ported/idbfactory-open.test.ts index 68d58a162..411a6c69a 100644 --- a/packages/idb-bridge/src/idb-wpt-ported/idbfactory-open.test.ts +++ b/packages/idb-bridge/src/idb-wpt-ported/idbfactory-open.test.ts @@ -1,5 +1,5 @@ import test from "ava"; -import { createdb, idbFactory } from "./wptsupport"; +import { createdb, format_value, idbFactory } from "./wptsupport"; // IDBFactory.open() - request has no source test("WPT idbfactory-open.htm", async (t) => { @@ -57,19 +57,411 @@ test("WPT idbfactory-open3.htm", async (t) => { t.pass(); }); - // IDBFactory.open() - new database has default version test("WPT idbfactory-open4.htm", async (t) => { const indexedDB = idbFactory; await new Promise((resolve, reject) => { - var open_rq = createdb(t, __filename + '-database_name'); + var open_rq = createdb(t, __filename + "-database_name"); + + open_rq.onupgradeneeded = function (e: any) { + t.deepEqual(e.target.result.version, 1, "db.version"); + }; + open_rq.onsuccess = function (e: any) { + t.deepEqual(e.target.result.version, 1, "db.version"); + resolve(); + }; + }); + t.pass(); +}); + +// IDBFactory.open() - new database is empty +test("WPT idbfactory-open5.htm", async (t) => { + const indexedDB = idbFactory; + await new Promise((resolve, reject) => { + var open_rq = createdb(t, __filename + "-database_name"); + + open_rq.onupgradeneeded = function () {}; + open_rq.onsuccess = function (e: any) { + t.deepEqual( + e.target.result.objectStoreNames.length, + 0, + "objectStoreNames.length", + ); + resolve(); + }; + }); + t.pass(); +}); + +// IDBFactory.open() - open database with a lower version than current +test("WPT idbfactory-open6.htm", async (t) => { + const indexedDB = idbFactory; + await new Promise((resolve, reject) => { + var open_rq = createdb(t, undefined, 13); + var did_upgrade = false; + var open_rq2: any; + + open_rq.onupgradeneeded = function () {}; + open_rq.onsuccess = function (e: any) { + var db = e.target.result; + db.close(); - open_rq.onupgradeneeded = function(e: any) { - t.deepEqual(e.target.result.version, 1, "db.version"); + open_rq2 = indexedDB.open(db.name, 14); + open_rq2.onupgradeneeded = function () {}; + open_rq2.onsuccess = open_previous_db; + open_rq2.onerror = () => t.fail("Unexpected error"); }; - open_rq.onsuccess = function(e: any) { - t.deepEqual(e.target.result.version, 1, "db.version"); + + function open_previous_db(e: any) { + var open_rq3 = indexedDB.open(e.target.result.name, 13); + open_rq3.onerror = function (e: any) { + t.deepEqual(e.target.error.name, "VersionError", "e.target.error.name"); + open_rq2.result.close(); resolve(); + }; + open_rq3.onupgradeneeded = () => t.fail("Unexpected upgradeneeded"); + open_rq3.onsuccess = () => t.fail("Unexpected success"); + } + }); + t.pass(); +}); + +// IDBFactory.open() - open database with a higher version than current +test("WPT idbfactory-open7.htm", async (t) => { + const indexedDB = idbFactory; + await new Promise((resolve, reject) => { + var open_rq = createdb(t, undefined, 13); + var did_upgrade = false; + var open_rq2: any; + + open_rq.onupgradeneeded = function () {}; + open_rq.onsuccess = function (e: any) { + var db = e.target.result; + db.close(); + + open_rq2 = indexedDB.open(db.name, 14); + open_rq2.onupgradeneeded = function () { + did_upgrade = true; + }; + open_rq2.onsuccess = open_current_db; + open_rq2.onerror = () => t.fail("Unexpected error"); + }; + + function open_current_db(e: any) { + var open_rq3 = indexedDB.open(e.target.result.name); + open_rq3.onsuccess = function (e: any) { + t.deepEqual(e.target.result.version, 14, "db.version"); + open_rq2.result.close(); + open_rq3.result.close(); + resolve(); + }; + open_rq3.onupgradeneeded = () => t.fail("Unexpected upgradeneeded"); + open_rq3.onerror = () => t.fail("Unexpected error"); + + t.true(did_upgrade, "did upgrade"); + } + }); + t.pass(); +}); + +// IDBFactory.open() - error in version change transaction aborts open +test("WPT idbfactory-open8.htm", async (t) => { + const indexedDB = idbFactory; + await new Promise((resolve, reject) => { + var open_rq = createdb(t, undefined, 13); + var did_upgrade = false; + var did_db_abort = false; + + open_rq.onupgradeneeded = function (e: any) { + did_upgrade = true; + e.target.result.onabort = function () { + did_db_abort = true; + }; + e.target.transaction.abort(); + }; + open_rq.onerror = function (e: any) { + t.true(did_upgrade); + t.deepEqual(e.target.error.name, "AbortError", "target.error"); + resolve(); + }; + }); + t.pass(); +}); + +// IDBFactory.open() - errors in version argument +test("WPT idbfactory-open9.htm", async (t) => { + const indexedDB = idbFactory; + function should_throw(val: any, name?: string) { + if (!name) { + name = typeof val == "object" && val ? "object" : format_value(val); + } + t.throws( + () => { + indexedDB.open("test", val); + }, + { instanceOf: TypeError }, + "Calling open() with version argument " + + name + + " should throw TypeError.", + ); + } + + should_throw(-1); + should_throw(-0.5); + should_throw(0); + should_throw(0.5); + should_throw(0.8); + should_throw(0x20000000000000); // Number.MAX_SAFE_INTEGER + 1 + should_throw(NaN); + should_throw(Infinity); + should_throw(-Infinity); + should_throw("foo"); + should_throw(null); + should_throw(false); + + should_throw({ + toString: function () { + t.fail("toString should not be called for ToPrimitive [Number]"); + }, + valueOf: function () { + return 0; + }, + }); + should_throw( + { + toString: function () { + return 0; + }, + valueOf: function () { + return {}; + }, + }, + "object (second)", + ); + should_throw( + { + toString: function () { + return {}; + }, + valueOf: function () { + return {}; + }, + }, + "object (third)", + ); + + /* Valid */ + + async function should_work(val: any, expected_version: number) { + var name = format_value(val); + var dbname = "test-db-does-not-exist"; + + await t.notThrowsAsync(async () => { + return new Promise((resolve, reject) => { + indexedDB.deleteDatabase(dbname); + var rq = indexedDB.open(dbname, val); + rq.onupgradeneeded = function () { + var db = rq.result; + t.deepEqual(db.version, expected_version, "version"); + rq!.transaction!.abort(); + }; + rq.onsuccess = () => t.fail("open should fail"); + rq.onerror = () => resolve(); + }); + }, "Calling open() with version argument " + name + " should not throw."); + } + + await should_work(1.5, 1); + await should_work(Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER); // 0x20000000000000 - 1 + await should_work(undefined, 1); +}); + +// IDBFactory.open() - error in version change transaction aborts open +test("WPT idbfactory-open10.htm", async (t) => { + const indexedDB = idbFactory; + await new Promise((resolve, reject) => { + var db: any, db2: any; + var open_rq = createdb(t, undefined, 9); + + open_rq.onupgradeneeded = function (e: any) { + db = e.target.result; + + var st = db.createObjectStore("store"); + st.createIndex("index", "i"); + + t.deepEqual(db.version, 9, "first db.version"); + t.true( + db.objectStoreNames.contains("store"), + "objectStoreNames contains store", + ); + t.true(st.indexNames.contains("index"), "indexNames contains index"); + + st.add({ i: "Joshua" }, 1); + st.add({ i: "Jonas" }, 2); + }; + open_rq.onsuccess = function (e) { + db.close(); + var open_rq2 = indexedDB.open(db.name, 10); + open_rq2.onupgradeneeded = function (e: any) { + db2 = e.target.result; + + db2.createObjectStore("store2"); + + var store = open_rq2.transaction!.objectStore("store"); + store.createIndex("index2", "i"); + + t.deepEqual(db2.version, 10, "db2.version"); + + t.true( + db2.objectStoreNames.contains("store"), + "second objectStoreNames contains store", + ); + t.true( + db2.objectStoreNames.contains("store2"), + "second objectStoreNames contains store2", + ); + t.true( + store.indexNames.contains("index"), + "second indexNames contains index", + ); + t.true( + store.indexNames.contains("index2"), + "second indexNames contains index2", + ); + + store.add({ i: "Odin" }, 3); + store.put({ i: "Sicking" }, 2); + + open_rq2.transaction!.abort(); + }; + open_rq2.onerror = function () { + t.deepEqual(db2.version, 9, "db2.version after error"); + t.true( + db2.objectStoreNames.contains("store"), + "objectStoreNames contains store after error", + ); + t.false( + db2.objectStoreNames.contains("store2"), + "objectStoreNames not contains store2 after error", + ); + + var open_rq3 = indexedDB.open(db.name); + open_rq3.onsuccess = function (e: any) { + var db3 = e.target.result; + + t.true( + db3.objectStoreNames.contains("store"), + "third objectStoreNames contains store", + ); + t.false( + db3.objectStoreNames.contains("store2"), + "third objectStoreNames contains store2", + ); + + var st = db3.transaction("store").objectStore("store"); + + t.deepEqual(db3.version, 9, "db3.version"); + + t.true( + st.indexNames.contains("index"), + "third indexNames contains index", + ); + t.false( + st.indexNames.contains("index2"), + "third indexNames contains index2", + ); + + st.openCursor(null, "prev").onsuccess = function (e: any) { + t.deepEqual(e.target.result.key, 2, "opencursor(prev) key"); + t.deepEqual( + e.target.result.value.i, + "Jonas", + "opencursor(prev) value", + ); + }; + st.get(3).onsuccess = function (e: any) { + t.deepEqual(e.target.result, undefined, "get(3)"); + }; + + var idx = st.index("index"); + idx.getKey("Jonas").onsuccess = function (e: any) { + t.deepEqual(e.target.result, 2, "getKey(Jonas)"); + }; + idx.getKey("Odin").onsuccess = function (e: any) { + t.deepEqual(e.target.result, undefined, "getKey(Odin)"); + }; + idx.getKey("Sicking").onsuccess = function (e: any) { + t.deepEqual(e.target.result, undefined, "getKey(Sicking)"); + db3.close(); + resolve(); + }; + }; + }; + }; + }); + t.pass(); +}); + +// IDBFactory.open() - second open's transaction is available to get objectStores +test("WPT idbfactory-open11.htm", async (t) => { + const indexedDB = idbFactory; + await new Promise((resolve, reject) => { + var db: any; + var count_done = 0; + var open_rq = createdb(t); + + open_rq.onupgradeneeded = function (e: any) { + db = e.target.result; + + db.createObjectStore("store"); + assert_true( + db.objectStoreNames.contains("store"), + "objectStoreNames contains store", + ); + + var store = e.target.transaction.objectStore("store"); + assert_equals(store.name, "store", "store.name"); + + store.add("data", 1); + + store.count().onsuccess = this.step_func(function (e) { + assert_equals(e.target.result, 1, "count()"); + count_done++; + }); + + store.add("data2", 2); + }; + open_rq.onsuccess = function (e) { + var store = db.transaction("store").objectStore("store"); + assert_equals(store.name, "store", "store.name"); + store.count().onsuccess = this.step_func(function (e) { + assert_equals(e.target.result, 2, "count()"); + count_done++; + }); + db.close(); + + var open_rq2 = indexedDB.open(db.name, 10); + open_rq2.onupgradeneeded = function (e: any) { + var db2 = e.target.result; + t.true( + db2.objectStoreNames.contains("store"), + "objectStoreNames contains store", + ); + var store = open_rq2.transaction!.objectStore("store"); + t.deepEqual(store.name, "store", "store.name"); + + store.add("data3", 3); + + store.count().onsuccess = function (e: any) { + t.deepEqual(e.target.result, 3, "count()"); + count_done++; + + t.deepEqual(count_done, 3, "count_done"); + + db2.close(); + resolve(); + }; + }; }; }); t.pass(); diff --git a/packages/idb-bridge/src/idb-wpt-ported/wptsupport.ts b/packages/idb-bridge/src/idb-wpt-ported/wptsupport.ts index 2d52ea074..4a7205f8d 100644 --- a/packages/idb-bridge/src/idb-wpt-ported/wptsupport.ts +++ b/packages/idb-bridge/src/idb-wpt-ported/wptsupport.ts @@ -301,3 +301,124 @@ export function createNotBooksStore( store.createIndex("not_by_title", "title", { unique: true }); return store; } + +/* + * Return a string truncated to the given length, with ... added at the end + * if it was longer. + */ +function truncate(s: string, len: number): string { + if (s.length > len) { + return s.substring(0, len - 3) + "..."; + } + return s; +} + +var replacements = { + "0": "0", + "1": "x01", + "2": "x02", + "3": "x03", + "4": "x04", + "5": "x05", + "6": "x06", + "7": "x07", + "8": "b", + "9": "t", + "10": "n", + "11": "v", + "12": "f", + "13": "r", + "14": "x0e", + "15": "x0f", + "16": "x10", + "17": "x11", + "18": "x12", + "19": "x13", + "20": "x14", + "21": "x15", + "22": "x16", + "23": "x17", + "24": "x18", + "25": "x19", + "26": "x1a", + "27": "x1b", + "28": "x1c", + "29": "x1d", + "30": "x1e", + "31": "x1f", + "0xfffd": "ufffd", + "0xfffe": "ufffe", + "0xffff": "uffff", +}; + +/* + * Convert a value to a nice, human-readable string + */ +export function format_value(val: any, seen?: any): string { + if (!seen) { + seen = []; + } + if (typeof val === "object" && val !== null) { + if (seen.indexOf(val) >= 0) { + return "[...]"; + } + seen.push(val); + } + if (Array.isArray(val)) { + let output = "["; + // @ts-ignore + if (val.beginEllipsis !== undefined) { + output += "…, "; + } + output += val + .map(function (x) { + return format_value(x, seen); + }) + .join(", "); + // @ts-ignore + if (val.endEllipsis !== undefined) { + output += ", …"; + } + return output + "]"; + } + + switch (typeof val) { + case "string": + val = val.replace(/\\/g, "\\\\"); + for (var p in replacements) { + // @ts-ignore + var replace = "\\" + replacements[p]; + // @ts-ignore + val = val.replace(RegExp(String.fromCharCode(p), "g"), replace); + } + return '"' + val.replace(/"/g, '\\"') + '"'; + case "boolean": + case "undefined": + return String(val); + case "number": + // In JavaScript, -0 === 0 and String(-0) == "0", so we have to + // special-case. + if (val === -0 && 1 / val === -Infinity) { + return "-0"; + } + return String(val); + case "object": + if (val === null) { + return "null"; + } + + /* falls through */ + default: + try { + return typeof val + ' "' + truncate(String(val), 1000) + '"'; + } catch (e) { + return ( + "[stringifying object threw " + + String(e) + + " with type " + + String(typeof e) + + "]" + ); + } + } +} -- cgit v1.2.3