idb: fix renaming, make renaming tests pass

This commit is contained in:
Florian Dold 2021-02-17 11:45:28 +01:00
parent 47bddb2a1b
commit 69b62c62a0
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
4 changed files with 264 additions and 181 deletions

View File

@ -55,7 +55,7 @@ import {
TransactionInactiveError,
VersionError,
} from "./util/errors";
import { fakeDOMStringList } from "./util/fakeDOMStringList";
import { FakeDOMStringList, fakeDOMStringList } from "./util/fakeDOMStringList";
import FakeEvent from "./util/FakeEvent";
import FakeEventTarget from "./util/FakeEventTarget";
import { makeStoreKeyValue } from "./util/makeStoreKeyValue";
@ -73,12 +73,6 @@ import { valueToKey } from "./util/valueToKey";
/** @public */
export type CursorSource = BridgeIDBIndex | BridgeIDBObjectStore;
/** @public */
export interface FakeDOMStringList extends Array<string> {
contains: (value: string) => boolean;
item: (i: number) => string | undefined;
}
/** @public */
export interface RequestObj {
operation: () => Promise<any>;
@ -828,7 +822,9 @@ export class BridgeIDBFactory {
);
// We need to expose the new version number to the upgrade transaction.
db._schema = this.backend.getCurrentTransactionSchema(backendTransaction);
db._schema = this.backend.getCurrentTransactionSchema(
backendTransaction,
);
const transaction = db._internalTransaction(
[],
@ -1405,6 +1401,14 @@ export class BridgeIDBObjectStore implements IDBObjectStore {
this._transaction._db._schema = this._backend.getCurrentTransactionSchema(
btx,
);
// We don't modify scope, as the scope of the transaction
// doesn't matter if we're in an upgrade transaction.
this._transaction._objectStoresCache.delete(oldName);
this._transaction._objectStoresCache.set(newName, this);
this._transaction._cachedObjectStoreNames = undefined;
this._name = newName;
}
public _store(value: any, key: IDBValidKey | undefined, overwrite: boolean) {
@ -1910,9 +1914,19 @@ export class BridgeIDBTransaction
_backendTransaction?: DatabaseTransaction;
_objectStoreNames: FakeDOMStringList;
_cachedObjectStoreNames: DOMStringList | undefined;
get objectStoreNames(): DOMStringList {
return this._objectStoreNames as DOMStringList;
if (!this._cachedObjectStoreNames) {
if (this._openRequest) {
this._cachedObjectStoreNames = this._db.objectStoreNames;
} else {
this._cachedObjectStoreNames = fakeDOMStringList(
Array.from(this._scope).sort(),
);
}
}
return this._cachedObjectStoreNames;
}
mode: IDBTransactionMode;
_db: BridgeIDBDatabase;
@ -1961,7 +1975,6 @@ export class BridgeIDBTransaction
this._backendTransaction = backendTransaction;
this.mode = mode;
this._db = db;
this._objectStoreNames = fakeDOMStringList(Array.from(this._scope).sort());
this._db._transactions.push(this);
@ -2049,12 +2062,24 @@ export class BridgeIDBTransaction
throw new InvalidStateError();
}
if (!this._db._schema.objectStores[name]) {
throw new NotFoundError();
}
if (!this._db._upgradeTransaction) {
if (!this._scope.has(name)) {
throw new NotFoundError();
}
}
const objectStore = this._objectStoresCache.get(name);
if (objectStore !== undefined) {
return objectStore;
}
return new BridgeIDBObjectStore(this, name);
const newObjectStore = new BridgeIDBObjectStore(this, name);
this._objectStoresCache.set(name, newObjectStore);
return newObjectStore;
}
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-asynchronously-executing-a-request

View File

@ -1,4 +1,6 @@
import test from "ava";
import { BridgeIDBVersionChangeEvent } from "../bridge-idb";
import FakeEvent from "../util/FakeEvent";
import { createdb, format_value, idbFactory } from "./wptsupport";
// IDBFactory.open() - request has no source
@ -466,3 +468,62 @@ test("WPT idbfactory-open11.htm", async (t) => {
});
t.pass();
});
// IDBFactory.open() - upgradeneeded gets VersionChangeEvent
test("WPT idbfactory-open12.htm", async (t) => {
const indexedDB = idbFactory;
var db: any;
var open_rq = createdb(t, undefined, 9);
await new Promise<void>((resolve, reject) => {
open_rq.onupgradeneeded = function (e: any) {
db = e.target.result;
t.true(
e instanceof BridgeIDBVersionChangeEvent,
"e instanceof IDBVersionChangeEvent",
);
t.deepEqual(e.oldVersion, 0, "oldVersion");
t.deepEqual(e.newVersion, 9, "newVersion");
t.deepEqual(e.type, "upgradeneeded", "event type");
t.deepEqual(db.version, 9, "db.version");
};
open_rq.onsuccess = function (e) {
t.true(e instanceof FakeEvent, "e instanceof Event");
t.false(
e instanceof BridgeIDBVersionChangeEvent,
"e not instanceof IDBVersionChangeEvent",
);
t.deepEqual(e.type, "success", "event type");
resolve();
};
});
await new Promise<void>((resolve, reject) => {
/**
* Second test
*/
db.onversionchange = function () {
db.close();
};
var open_rq2 = createdb(t, db.name, 10);
open_rq2.onupgradeneeded = function (e: any) {
var db2 = e.target.result;
t.true(
e instanceof BridgeIDBVersionChangeEvent,
"e instanceof IDBVersionChangeEvent",
);
t.deepEqual(e.oldVersion, 9, "oldVersion");
t.deepEqual(e.newVersion, 10, "newVersion");
t.deepEqual(e.type, "upgradeneeded", "event type");
t.deepEqual(db2.version, 10, "new db.version");
resolve();
};
});
t.pass();
});

