idb: fix 'prevunique' iteration and other bugs
This commit is contained in:
parent
3eced74a88
commit
f0d820d8c6
@ -234,7 +234,7 @@ test("Spec: Example 1 Part 3", async (t) => {
|
||||
await promiseFromRequest(request7);
|
||||
cursor = request7.result;
|
||||
t.is(cursor.value.author, "Fred");
|
||||
t.is(cursor.value.isbn, 234567);
|
||||
t.is(cursor.value.isbn, 123456);
|
||||
cursor.continue();
|
||||
|
||||
await promiseFromRequest(request7);
|
||||
|
@ -1137,127 +1137,140 @@ export class MemoryBackend implements Backend {
|
||||
let indexEntry: IndexRecord | undefined;
|
||||
indexEntry = indexData.get(indexPos);
|
||||
if (!indexEntry) {
|
||||
const res = indexData.nextHigherPair(indexPos);
|
||||
const res = forward
|
||||
? indexData.nextHigherPair(indexPos)
|
||||
: indexData.nextLowerPair(indexPos);
|
||||
if (res) {
|
||||
indexEntry = res[1];
|
||||
indexPos = indexEntry.indexKey;
|
||||
}
|
||||
}
|
||||
|
||||
let primkeySubPos = 0;
|
||||
|
||||
// Sort out the case where the index key is the same, so we have
|
||||
// to get the prev/next primary key
|
||||
if (
|
||||
indexEntry !== undefined &&
|
||||
req.lastIndexPosition !== undefined &&
|
||||
compareKeys(indexEntry.indexKey, req.lastIndexPosition) === 0
|
||||
) {
|
||||
let pos = forward ? 0 : indexEntry.primaryKeys.length - 1;
|
||||
this.enableTracing &&
|
||||
console.log("number of primary keys", indexEntry.primaryKeys.length);
|
||||
this.enableTracing && console.log("start pos is", pos);
|
||||
// Advance past the lastObjectStorePosition
|
||||
do {
|
||||
const cmpResult = compareKeys(
|
||||
req.lastObjectStorePosition,
|
||||
indexEntry.primaryKeys[pos],
|
||||
);
|
||||
this.enableTracing && console.log("cmp result is", cmpResult);
|
||||
if ((forward && cmpResult < 0) || (!forward && cmpResult > 0)) {
|
||||
if (unique) {
|
||||
while (1) {
|
||||
if (req.limit != 0 && numResults == req.limit) {
|
||||
break;
|
||||
}
|
||||
pos += forward ? 1 : -1;
|
||||
this.enableTracing && console.log("now pos is", pos);
|
||||
} while (pos >= 0 && pos < indexEntry.primaryKeys.length);
|
||||
|
||||
// Make sure we're at least at advancedPrimaryPos
|
||||
while (
|
||||
primaryPos !== undefined &&
|
||||
pos >= 0 &&
|
||||
pos < indexEntry.primaryKeys.length
|
||||
) {
|
||||
const cmpResult = compareKeys(
|
||||
primaryPos,
|
||||
indexEntry.primaryKeys[pos],
|
||||
);
|
||||
if ((forward && cmpResult <= 0) || (!forward && cmpResult >= 0)) {
|
||||
if (indexPos === undefined) {
|
||||
break;
|
||||
}
|
||||
if (!range.includes(indexPos)) {
|
||||
break;
|
||||
}
|
||||
if (indexEntry === undefined) {
|
||||
break;
|
||||
}
|
||||
pos += forward ? 1 : -1;
|
||||
}
|
||||
primkeySubPos = pos;
|
||||
} else if (indexEntry !== undefined) {
|
||||
primkeySubPos = forward ? 0 : indexEntry.primaryKeys.length - 1;
|
||||
}
|
||||
|
||||
if (this.enableTracing) {
|
||||
console.log("subPos=", primkeySubPos);
|
||||
console.log("indexPos=", indexPos);
|
||||
}
|
||||
if (
|
||||
req.lastIndexPosition === null ||
|
||||
req.lastIndexPosition === undefined ||
|
||||
compareKeys(indexEntry.indexKey, req.lastIndexPosition) !== 0
|
||||
) {
|
||||
indexKeys.push(indexEntry.indexKey);
|
||||
primaryKeys.push(indexEntry.primaryKeys[0]);
|
||||
numResults++;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
if (req.limit != 0 && numResults == req.limit) {
|
||||
break;
|
||||
}
|
||||
if (indexPos === undefined) {
|
||||
break;
|
||||
}
|
||||
if (!range.includes(indexPos)) {
|
||||
break;
|
||||
}
|
||||
if (indexEntry === undefined) {
|
||||
break;
|
||||
}
|
||||
if (
|
||||
primkeySubPos < 0 ||
|
||||
primkeySubPos >= indexEntry.primaryKeys.length
|
||||
) {
|
||||
const res: any = forward
|
||||
? indexData.nextHigherPair(indexPos)
|
||||
: indexData.nextLowerPair(indexPos);
|
||||
if (res) {
|
||||
indexPos = res[1].indexKey;
|
||||
indexEntry = res[1];
|
||||
primkeySubPos = forward ? 0 : indexEntry!.primaryKeys.length - 1;
|
||||
continue;
|
||||
indexEntry = res[1] as IndexRecord;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let primkeySubPos = 0;
|
||||
|
||||
// Skip repeated index keys if unique results are requested.
|
||||
let skip = false;
|
||||
if (unique) {
|
||||
if (
|
||||
indexKeys.length > 0 &&
|
||||
compareKeys(
|
||||
indexEntry.indexKey,
|
||||
indexKeys[indexKeys.length - 1],
|
||||
) === 0
|
||||
// Sort out the case where the index key is the same, so we have
|
||||
// to get the prev/next primary key
|
||||
if (
|
||||
indexEntry !== undefined &&
|
||||
req.lastIndexPosition !== undefined &&
|
||||
compareKeys(indexEntry.indexKey, req.lastIndexPosition) === 0
|
||||
) {
|
||||
let pos = forward ? 0 : indexEntry.primaryKeys.length - 1;
|
||||
this.enableTracing &&
|
||||
console.log(
|
||||
"number of primary keys",
|
||||
indexEntry.primaryKeys.length,
|
||||
);
|
||||
this.enableTracing && console.log("start pos is", pos);
|
||||
// Advance past the lastObjectStorePosition
|
||||
do {
|
||||
const cmpResult = compareKeys(
|
||||
req.lastObjectStorePosition,
|
||||
indexEntry.primaryKeys[pos],
|
||||
);
|
||||
this.enableTracing && console.log("cmp result is", cmpResult);
|
||||
if ((forward && cmpResult < 0) || (!forward && cmpResult > 0)) {
|
||||
break;
|
||||
}
|
||||
pos += forward ? 1 : -1;
|
||||
this.enableTracing && console.log("now pos is", pos);
|
||||
} while (pos >= 0 && pos < indexEntry.primaryKeys.length);
|
||||
|
||||
// Make sure we're at least at advancedPrimaryPos
|
||||
while (
|
||||
primaryPos !== undefined &&
|
||||
pos >= 0 &&
|
||||
pos < indexEntry.primaryKeys.length
|
||||
) {
|
||||
skip = true;
|
||||
}
|
||||
if (
|
||||
req.lastIndexPosition !== undefined &&
|
||||
compareKeys(indexPos, req.lastIndexPosition) === 0
|
||||
) {
|
||||
skip = true;
|
||||
const cmpResult = compareKeys(
|
||||
primaryPos,
|
||||
indexEntry.primaryKeys[pos],
|
||||
);
|
||||
if ((forward && cmpResult <= 0) || (!forward && cmpResult >= 0)) {
|
||||
break;
|
||||
}
|
||||
pos += forward ? 1 : -1;
|
||||
}
|
||||
primkeySubPos = pos;
|
||||
} else if (indexEntry !== undefined) {
|
||||
primkeySubPos = forward ? 0 : indexEntry.primaryKeys.length - 1;
|
||||
}
|
||||
if (!skip) {
|
||||
if (this.enableTracing) {
|
||||
console.log(`not skipping!, subPos=${primkeySubPos}`);
|
||||
|
||||
if (this.enableTracing) {
|
||||
console.log("subPos=", primkeySubPos);
|
||||
console.log("indexPos=", indexPos);
|
||||
}
|
||||
|
||||
while (1) {
|
||||
if (req.limit != 0 && numResults == req.limit) {
|
||||
break;
|
||||
}
|
||||
if (indexPos === undefined) {
|
||||
break;
|
||||
}
|
||||
if (!range.includes(indexPos)) {
|
||||
break;
|
||||
}
|
||||
if (indexEntry === undefined) {
|
||||
break;
|
||||
}
|
||||
if (
|
||||
primkeySubPos < 0 ||
|
||||
primkeySubPos >= indexEntry.primaryKeys.length
|
||||
) {
|
||||
const res: any = forward
|
||||
? indexData.nextHigherPair(indexPos)
|
||||
: indexData.nextLowerPair(indexPos);
|
||||
if (res) {
|
||||
indexPos = res[1].indexKey;
|
||||
indexEntry = res[1];
|
||||
primkeySubPos = forward ? 0 : indexEntry!.primaryKeys.length - 1;
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
indexKeys.push(indexEntry.indexKey);
|
||||
primaryKeys.push(indexEntry.primaryKeys[primkeySubPos]);
|
||||
numResults++;
|
||||
} else {
|
||||
if (this.enableTracing) {
|
||||
console.log("skipping!");
|
||||
}
|
||||
primkeySubPos += forward ? 1 : -1;
|
||||
}
|
||||
primkeySubPos += forward ? 1 : -1;
|
||||
}
|
||||
|
||||
// Now we can collect the values based on the primary keys,
|
||||
|
@ -102,7 +102,7 @@ export interface RecordGetRequest {
|
||||
*/
|
||||
advancePrimaryKey?: IDBValidKey;
|
||||
/**
|
||||
* Maximum number of resuts to return.
|
||||
* Maximum number of results to return.
|
||||
* If -1, return all available results
|
||||
*/
|
||||
limit: number;
|
||||
|
@ -702,7 +702,8 @@ export class BridgeIDBDatabase extends FakeEventTarget implements IDBDatabase {
|
||||
this._transactions.push(tx);
|
||||
|
||||
queueTask(() => {
|
||||
console.log("TRACE: calling auto-commit", this._getReadableName());
|
||||
BridgeIDBFactory.enableTracing &&
|
||||
console.log("TRACE: calling auto-commit", this._getReadableName());
|
||||
tx._start();
|
||||
});
|
||||
if (BridgeIDBFactory.enableTracing) {
|
||||
@ -941,7 +942,8 @@ export class BridgeIDBFactory {
|
||||
|
||||
// We re-use the same transaction (as per spec) here.
|
||||
transaction._active = true;
|
||||
if (transaction._aborted) {
|
||||
|
||||
if (db._closed || db._closePending) {
|
||||
request.result = undefined;
|
||||
request.error = new AbortError();
|
||||
request.readyState = "done";
|
||||
@ -951,6 +953,23 @@ export class BridgeIDBFactory {
|
||||
});
|
||||
event2.eventPath = [];
|
||||
request.dispatchEvent(event2);
|
||||
} else if (transaction._aborted) {
|
||||
try {
|
||||
await db._backend.close(db._backendConnection);
|
||||
} catch (e) {
|
||||
console.error("failed to close database");
|
||||
}
|
||||
|
||||
request.result = undefined;
|
||||
request.error = new AbortError();
|
||||
request.readyState = "done";
|
||||
const event2 = new FakeEvent("error", {
|
||||
bubbles: false,
|
||||
cancelable: false,
|
||||
});
|
||||
event2.eventPath = [];
|
||||
request.dispatchEvent(event2);
|
||||
|
||||
} else {
|
||||
if (BridgeIDBFactory.enableTracing) {
|
||||
console.log("dispatching 'success' event for opening db");
|
||||
@ -2361,6 +2380,9 @@ export class BridgeIDBTransaction
|
||||
|
||||
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-fire-an-error-event
|
||||
this._active = true;
|
||||
queueTask(() => {
|
||||
this._active = false;
|
||||
});
|
||||
event = new FakeEvent("error", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
|
@ -1,3 +1,10 @@
|
||||
This directory contains test cases from the W3C Web Platform Tests suite for IndexedDB.
|
||||
|
||||
The original code for these tests can be found here: https://github.com/web-platform-tests/wpt/tree/master/IndexedDB
|
||||
The original code for these tests can be found here: https://github.com/web-platform-tests/wpt/tree/master/IndexedDB
|
||||
|
||||
The following tests are intentionally not included:
|
||||
* error-attributes.html (assumes we have a DOM)
|
||||
* file_support.sub.html (assumes we have a DOM)
|
||||
* fire-error-event-exception.html (ava can't test unhandled rejections)
|
||||
* fire-success-event-exception.html (ava can't test unhandled rejections)
|
||||
* fire-upgradeneeded-event-exception.html (ava can't test unhandled rejections)
|
@ -0,0 +1,44 @@
|
||||
import test from "ava";
|
||||
import { BridgeIDBCursor } from "..";
|
||||
import { BridgeIDBCursorWithValue } from "../bridge-idb";
|
||||
import { createdb } from "./wptsupport";
|
||||
|
||||
// When db.close is called in upgradeneeded, the db is cleaned up on refresh
|
||||
test.cb("WPT test close-in-upgradeneeded.htm", (t) => {
|
||||
var db: any;
|
||||
var open_rq = createdb(t);
|
||||
var sawTransactionComplete = false;
|
||||
|
||||
open_rq.onupgradeneeded = function (e: any) {
|
||||
db = e.target.result;
|
||||
t.deepEqual(db.version, 1);
|
||||
|
||||
db.createObjectStore("os");
|
||||
db.close();
|
||||
|
||||
e.target.transaction.oncomplete = function () {
|
||||
sawTransactionComplete = true;
|
||||
};
|
||||
};
|
||||
|
||||
open_rq.onerror = function (e: any) {
|
||||
t.true(sawTransactionComplete, "saw transaction.complete");
|
||||
|
||||
t.deepEqual(e.target.error.name, "AbortError");
|
||||
t.deepEqual(e.result, undefined);
|
||||
|
||||
t.true(!!db);
|
||||
t.deepEqual(db.version, 1);
|
||||
t.deepEqual(db.objectStoreNames.length, 1);
|
||||
t.throws(
|
||||
() => {
|
||||
db.transaction("os");
|
||||
},
|
||||
{
|
||||
name: "InvalidStateError",
|
||||
},
|
||||
);
|
||||
|
||||
t.end();
|
||||
};
|
||||
});
|
114
packages/idb-bridge/src/idb-wpt-ported/cursor-overloads.test.ts
Normal file
114
packages/idb-bridge/src/idb-wpt-ported/cursor-overloads.test.ts
Normal file
@ -0,0 +1,114 @@
|
||||
import test from "ava";
|
||||
import { BridgeIDBCursor } from "..";
|
||||
import { BridgeIDBCursorWithValue } from "../bridge-idb";
|
||||
import { createdb } from "./wptsupport";
|
||||
|
||||
// Validate the overloads of IDBObjectStore.openCursor(), IDBIndex.openCursor() and IDBIndex.openKeyCursor()
|
||||
test.cb("WPT test cursor-overloads.htm", (t) => {
|
||||
var db: any, trans: any, store: any, index: any;
|
||||
|
||||
var request = createdb(t);
|
||||
request.onupgradeneeded = function (e) {
|
||||
db = request.result;
|
||||
store = db.createObjectStore("store");
|
||||
index = store.createIndex("index", "value");
|
||||
store.put({ value: 0 }, 0);
|
||||
trans = request.transaction;
|
||||
trans.oncomplete = verifyOverloads;
|
||||
};
|
||||
|
||||
function verifyOverloads() {
|
||||
trans = db.transaction("store");
|
||||
store = trans.objectStore("store");
|
||||
index = store.index("index");
|
||||
|
||||
checkCursorDirection("store.openCursor()", "next");
|
||||
checkCursorDirection("store.openCursor(0)", "next");
|
||||
checkCursorDirection("store.openCursor(0, 'next')", "next");
|
||||
checkCursorDirection("store.openCursor(0, 'nextunique')", "nextunique");
|
||||
checkCursorDirection("store.openCursor(0, 'prev')", "prev");
|
||||
checkCursorDirection("store.openCursor(0, 'prevunique')", "prevunique");
|
||||
|
||||
checkCursorDirection("store.openCursor(IDBKeyRange.only(0))", "next");
|
||||
checkCursorDirection(
|
||||
"store.openCursor(IDBKeyRange.only(0), 'next')",
|
||||
"next",
|
||||
);
|
||||
checkCursorDirection(
|
||||
"store.openCursor(IDBKeyRange.only(0), 'nextunique')",
|
||||
"nextunique",
|
||||
);
|
||||
checkCursorDirection(
|
||||
"store.openCursor(IDBKeyRange.only(0), 'prev')",
|
||||
"prev",
|
||||
);
|
||||
checkCursorDirection(
|
||||
"store.openCursor(IDBKeyRange.only(0), 'prevunique')",
|
||||
"prevunique",
|
||||
);
|
||||
|
||||
checkCursorDirection("index.openCursor()", "next");
|
||||
checkCursorDirection("index.openCursor(0)", "next");
|
||||
checkCursorDirection("index.openCursor(0, 'next')", "next");
|
||||
checkCursorDirection("index.openCursor(0, 'nextunique')", "nextunique");
|
||||
checkCursorDirection("index.openCursor(0, 'prev')", "prev");
|
||||
checkCursorDirection("index.openCursor(0, 'prevunique')", "prevunique");
|
||||
|
||||
checkCursorDirection("index.openCursor(IDBKeyRange.only(0))", "next");
|
||||
checkCursorDirection(
|
||||
"index.openCursor(IDBKeyRange.only(0), 'next')",
|
||||
"next",
|
||||
);
|
||||
checkCursorDirection(
|
||||
"index.openCursor(IDBKeyRange.only(0), 'nextunique')",
|
||||
"nextunique",
|
||||
);
|
||||
checkCursorDirection(
|
||||
"index.openCursor(IDBKeyRange.only(0), 'prev')",
|
||||
"prev",
|
||||
);
|
||||
checkCursorDirection(
|
||||
"index.openCursor(IDBKeyRange.only(0), 'prevunique')",
|
||||
"prevunique",
|
||||
);
|
||||
|
||||
checkCursorDirection("index.openKeyCursor()", "next");
|
||||
checkCursorDirection("index.openKeyCursor(0)", "next");
|
||||
checkCursorDirection("index.openKeyCursor(0, 'next')", "next");
|
||||
checkCursorDirection("index.openKeyCursor(0, 'nextunique')", "nextunique");
|
||||
checkCursorDirection("index.openKeyCursor(0, 'prev')", "prev");
|
||||
checkCursorDirection("index.openKeyCursor(0, 'prevunique')", "prevunique");
|
||||
|
||||
checkCursorDirection("index.openKeyCursor(IDBKeyRange.only(0))", "next");
|
||||
checkCursorDirection(
|
||||
"index.openKeyCursor(IDBKeyRange.only(0), 'next')",
|
||||
"next",
|
||||
);
|
||||
checkCursorDirection(
|
||||
"index.openKeyCursor(IDBKeyRange.only(0), 'nextunique')",
|
||||
"nextunique",
|
||||
);
|
||||
checkCursorDirection(
|
||||
"index.openKeyCursor(IDBKeyRange.only(0), 'prev')",
|
||||
"prev",
|
||||
);
|
||||
checkCursorDirection(
|
||||
"index.openKeyCursor(IDBKeyRange.only(0), 'prevunique')",
|
||||
"prevunique",
|
||||
);
|
||||
|
||||
t.end();
|
||||
}
|
||||
|
||||
function checkCursorDirection(statement: string, direction: string) {
|
||||
request = eval(statement);
|
||||
request.onsuccess = function (event: any) {
|
||||
t.notDeepEqual(event.target.result, null, "Check the result is not null");
|
||||
t.deepEqual(
|
||||
event.target.result.direction,
|
||||
direction,
|
||||
"Check the result direction",
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
@ -7,7 +7,7 @@ import {
|
||||
keep_alive,
|
||||
} from "./wptsupport";
|
||||
|
||||
test("WPT test abort-in-initial-upgradeneeded.htm", async (t) => {
|
||||
test("WPT test abort-in-initial-upgradeneeded.htm (subtest 1)", async (t) => {
|
||||
// Transactions are active during success handlers
|
||||
await indexeddb_test(
|
||||
t,
|
||||
@ -24,10 +24,9 @@ test("WPT test abort-in-initial-upgradeneeded.htm", async (t) => {
|
||||
);
|
||||
|
||||
const request = tx.objectStore("store").get(4242);
|
||||
(request as BridgeIDBRequest)._debugName = "req-main";
|
||||
(request as BridgeIDBRequest)._debugName = "req-main";
|
||||
request.onerror = () => t.fail("request should succeed");
|
||||
request.onsuccess = () => {
|
||||
|
||||
t.true(
|
||||
is_transaction_active(t, tx, "store"),
|
||||
"Transaction should be active during success handler",
|
||||
@ -55,3 +54,147 @@ test("WPT test abort-in-initial-upgradeneeded.htm", async (t) => {
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("WPT test abort-in-initial-upgradeneeded.htm (subtest 2)", async (t) => {
|
||||
// Transactions are active during success listeners
|
||||
await indexeddb_test(
|
||||
t,
|
||||
(done, db, tx) => {
|
||||
db.createObjectStore("store");
|
||||
},
|
||||
(done, db) => {
|
||||
const tx = db.transaction("store");
|
||||
const release_tx = keep_alive(t, tx, "store");
|
||||
t.true(
|
||||
is_transaction_active(t, tx, "store"),
|
||||
"Transaction should be active after creation",
|
||||
);
|
||||
|
||||
const request = tx.objectStore("store").get(0);
|
||||
request.onerror = () => t.fail("request should succeed");
|
||||
request.addEventListener("success", () => {
|
||||
t.true(
|
||||
is_transaction_active(t, tx, "store"),
|
||||
"Transaction should be active during success listener",
|
||||
);
|
||||
|
||||
let saw_listener_promise = false;
|
||||
Promise.resolve().then(() => {
|
||||
saw_listener_promise = true;
|
||||
t.true(
|
||||
is_transaction_active(t, tx, "store"),
|
||||
"Transaction should be active in listener's microtasks",
|
||||
);
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
t.true(saw_listener_promise);
|
||||
t.false(
|
||||
is_transaction_active(t, tx, "store"),
|
||||
"Transaction should be inactive in next task",
|
||||
);
|
||||
release_tx();
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("WPT test abort-in-initial-upgradeneeded.htm (subtest 3)", async (t) => {
|
||||
// Transactions are active during error handlers
|
||||
await indexeddb_test(
|
||||
t,
|
||||
(done, db, tx) => {
|
||||
db.createObjectStore("store");
|
||||
},
|
||||
(done, db) => {
|
||||
const tx = db.transaction("store", "readwrite");
|
||||
const release_tx = keep_alive(t, tx, "store");
|
||||
t.true(
|
||||
is_transaction_active(t, tx, "store"),
|
||||
"Transaction should be active after creation",
|
||||
);
|
||||
|
||||
tx.objectStore("store").put(0, 0);
|
||||
const request = tx.objectStore("store").add(0, 0);
|
||||
request.onsuccess = () => t.fail("request should fail");
|
||||
request.onerror = (e: any) => {
|
||||
e.preventDefault();
|
||||
|
||||
t.true(
|
||||
is_transaction_active(t, tx, "store"),
|
||||
"Transaction should be active during error handler",
|
||||
);
|
||||
|
||||
let saw_handler_promise = false;
|
||||
Promise.resolve().then(() => {
|
||||
saw_handler_promise = true;
|
||||
t.true(
|
||||
is_transaction_active(t, tx, "store"),
|
||||
"Transaction should be active in handler's microtasks",
|
||||
);
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
t.true(saw_handler_promise);
|
||||
t.false(
|
||||
is_transaction_active(t, tx, "store"),
|
||||
"Transaction should be inactive in next task",
|
||||
);
|
||||
release_tx();
|
||||
done();
|
||||
}, 0);
|
||||
};
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("WPT test abort-in-initial-upgradeneeded.htm (subtest 4)", async (t) => {
|
||||
// Transactions are active during error listeners
|
||||
await indexeddb_test(
|
||||
t,
|
||||
(done, db, tx) => {
|
||||
db.createObjectStore("store");
|
||||
},
|
||||
(done, db) => {
|
||||
const tx = db.transaction("store", "readwrite");
|
||||
const release_tx = keep_alive(t, tx, "store");
|
||||
t.true(
|
||||
is_transaction_active(t, tx, "store"),
|
||||
"Transaction should be active after creation",
|
||||
);
|
||||
|
||||
tx.objectStore("store").put(0, 0);
|
||||
const request = tx.objectStore("store").add(0, 0);
|
||||
request.onsuccess = () => t.fail("request should fail");
|
||||
request.addEventListener("error", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
t.true(
|
||||
is_transaction_active(t, tx, "store"),
|
||||
"Transaction should be active during error listener",
|
||||
);
|
||||
|
||||
let saw_listener_promise = false;
|
||||
Promise.resolve().then(() => {
|
||||
saw_listener_promise = true;
|
||||
t.true(
|
||||
is_transaction_active(t, tx, "store"),
|
||||
"Transaction should be active in listener's microtasks",
|
||||
);
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
t.true(saw_listener_promise);
|
||||
t.false(
|
||||
is_transaction_active(t, tx, "store"),
|
||||
"Transaction should be inactive in next task",
|
||||
);
|
||||
release_tx();
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
@ -1,46 +1,385 @@
|
||||
import test from "ava";
|
||||
import { BridgeIDBCursor } from "..";
|
||||
import { BridgeIDBCursorWithValue } from "../bridge-idb";
|
||||
import { createdb } from "./wptsupport";
|
||||
|
||||
test("WPT test idbcursor_continue_index.htm", async (t) => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
var db: any;
|
||||
let count = 0;
|
||||
const records = [ { pKey: "primaryKey_0", iKey: "indexKey_0" },
|
||||
{ pKey: "primaryKey_1", iKey: "indexKey_1" },
|
||||
{ pKey: "primaryKey_1-2", iKey: "indexKey_1" } ];
|
||||
test.cb("WPT test idbcursor_continue_index.htm", (t) => {
|
||||
var db: any;
|
||||
let count = 0;
|
||||
const records = [
|
||||
{ pKey: "primaryKey_0", iKey: "indexKey_0" },
|
||||
{ pKey: "primaryKey_1", iKey: "indexKey_1" },
|
||||
{ pKey: "primaryKey_1-2", iKey: "indexKey_1" },
|
||||
];
|
||||
|
||||
var open_rq = createdb(t);
|
||||
open_rq.onupgradeneeded = function(e: any) {
|
||||
db = e.target.result;
|
||||
var objStore = db.createObjectStore("test", { keyPath:"pKey" });
|
||||
open_rq.onupgradeneeded = function (e: any) {
|
||||
db = e.target.result;
|
||||
var objStore = db.createObjectStore("test", { keyPath: "pKey" });
|
||||
|
||||
objStore.createIndex("index", "iKey");
|
||||
objStore.createIndex("index", "iKey");
|
||||
|
||||
for (var i = 0; i < records.length; i++)
|
||||
objStore.add(records[i]);
|
||||
for (var i = 0; i < records.length; i++) objStore.add(records[i]);
|
||||
};
|
||||
|
||||
open_rq.onsuccess = function(e) {
|
||||
var cursor_rq = db.transaction("test")
|
||||
.objectStore("test")
|
||||
.index("index")
|
||||
.openCursor();
|
||||
open_rq.onsuccess = function (e) {
|
||||
var cursor_rq = db
|
||||
.transaction("test")
|
||||
.objectStore("test")
|
||||
.index("index")
|
||||
.openCursor();
|
||||
|
||||
cursor_rq.onsuccess = function(e: any) {
|
||||
var cursor = e.target.result;
|
||||
if (!cursor) {
|
||||
t.deepEqual(count, records.length, "cursor run count");
|
||||
resolve();
|
||||
}
|
||||
cursor_rq.onsuccess = function (e: any) {
|
||||
var cursor = e.target.result;
|
||||
if (!cursor) {
|
||||
t.deepEqual(count, records.length, "cursor run count");
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
var record = cursor.value;
|
||||
t.deepEqual(record.pKey, records[count].pKey, "primary key");
|
||||
t.deepEqual(record.iKey, records[count].iKey, "index key");
|
||||
var record = cursor.value;
|
||||
t.deepEqual(record.pKey, records[count].pKey, "primary key");
|
||||
t.deepEqual(record.iKey, records[count].iKey, "index key");
|
||||
|
||||
cursor.continue();
|
||||
count++;
|
||||
};
|
||||
cursor.continue();
|
||||
count++;
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
// IDBCursor.continue() - index - attempt to pass a key parameter that is not a valid key
|
||||
test.cb("WPT idbcursor-continue-index2.htm", (t) => {
|
||||
var db: any;
|
||||
let records = [
|
||||
{ pKey: "primaryKey_0", iKey: "indexKey_0" },
|
||||
{ pKey: "primaryKey_1", iKey: "indexKey_1" },
|
||||
];
|
||||
|
||||
var open_rq = createdb(t);
|
||||
open_rq.onupgradeneeded = function (e: any) {
|
||||
db = e.target.result;
|
||||
var objStore = db.createObjectStore("test", { keyPath: "pKey" });
|
||||
|
||||
objStore.createIndex("index", "iKey");
|
||||
|
||||
for (var i = 0; i < records.length; i++) objStore.add(records[i]);
|
||||
};
|
||||
|
||||
open_rq.onsuccess = function (e) {
|
||||
var cursor_rq = db
|
||||
.transaction("test")
|
||||
.objectStore("test")
|
||||
.index("index")
|
||||
.openCursor();
|
||||
|
||||
cursor_rq.onsuccess = function (e: any) {
|
||||
var cursor = e.target.result;
|
||||
|
||||
t.throws(
|
||||
() => {
|
||||
cursor.continue({ foo: "bar" });
|
||||
},
|
||||
{ name: "DataError" },
|
||||
);
|
||||
|
||||
t.true(cursor instanceof BridgeIDBCursorWithValue, "cursor");
|
||||
|
||||
t.end();
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
// IDBCursor.continue() - index - attempt to iterate to the previous
|
||||
// record when the direction is set for the next record
|
||||
test.cb("WPT idbcursor-continue-index3.htm", (t) => {
|
||||
var db: any;
|
||||
const records = [
|
||||
{ pKey: "primaryKey_0", iKey: "indexKey_0" },
|
||||
{ pKey: "primaryKey_1", iKey: "indexKey_1" },
|
||||
];
|
||||
|
||||
var open_rq = createdb(t);
|
||||
open_rq.onupgradeneeded = function (e: any) {
|
||||
db = e.target.result;
|
||||
var objStore = db.createObjectStore("test", { keyPath: "pKey" });
|
||||
|
||||
objStore.createIndex("index", "iKey");
|
||||
|
||||
for (var i = 0; i < records.length; i++) objStore.add(records[i]);
|
||||
};
|
||||
|
||||
open_rq.onsuccess = function (e) {
|
||||
var count = 0;
|
||||
var cursor_rq = db
|
||||
.transaction("test")
|
||||
.objectStore("test")
|
||||
.index("index")
|
||||
.openCursor(undefined, "next"); // XXX: Fx has issue with "undefined"
|
||||
|
||||
cursor_rq.onsuccess = function (e: any) {
|
||||
var cursor = e.target.result;
|
||||
if (!cursor) {
|
||||
t.deepEqual(count, 2, "ran number of times");
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
|
||||
// First time checks key equal, second time checks key less than
|
||||
t.throws(
|
||||
() => {
|
||||
cursor.continue(records[0].iKey);
|
||||
},
|
||||
{ name: "DataError" },
|
||||
);
|
||||
|
||||
cursor.continue();
|
||||
|
||||
count++;
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
// IDBCursor.continue() - index - attempt to iterate to the next
|
||||
// record when the direction is set for the previous record
|
||||
test.cb("WPT idbcursor-continue-index4.htm", (t) => {
|
||||
var db: any;
|
||||
const records = [
|
||||
{ pKey: "primaryKey_0", iKey: "indexKey_0" },
|
||||
{ pKey: "primaryKey_1", iKey: "indexKey_1" },
|
||||
{ pKey: "primaryKey_2", iKey: "indexKey_2" },
|
||||
];
|
||||
|
||||
var open_rq = createdb(t);
|
||||
open_rq.onupgradeneeded = function (e: any) {
|
||||
db = e.target.result;
|
||||
var objStore = db.createObjectStore("test", { keyPath: "pKey" });
|
||||
|
||||
objStore.createIndex("index", "iKey");
|
||||
|
||||
for (var i = 0; i < records.length; i++) objStore.add(records[i]);
|
||||
};
|
||||
|
||||
open_rq.onsuccess = function (e) {
|
||||
var count = 0,
|
||||
cursor_rq = db
|
||||
.transaction("test")
|
||||
.objectStore("test")
|
||||
.index("index")
|
||||
.openCursor(undefined, "prev"); // XXX Fx issues w undefined
|
||||
|
||||
cursor_rq.onsuccess = function (e: any) {
|
||||
var cursor = e.target.result,
|
||||
record = cursor.value;
|
||||
|
||||
switch (count) {
|
||||
case 0:
|
||||
t.deepEqual(record.pKey, records[2].pKey, "first pKey");
|
||||
t.deepEqual(record.iKey, records[2].iKey, "first iKey");
|
||||
cursor.continue();
|
||||
break;
|
||||
|
||||
case 1:
|
||||
t.deepEqual(record.pKey, records[1].pKey, "second pKey");
|
||||
t.deepEqual(record.iKey, records[1].iKey, "second iKey");
|
||||
t.throws(
|
||||
() => {
|
||||
cursor.continue("indexKey_2");
|
||||
},
|
||||
{ name: "DataError" },
|
||||
);
|
||||
t.end();
|
||||
break;
|
||||
|
||||
default:
|
||||
t.fail("Unexpected count value: " + count);
|
||||
}
|
||||
|
||||
count++;
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
// IDBCursor.continue() - index - iterate using 'prevunique'
|
||||
test.cb("WPT idbcursor-continue-index5.htm", (t) => {
|
||||
var db: any;
|
||||
const records = [
|
||||
{ pKey: "primaryKey_0", iKey: "indexKey_0" },
|
||||
{ pKey: "primaryKey_1", iKey: "indexKey_1" },
|
||||
{ pKey: "primaryKey_1-2", iKey: "indexKey_1" },
|
||||
{ pKey: "primaryKey_2", iKey: "indexKey_2" },
|
||||
];
|
||||
const expected = [
|
||||
{ pKey: "primaryKey_2", iKey: "indexKey_2" },
|
||||
{ pKey: "primaryKey_1", iKey: "indexKey_1" },
|
||||
{ pKey: "primaryKey_0", iKey: "indexKey_0" },
|
||||
];
|
||||
|
||||
var open_rq = createdb(t);
|
||||
open_rq.onupgradeneeded = function (e: any) {
|
||||
db = e.target.result;
|
||||
var objStore = db.createObjectStore("test", { keyPath: "pKey" });
|
||||
|
||||
objStore.createIndex("index", "iKey");
|
||||
|
||||
for (var i = 0; i < records.length; i++) objStore.add(records[i]);
|
||||
};
|
||||
|
||||
open_rq.onsuccess = function (e) {
|
||||
var count = 0,
|
||||
cursor_rq = db
|
||||
.transaction("test")
|
||||
.objectStore("test")
|
||||
.index("index")
|
||||
.openCursor(undefined, "prevunique");
|
||||
|
||||
cursor_rq.onsuccess = function (e: any) {
|
||||
if (!e.target.result) {
|
||||
t.deepEqual(count, expected.length, "count");
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
const cursor = e.target.result;
|
||||
const record = cursor.value;
|
||||
t.deepEqual(record.pKey, expected[count].pKey, "pKey #" + count);
|
||||
t.deepEqual(record.iKey, expected[count].iKey, "iKey #" + count);
|
||||
|
||||
t.deepEqual(cursor.key, expected[count].iKey, "cursor.key #" + count);
|
||||
t.deepEqual(
|
||||
cursor.primaryKey,
|
||||
expected[count].pKey,
|
||||
"cursor.primaryKey #" + count,
|
||||
);
|
||||
|
||||
count++;
|
||||
cursor.continue(expected[count] ? expected[count].iKey : undefined);
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
// IDBCursor.continue() - index - iterate using nextunique
|
||||
test.cb("WPT idbcursor-continue-index6.htm", (t) => {
|
||||
var db: any;
|
||||
const records = [
|
||||
{ pKey: "primaryKey_0", iKey: "indexKey_0" },
|
||||
{ pKey: "primaryKey_1", iKey: "indexKey_1" },
|
||||
{ pKey: "primaryKey_1-2", iKey: "indexKey_1" },
|
||||
{ pKey: "primaryKey_2", iKey: "indexKey_2" },
|
||||
];
|
||||
const expected = [
|
||||
{ pKey: "primaryKey_0", iKey: "indexKey_0" },
|
||||
{ pKey: "primaryKey_1", iKey: "indexKey_1" },
|
||||
{ pKey: "primaryKey_2", iKey: "indexKey_2" },
|
||||
];
|
||||
|
||||
var open_rq = createdb(t);
|
||||
open_rq.onupgradeneeded = function (e: any) {
|
||||
db = e.target.result;
|
||||
var objStore = db.createObjectStore("test", { keyPath: "pKey" });
|
||||
|
||||
objStore.createIndex("index", "iKey");
|
||||
|
||||
for (var i = 0; i < records.length; i++) objStore.add(records[i]);
|
||||
};
|
||||
|
||||
open_rq.onsuccess = function (e) {
|
||||
var count = 0,
|
||||
cursor_rq = db
|
||||
.transaction("test")
|
||||
.objectStore("test")
|
||||
.index("index")
|
||||
.openCursor(undefined, "nextunique");
|
||||
|
||||
cursor_rq.onsuccess = function (e: any) {
|
||||
if (!e.target.result) {
|
||||
t.deepEqual(count, expected.length, "count");
|
||||
t.end();
|
||||
return;
|
||||
}
|
||||
var cursor = e.target.result,
|
||||
record = cursor.value;
|
||||
|
||||
t.deepEqual(record.pKey, expected[count].pKey, "pKey #" + count);
|
||||
t.deepEqual(record.iKey, expected[count].iKey, "iKey #" + count);
|
||||
|
||||
t.deepEqual(cursor.key, expected[count].iKey, "cursor.key #" + count);
|
||||
t.deepEqual(
|
||||
cursor.primaryKey,
|
||||
expected[count].pKey,
|
||||
"cursor.primaryKey #" + count,
|
||||
);
|
||||
|
||||
count++;
|
||||
cursor.continue(expected[count] ? expected[count].iKey : undefined);
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
// IDBCursor.continue() - index - throw TransactionInactiveError
|
||||
test.cb("WPT idbcursor-continue-index7.htm", (t) => {
|
||||
var db,
|
||||
records = [
|
||||
{ pKey: "primaryKey_0", iKey: "indexKey_0" },
|
||||
{ pKey: "primaryKey_1", iKey: "indexKey_1" },
|
||||
];
|
||||
|
||||
var open_rq = createdb(t);
|
||||
open_rq.onupgradeneeded = function (event: any) {
|
||||
db = event.target.result;
|
||||
var objStore = db.createObjectStore("store", { keyPath: "pKey" });
|
||||
objStore.createIndex("index", "iKey");
|
||||
for (var i = 0; i < records.length; i++) {
|
||||
objStore.add(records[i]);
|
||||
}
|
||||
var rq = objStore.index("index").openCursor();
|
||||
rq.onsuccess = function (event: any) {
|
||||
var cursor = event.target.result;
|
||||
t.true(cursor instanceof BridgeIDBCursor);
|
||||
|
||||
event.target.transaction.abort();
|
||||
t.throws(
|
||||
() => {
|
||||
cursor.continue();
|
||||
},
|
||||
{ name: "TransactionInactiveError" },
|
||||
"Calling continue() should throws an exception TransactionInactiveError when the transaction is not active.",
|
||||
);
|
||||
t.end();
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
// IDBCursor.continue() - index - throw InvalidStateError caused by object store been deleted
|
||||
test.cb("WPT idbcursor-continue-index8.htm", (t) => {
|
||||
var db: any,
|
||||
records = [
|
||||
{ pKey: "primaryKey_0", iKey: "indexKey_0" },
|
||||
{ pKey: "primaryKey_1", iKey: "indexKey_1" },
|
||||
];
|
||||
|
||||
var open_rq = createdb(t);
|
||||
open_rq.onupgradeneeded = function (event: any) {
|
||||
db = event.target.result;
|
||||
var objStore = db.createObjectStore("store", { keyPath: "pKey" });
|
||||
objStore.createIndex("index", "iKey");
|
||||
for (var i = 0; i < records.length; i++) {
|
||||
objStore.add(records[i]);
|
||||
}
|
||||
var rq = objStore.index("index").openCursor();
|
||||
rq.onsuccess = function (event: any) {
|
||||
var cursor = event.target.result;
|
||||
t.true(cursor instanceof BridgeIDBCursor);
|
||||
|
||||
db.deleteObjectStore("store");
|
||||
|
||||
t.throws(
|
||||
() => {
|
||||
cursor.continue();
|
||||
},
|
||||
{ name: "InvalidStateError" },
|
||||
"If the cursor's source or effective object store has been deleted, the implementation MUST throw a DOMException of type InvalidStateError",
|
||||
);
|
||||
|
||||
t.end();
|
||||
};
|
||||
};
|
||||
});
|
||||
});
|
||||
|
@ -12,9 +12,9 @@ import {
|
||||
import { MemoryBackend } from "../MemoryBackend";
|
||||
import { compareKeys } from "../util/cmp";
|
||||
|
||||
BridgeIDBFactory.enableTracing = true;
|
||||
BridgeIDBFactory.enableTracing = false;
|
||||
const backend = new MemoryBackend();
|
||||
backend.enableTracing = true;
|
||||
backend.enableTracing = false;
|
||||
export const idbFactory = new BridgeIDBFactory(backend);
|
||||
|
||||
const self = {
|
||||
|
Loading…
Reference in New Issue
Block a user