### Install and Build fake-indexeddb Source: https://github.com/dumbmatter/fakeindexeddb/blob/master/CONTRIBUTING.md Install project dependencies and build the project using pnpm. Ensure pnpm is installed before running these commands. ```sh pnpm i pnpm build ``` -------------------------------- ### Object Store Setup Function Source: https://github.com/dumbmatter/fakeindexeddb/blob/master/src/test/web-platform-tests/IndexedDB/resources/idb-partitioned-coverage-iframe.html A helper function to set up an object store named 'store' with 10 entries for testing purposes. It's used by subsequent tests that operate on this store. ```JavaScript function store_test(func, name) { indexeddb_test( function(t, db, tx) { var store = db.createObjectStore("store"); for (var i = 0; i < 10; ++i) { store.put("value: " + i, i); } }, function(t, db) { var tx = db.transaction("store", "readonly"); var store = tx.objectStore("store"); func(t, db, tx, store); }, name); } ``` -------------------------------- ### Install fake-indexeddb Source: https://github.com/dumbmatter/fakeindexeddb/blob/master/README.md Install fake-indexeddb as a development dependency using npm. ```sh npm install --save-dev fake-indexeddb ``` -------------------------------- ### IndexedDB Partitioned Storage Test Setup Source: https://github.com/dumbmatter/fakeindexeddb/blob/master/src/test/web-platform-tests/IndexedDB/idb-partitioned-basic.sub.html Sets up message listeners and orchestrates the test flow for partitioned IndexedDB storage. It handles messages from same-site and cross-site iframes to verify database existence and perform cleanup. ```JavaScript const altOrigin = "http://{{hosts\[alt\]\[\]}}:{{ports\[http\]\[0\]}}"; async_test(t => { const iframe = document.getElementById("shared-iframe"); // Step 1 window.addEventListener("message", t.step_func(e => { // Step 3 if (e.data.message === "same-site iframe loaded") { if (location.origin !== altOrigin) { const crossSiteWindow = window.open(`${altOrigin}/IndexedDB/idb-partitioned-basic.sub.html`, "", "noopener=false"); t.add_cleanup(() => crossSiteWindow.close()); } } // Step 5 if (e.data.message === "cross-site iframe loaded") { t.step(() => { assert_false( e.data.doesDatabaseExist, "The cross-site iframe should not see the same-site database", ); }); iframe.contentWindow.postMessage( {message: "delete database"}, iframe.contentWindow.origin, ); }; // Step 7 if (e.data.message === "database deleted") { t.done(); }; }, "Simple test for partitioned IndexedDB"); }); ``` -------------------------------- ### Use fake-indexeddb with auto import Source: https://github.com/dumbmatter/fakeindexeddb/blob/master/README.md Import 'fake-indexeddb/auto' to make IndexedDB variables globally available. This example demonstrates opening a database, creating an object store, adding data, and querying it using indexes and cursors. ```js import "fake-indexeddb/auto"; var request = indexedDB.open("test", 3); request.onupgradeneeded = function () { var db = request.result; var store = db.createObjectStore("books", {keyPath: "isbn"}); store.createIndex("by_title", "title", {unique: true}); 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}); } request.onsuccess = function (event) { var db = event.target.result; var tx = db.transaction("books"); tx.objectStore("books").index("by_title").get("Quarry Memories").addEventListener("success", function (event) { console.log("From index:", event.target.result); }); tx.objectStore("books").openCursor(IDBKeyRange.lowerBound(200000)).onsuccess = function (event) { var cursor = event.target.result; if (cursor) { console.log("From cursor:", cursor.value); cursor.continue(); } }; tx.oncomplete = function () { console.log("All done!"); }; }; ``` -------------------------------- ### Run IndexedDB Origin Isolation Test Source: https://github.com/dumbmatter/fakeindexeddb/blob/master/src/test/web-platform-tests/IndexedDB/resources/idbfactory-origin-isolation-iframe.html This asynchronous function deletes existing databases, opens a new one for testing, and initiates a keep-alive transaction. It posts a message to the parent window once the keep-alive mechanism has started. ```javascript async function run() { const dbs_to_delete = await indexedDB.databases(); for (const db_info of dbs_to_delete) { let request = indexedDB.deleteDatabase(db_info.name); await new Promise((resolve, reject) => { request.onsuccess = resolve; request.onerror = reject; }); } var openRequest = indexedDB.open('db-isolation-test'); openRequest.onupgradeneeded = () => { openRequest.result.createObjectStore('s'); }; openRequest.onsuccess = () => { var tx = openRequest.result.transaction('s', 'readonly'); keep_alive(tx, 's'); window.parent.postMessage("keep_alive_started", "*"); }; } run(); ``` -------------------------------- ### Test Cases for IDBObjectStore Methods Source: https://github.com/dumbmatter/fakeindexeddb/blob/master/src/test/web-platform-tests/IndexedDB/idbobjectstore-cross-realm-methods.html Defines test cases for various IDBObjectStore methods, including arguments and validation logic for results. This setup is used to test cross-realm functionality. ```JavaScript "use strict"; const KEY_EXISTING_LOWER = 1000; const KEY_EXISTING_UPPER = 1001; const KEY_EXISTING_RANGE = IDBKeyRange.bound(KEY_EXISTING_LOWER, KEY_EXISTING_UPPER); const KEY_NEWLY_ADDED = 1002; const VALUE_EXISTING_LOWER = "VALUE_EXISTING_LOWER"; const VALUE_EXISTING_UPPER = "VALUE_EXISTING_UPPER"; const VALUE_NEWLY_ADDED = "VALUE_NEWLY_ADDED"; const testCases = [ { methodName: "put", arguments: [KEY_NEWLY_ADDED, KEY_EXISTING_LOWER], validateResult: (t, e) => { assert_equals(e.target.result, KEY_EXISTING_LOWER); const rq = e.target.source.getAll(); rq.onsuccess = t.step_func_done(e => { assert_array_equals(e.target.result, [KEY_NEWLY_ADDED, VALUE_EXISTING_UPPER]); }); }, }, { methodName: "add", arguments: [VALUE_NEWLY_ADDED, KEY_NEWLY_ADDED], validateResult: (t, e) => { assert_equals(e.target.result, KEY_NEWLY_ADDED); const rq = e.target.source.getAll(); rq.onsuccess = t.step_func_done(e => { assert_array_equals(e.target.result, [VALUE_EXISTING_LOWER, VALUE_EXISTING_UPPER, VALUE_NEWLY_ADDED]); }); }, }, { methodName: "delete", arguments: [KEY_EXISTING_LOWER], validateResult: (t, e) => { assert_equals(e.target.result, undefined); const rq = e.target.source.getAllKeys(); rq.onsuccess = t.step_func_done(e => { assert_array_equals(e.target.result, [KEY_EXISTING_UPPER]); }); }, }, { methodName: "clear", arguments: [], validateResult: (t, e) => { assert_equals(e.target.result, undefined); const rq = e.target.source.count(); rq.onsuccess = t.step_func_done(e => { assert_equals(e.target.result, 0); }); }, }, { methodName: "get", arguments: [KEY_EXISTING_UPPER], validateResult: (t, e) => { assert_equals(e.target.result, VALUE_EXISTING_UPPER); t.done(); }, }, { methodName: "getKey", arguments: [KEY_EXISTING_LOWER], validateResult: (t, e) => { assert_equals(e.target.result, KEY_EXISTING_LOWER); t.done(); }, }, { methodName: "getAll", arguments: [KEY_EXISTING_RANGE], validateResult: (t, e) => { assert_array_equals(e.target.result, [VALUE_EXISTING_LOWER, VALUE_EXISTING_UPPER]); t.done(); }, }, { methodName: "getAllKeys", arguments: [KEY_EXISTING_RANGE], validateResult: (t, e) => { assert_array_equals(e.target.result, [KEY_EXISTING_LOWER, KEY_EXISTING_UPPER]); t.done(); }, }, { methodName: "count", arguments: [], validateResult: (t, e) => { assert_equals(e.target.result, 2); t.done(); }, }, { methodName: "openCursor", arguments: [], validateResult: (t, e) => { const cursor = e.target.result; assert_true(cursor instanceof IDBCursor); assert_equals(cursor.value, VALUE_EXISTING_LOWER); t.done(); }, }, { methodName: "openKeyCursor", arguments: [], validateResult: (t, e) => { const cursor = e.target.result; assert_true(cursor instanceof IDBCursor); assert_equals(cursor.key, KEY_EXISTING_LOWER); t.done(); }, }, ]; ``` -------------------------------- ### Reset indexedDB state with a new IDBFactory Source: https://github.com/dumbmatter/fakeindexeddb/blob/master/README.md To ensure isolated tests, you can reset the state of the mocked indexedDB by creating a new instance of IDBFactory. This provides a completely fresh start for your database. ```javascript import "fake-indexeddb/auto"; import { IDBFactory } from "fake-indexeddb"; // Whenever you want a fresh indexedDB indexedDB = new IDBFactory(); ``` -------------------------------- ### Add structuredClone polyfill for jsdom Source: https://github.com/dumbmatter/fakeindexeddb/blob/master/README.md When using jsdom with fake-indexeddb, you might need to include a structuredClone polyfill. Install core-js and import its polyfill before using fake-indexeddb. ```javascript import "core-js/stable/structured-clone"; import "fake-indexeddb/auto"; ``` -------------------------------- ### Test: Database Names Don't Leak Cross-Origin Source: https://github.com/dumbmatter/fakeindexeddb/blob/master/src/test/web-platform-tests/IndexedDB/database-names-by-origin.html Tests that database creation in one origin does not affect the database list in another origin. It performs setup, database creation, and then checks visibility from both same and cross origins. ```javascript promise_test(async testCase => { const dbName = databaseName(testCase); assert_true( await crossOriginHelper( testCase, mode, sameOrigin, {action: 'delete-database', name: dbName} ), 'Same-origin setup error' ); assert_true( await crossOriginHelper( testCase, mode, otherOrigin, { action: 'delete-database', name: dbName } ), 'Cross-origin setup error' ); const db = await createNamedDatabase(testCase, dbName, database => { database.createObjectStore('store'); }); if (databaseKind === 'closed') await db.close(); const sameOriginDbNames = await crossOriginHelper( testCase, mode, sameOrigin, { action: 'get-database-names' } ); assert_in_array( dbName, sameOriginDbNames, `Database creation should reflect in same-origin ${mode}`); const otherOriginDbNames = await crossOriginHelper( testCase, mode, otherOrigin, { action: 'get-database-names' } ); assert_true( otherOriginDbNames.indexOf(dbName) === -1, `Database creation should not impact cross-origin ${mode} list` ); if (databaseKind !== 'closed') await db.close(); }, `${databaseKind} database names don't leak to cross-origin ${mode}`) ``` -------------------------------- ### IndexedDB Helper Frame Logic Source: https://github.com/dumbmatter/fakeindexeddb/blob/master/src/test/web-platform-tests/IndexedDB/resources/cross-origin-helper-frame.html This script acts as a helper for cross-origin IndexedDB operations. It listens for messages to perform actions like getting database names or deleting databases, then posts responses back and closes the window. ```javascript "use strict"; self.addEventListener('message', async event => { const action = event.data.action; let response = null; switch(action) { case 'get-database-names': { const dbInfos = await self.indexedDB.databases(); response = dbInfos.map(dbInfo => dbInfo.name); break; } case 'delete-database': { const dbName = event.data.name; await new Promise((resolve, reject) => { const request = indexedDB.deleteDatabase(dbName); request.onsuccess = resolve; request.onerror = reject; }); response = true; break; } } event.source.postMessage({ action, response }, event.origin); window.close(); }); // Make up for the fact that the opener of a cross-origin window has no way of // knowing when the window finishes loading. if (window.opener !== null) { window.opener.postMessage({ action: null, response: 'ready' }, '*'); } ``` -------------------------------- ### Run All Web Platform Tests Source: https://github.com/dumbmatter/fakeindexeddb/blob/master/src/test/web-platform-tests/README.md Execute all web platform tests using the pnpm test-w3c command. ```sh pnpm run test-w3c ``` -------------------------------- ### Convert Tests with convert.js Source: https://github.com/dumbmatter/fakeindexeddb/blob/master/src/test/web-platform-tests/README.md Run the convert.js script to process the copied tests and IDL files. ```sh node src/test/web-platform-tests/convert.js ``` -------------------------------- ### Fetch Tests from Window Source: https://github.com/dumbmatter/fakeindexeddb/blob/master/src/test/web-platform-tests/IndexedDB/idb-partitioned-coverage.sub.html Initiates tests by fetching them from the content window of an iframe. This is a common pattern for running tests within a sandboxed environment. ```JavaScript fetch_tests_from_window(document.getElementById("iframe").contentWindow); ``` -------------------------------- ### Main Window Message Listener and Coordination Source: https://github.com/dumbmatter/fakeindexeddb/blob/master/src/test/web-platform-tests/IndexedDB/idb-partitioned-persistence.sub.html Sets up event listeners in the main window to coordinate actions between iframes. It handles messages for iframe loading, database creation confirmation, and database existence checks. ```JavaScript async_test(t => { const iframe1 = document.getElementById("iframe1"); const iframe2 = document.getElementById("iframe2"); let iframes_loaded = 0; // Step 1 window.addEventListener("message", t.step_func(e => { // Step 3 if (e.data.message === "iframe loaded") { iframes_loaded++; if (iframes_loaded === 2) { iframe1.contentWindow.postMessage( {message: "create database"}, "*", ); } } // Step 5 if (e.data.message === "database created") { iframe2.contentWindow.postMessage( {message: "check database"}, "*", ); } // Step 7 if (e.data.message === "database checked") { t.step(() => { assert_true( e.data.doesDatabaseExist, "The same database should exist in both frames", ); }); t.done(); } })); iframe1.src = "http://{{hosts[alt][]}}:{{ports[http][0]}}/IndexedDB/resources/idb-partitioned-persistence-iframe.html"; iframe2.src = "http://{{hosts[alt][]}}:{{ports[http][0]}}/IndexedDB/resources/idb-partitioned-persistence-iframe.html"; }, "Persistence test for partitioned IndexedDB"); ``` -------------------------------- ### Integrate fake-indexeddb with Dexie (auto import) Source: https://github.com/dumbmatter/fakeindexeddb/blob/master/README.md Use 'fake-indexeddb/auto' before importing Dexie to ensure Dexie uses the fake IndexedDB implementation. This is the recommended approach for seamless integration. ```js import "fake-indexeddb/auto"; import Dexie from "dexie"; const db = new Dexie("MyDatabase"); ``` -------------------------------- ### Object Store and Index Creation Source: https://github.com/dumbmatter/fakeindexeddb/blob/master/src/test/web-platform-tests/IndexedDB/resources/idb-partitioned-coverage-iframe.html Defines an upgrade function to create an object store named 'test' and an index named 'index' within it. It also adds initial data to the object store. ```JavaScript function upgrade_func(t, db, tx) { var objStore = db.createObjectStore("test"); objStore.createIndex("index", ""); objStore.add("data", 1); objStore.add("data2", 2); } ``` -------------------------------- ### Run Subset of Web Platform Tests Source: https://github.com/dumbmatter/fakeindexeddb/blob/master/src/test/web-platform-tests/README.md Run a specific subset of web platform tests by providing a test name pattern. ```sh node --test --test-name-pattern="name of test" \ ./src/test/web-platform-tests/run-all.js ``` -------------------------------- ### Open Key Cursor Backward Iteration Source: https://github.com/dumbmatter/fakeindexeddb/blob/master/src/test/web-platform-tests/IndexedDB/resources/idb-partitioned-coverage-iframe.html Tests 'openKeyCursor' with a 'prev' direction for backward iteration. It collects keys and asserts they match the expected descending order. ```JavaScript store_test(function(t, db, tx, store) { var expected = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] var actual = [] var request = store.openKeyCursor(null, "prev"); request.ons ``` -------------------------------- ### Cursor Iteration with Continue (by key) Source: https://github.com/dumbmatter/fakeindexeddb/blob/master/src/test/web-platform-tests/IndexedDB/resources/idb-partitioned-coverage-iframe.html Tests the 'continue' method on an IndexedDB cursor opened on an index. It demonstrates continuing the cursor to a specific key ('data2') and then without arguments. ```JavaScript indexeddb_test( upgrade_func, function(t, db) { var count = 0; var rq = db.transaction("test", "readonly").objectStore("test").index("index").openCursor(); rq.onsuccess = t.step_func(function(e) { if (!e.target.result) { assert_equals(count, 2, 'count'); t.done(); return; } var cursor = e.target.result; switch(count) { case 0: assert_equals(cursor.value, "data") assert_equals(cursor.key, "data") assert_equals(cursor.primaryKey, 1) cursor.continue("data2") assert_equals(cursor.value, "data") assert_equals(cursor.key, "data") assert_equals(cursor.primaryKey, 1) break case 1: assert_equals(cursor.value, "data2") assert_equals(cursor.key, "data2") assert_equals(cursor.primaryKey, 2) cursor.continue() assert_equals(cursor.value, "data2") assert_equals(cursor.key, "data2") assert_equals(cursor.primaryKey, 2) break default: assert_unreached("Unexpected count: " + count) } count++; }); rq.onerror = t.unreached_func("unexpected error") }, document.title + " - continue" ); ``` -------------------------------- ### Test IDBFactory.databases() in Non-Sandboxed Iframe Source: https://github.com/dumbmatter/fakeindexeddb/blob/master/src/test/web-platform-tests/IndexedDB/idbfactory-databases-opaque-origin.html Verifies that IDBFactory.databases() does not reject when called from a non-sandboxed iframe. Asserts that the promise resolves successfully. ```javascript promise_test(async t => { const iframe = await load_iframe(iframe_script); iframe.contentWindow.postMessage({}, '*'); const message = await wait_for_message(self, iframe.contentWindow); assert_equals(message.result, 'no exception', 'IDBFactory.databases() should not reject'); }, 'IDBFactory.databases() in non-sandboxed iframe should not reject'); ``` -------------------------------- ### Create IndexedDB Database Source: https://github.com/dumbmatter/fakeindexeddb/blob/master/src/test/web-platform-tests/IndexedDB/resources/idb-partitioned-persistence-iframe.html Creates an IndexedDB database with version 1. Resolves on success, rejects on blocked or error. The database is immediately closed after creation. ```javascript const dbName = "users"; // Create the database at v1 and detect success via dbRequest.onsuccess = (e) => { e.target.result.close(); resolve(); } }); } ``` -------------------------------- ### Helper functions for loading iframes and waiting for messages Source: https://github.com/dumbmatter/fakeindexeddb/blob/master/src/test/web-platform-tests/IndexedDB/idbfactory-deleteDatabase-opaque-origin.html These functions are used to set up and interact with iframes for testing purposes. `load_iframe` creates and loads an iframe with specified source and sandbox attributes, returning a promise that resolves with the iframe element. `wait_for_message` returns a promise that resolves when a message is received from a specific source. ```JavaScript function load_iframe(src, sandbox) { return new Promise(resolve => { const iframe = document.createElement('iframe'); iframe.onload = () => { resolve(iframe); }; if (sandbox) iframe.sandbox = sandbox; iframe.srcdoc = src; iframe.style.display = 'none'; document.documentElement.appendChild(iframe); }); } function wait_for_message(recipient, source) { return new Promise(resolve => { recipient.onmessage = function listener(e) { if (e.source === source) { resolve(e.data); recipient.removeEventListener('message', listener); } }; }); } ``` -------------------------------- ### JavaScript: IDBFactory.open() in Non-Sandboxed Iframe Source: https://github.com/dumbmatter/fakeindexeddb/blob/master/src/test/web-platform-tests/IndexedDB/idbfactory-open-opaque-origin.html Demonstrates calling IDBFactory.open() from a non-sandboxed iframe. This operation should not throw an exception. ```javascript const test_code = ' const handler = (reply) => {' + ' try {' + ' indexedDB.deleteDatabase("opaque-origin-test");' + ' } catch {}' + ' try {' + ' const r = indexedDB.open("opaque-origin-test");' + ' r.onupgradeneeded = () => { r.transaction.abort(); };' + ' reply({result: "no exception"});' + ' } catch (ex) {' + ' reply({result: ex.name});' + ' };' + ' };'; const iframe_script = '