View File

@ -1,13 +1,10 @@
import test, { ExecutionContext } from "ava";
import { BridgeIDBRequest } from "..";
import { EventTarget, IDBDatabase } from "../idbtypes";
import {
checkStoreContents,
checkStoreGenerator,
checkStoreIndexes,
createBooksStore,
createDatabase,
createdb,
createNotBooksStore,
migrateDatabase,
} from "./wptsupport";
@ -15,183 +12,175 @@ import {
// 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);
let bookStore: any = null;
let bookStore2: any = null;
let renamedBookStore: any = null;
let renamedBookStore2: any = null;
await 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((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";
.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",
"IDBObjectStore name should change immediately after a rename",
);
t.deepEqual(
renamedBookStore2.name,
"renamed_books",
"IDBObjectStore obtained after the rename transaction should " +
"reflect the new name",
database.objectStoreNames as any,
["renamed_books"],
"IDBDatabase.objectStoreNames should immediately reflect the " +
"rename",
);
});
});
t.pass();
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",
);
});
});
// 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";
let renamedBookStore: any = null,
renamedBookStore2: any = null;
await 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(
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 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",
"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((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();
.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",
);
});
});
// Renames the 'books' store to 'renamed_books'.
@ -333,13 +322,13 @@ test("WPT idbobjectstore-rename-store.html (IndexedDB object store swapping via
"IDBDatabase.objectStoreNames should immediately reflect the swap",
);
t.deepEqual(
t.is(
transaction.objectStore("books"),
notBookStore,
'IDBTransaction.objectStore should return the original "books" ' +
'store when queried with "not_books" after the swap',
);
t.deepEqual(
t.is(
transaction.objectStore("not_books"),
bookStore,
"IDBTransaction.objectStore should return the original " +
@ -452,9 +441,12 @@ test("WPT idbobjectstore-rename-store.html (IndexedDB object store rename string
t.pass();
});
function rename_test_macro(t: ExecutionContext, escapedName: string) {
function rename_test_macro(
t: ExecutionContext,
escapedName: string,
): Promise<void> {
const name = JSON.parse('"' + escapedName + '"');
createDatabase(t, (database, transaction) => {
return createDatabase(t, (database, transaction) => {
createBooksStore(t, database);
})
.then((database) => {

View File

@ -14,10 +14,12 @@
* permissions and limitations under the License.
*/
import { DOMStringList } from "../idbtypes";
/** @public */
export interface FakeDOMStringList extends Array<string> {
contains: (value: string) => boolean;
item: (i: number) => string | undefined;
item: (i: number) => string | null;
}
// Would be nicer to sublcass Array, but I'd have to sacrifice Node 4 support to do that.
@ -26,13 +28,16 @@ export const fakeDOMStringList = (arr: string[]): FakeDOMStringList => {
const arr2 = arr.slice();
Object.defineProperty(arr2, "contains", {
// tslint:disable-next-line object-literal-shorthand
value: (value: string) => arr2.indexOf(value) >= 0,
});
Object.defineProperty(arr2, "item", {
// tslint:disable-next-line object-literal-shorthand
value: (i: number) => arr2[i],
value: (i: number) => {
if (i < 0 || i >= arr2.length) {
return null;
}
return arr2[i];
},
});
return arr2 as FakeDOMStringList;