| // META: script=resources/support-promises.js | |
| 'use strict'; | |
| promise_test(async testCase => { | |
| const db = await createDatabase(testCase, db => { | |
| createBooksStore(testCase, db); | |
| }); | |
| const txn = db.transaction(['books'], 'readwrite'); | |
| const objectStore = txn.objectStore('books'); | |
| objectStore.put({isbn: 'one', title: 'title1'}); | |
| objectStore.put({isbn: 'two', title: 'title2'}); | |
| objectStore.put({isbn: 'three', title: 'title3'}); | |
| txn.commit(); | |
| await promiseForTransaction(testCase, txn); | |
| const txn2 = db.transaction(['books'], 'readonly'); | |
| const objectStore2 = txn2.objectStore('books'); | |
| const getRequestitle1 = objectStore2.get('one'); | |
| const getRequestitle2 = objectStore2.get('two'); | |
| const getRequestitle3 = objectStore2.get('three'); | |
| txn2.commit(); | |
| await promiseForTransaction(testCase, txn2); | |
| assert_array_equals( | |
| [getRequestitle1.result.title, | |
| getRequestitle2.result.title, | |
| getRequestitle3.result.title], | |
| ['title1', 'title2', 'title3'], | |
| 'All three retrieved titles should match those that were put.'); | |
| db.close(); | |
| }, 'Explicitly committed data can be read back out.'); | |
| promise_test(async testCase => { | |
| let db = await createDatabase(testCase, () => {}); | |
| assert_equals(1, db.version, 'A database should be created as version 1'); | |
| db.close(); | |
| // Upgrade the versionDB database and explicitly commit its versionchange | |
| // transaction. | |
| db = await migrateDatabase(testCase, 2, (db, txn) => { | |
| txn.commit(); | |
| }); | |
| assert_equals(2, db.version, | |
| 'The database version should have been incremented regardless of ' | |
| + 'whether the versionchange transaction was explicitly or implicitly ' | |
| + 'committed.'); | |
| db.close(); | |
| }, 'commit() on a version change transaction does not cause errors.'); | |
| promise_test(async testCase => { | |
| const db = await createDatabase(testCase, db => { | |
| createBooksStore(testCase, db); | |
| }); | |
| const txn = db.transaction(['books'], 'readwrite'); | |
| const objectStore = txn.objectStore('books'); | |
| txn.commit(); | |
| assert_throws_dom('TransactionInactiveError', | |
| () => { objectStore.put({isbn: 'one', title: 'title1'}); }, | |
| 'After commit is called, the transaction should be inactive.'); | |
| db.close(); | |
| }, 'A committed transaction becomes inactive immediately.'); | |
| promise_test(async testCase => { | |
| const db = await createDatabase(testCase, db => { | |
| createBooksStore(testCase, db); | |
| }); | |
| const txn = db.transaction(['books'], 'readwrite'); | |
| const objectStore = txn.objectStore('books'); | |
| const putRequest = objectStore.put({isbn: 'one', title: 'title1'}); | |
| putRequest.onsuccess = testCase.step_func(() => { | |
| assert_throws_dom('TransactionInactiveError', | |
| () => { objectStore.put({isbn:'two', title:'title2'}); }, | |
| 'The transaction should not be active in the callback of a request after ' | |
| + 'commit() is called.'); | |
| }); | |
| txn.commit(); | |
| await promiseForTransaction(testCase, txn); | |
| db.close(); | |
| }, 'A committed transaction is inactive in future request callbacks.'); | |
| promise_test(async testCase => { | |
| const db = await createDatabase(testCase, db => { | |
| createBooksStore(testCase, db); | |
| }); | |
| const txn = db.transaction(['books'], 'readwrite'); | |
| const objectStore = txn.objectStore('books'); | |
| txn.commit(); | |
| assert_throws_dom('TransactionInactiveError', | |
| () => { objectStore.put({isbn:'one', title:'title1'}); }, | |
| 'After commit is called, the transaction should be inactive.'); | |
| const txn2 = db.transaction(['books'], 'readonly'); | |
| const objectStore2 = txn2.objectStore('books'); | |
| const getRequest = objectStore2.get('one'); | |
| await promiseForTransaction(testCase, txn2); | |
| assert_equals(getRequest.result, undefined); | |
| db.close(); | |
| }, 'Puts issued after commit are not fulfilled.'); | |
| promise_test(async testCase => { | |
| const db = await createDatabase(testCase, db => { | |
| createBooksStore(testCase, db); | |
| }); | |
| const txn = db.transaction(['books'], 'readwrite'); | |
| const objectStore = txn.objectStore('books'); | |
| txn.abort(); | |
| assert_throws_dom('InvalidStateError', | |
| () => { txn.commit(); }, | |
| 'The transaction should have been aborted.'); | |
| db.close(); | |
| }, 'Calling commit on an aborted transaction throws.'); | |
| promise_test(async testCase => { | |
| const db = await createDatabase(testCase, db => { | |
| createBooksStore(testCase, db); | |
| }); | |
| const txn = db.transaction(['books'], 'readwrite'); | |
| const objectStore = txn.objectStore('books'); | |
| txn.commit(); | |
| assert_throws_dom('InvalidStateError', | |
| () => { txn.commit(); }, | |
| 'The transaction should have already committed.'); | |
| db.close(); | |
| }, 'Calling commit on a committed transaction throws.'); | |
| promise_test(async testCase => { | |
| const db = await createDatabase(testCase, db => { | |
| createBooksStore(testCase, db); | |
| }); | |
| const txn = db.transaction(['books'], 'readwrite'); | |
| const objectStore = txn.objectStore('books'); | |
| const putRequest = objectStore.put({isbn:'one', title:'title1'}); | |
| txn.commit(); | |
| assert_throws_dom('InvalidStateError', | |
| () => { txn.abort(); }, | |
| 'The transaction should already have committed.'); | |
| const txn2 = db.transaction(['books'], 'readwrite'); | |
| const objectStore2 = txn2.objectStore('books'); | |
| const getRequest = objectStore2.get('one'); | |
| await promiseForTransaction(testCase, txn2); | |
| assert_equals( | |
| getRequest.result.title, | |
| 'title1', | |
| 'Explicitly committed data should be gettable.'); | |
| db.close(); | |
| }, 'Calling abort on a committed transaction throws and does not prevent ' | |
| + 'persisting the data.'); | |
| promise_test(async testCase => { | |
| const db = await createDatabase(testCase, db => { | |
| createBooksStore(testCase, db); | |
| }); | |
| const txn = db.transaction(['books'], 'readwrite'); | |
| const objectStore = txn.objectStore('books'); | |
| const releaseTxnFunction = keepAlive(testCase, txn, 'books'); | |
| // Break up the scope of execution to force the transaction into an inactive | |
| // state. | |
| await timeoutPromise(0); | |
| assert_throws_dom('InvalidStateError', | |
| () => { txn.commit(); }, | |
| 'The transaction should be inactive so calling commit should throw.'); | |
| releaseTxnFunction(); | |
| db.close(); | |
| }, 'Calling txn.commit() when txn is inactive should throw.'); | |
| promise_test(async testCase => { | |
| const db = await createDatabase(testCase, db => { | |
| createBooksStore(testCase, db); | |
| createNotBooksStore(testCase, db); | |
| }); | |
| // Txn1 should commit before txn2, even though txn2 uses commit(). | |
| const txn1 = db.transaction(['books'], 'readwrite'); | |
| txn1.objectStore('books').put({isbn: 'one', title: 'title1'}); | |
| const releaseTxnFunction = keepAlive(testCase, txn1, 'books'); | |
| const txn2 = db.transaction(['books'], 'readwrite'); | |
| txn2.objectStore('books').put({isbn:'one', title:'title2'}); | |
| txn2.commit(); | |
| // Exercise the IndexedDB transaction ordering by executing one with a | |
| // different scope. A readonly transaction is used here because | |
| // implementations are not required to run non-overlapping readwrite | |
| // transactions in parallel, and some implementations (ex: Firefox) | |
| // will not. | |
| const txn3 = db.transaction(['not_books'], 'readonly'); | |
| txn3.objectStore('not_books').getAllKeys(); | |
| txn3.oncomplete = function() { | |
| releaseTxnFunction(); | |
| } | |
| await Promise.all([promiseForTransaction(testCase, txn1), | |
| promiseForTransaction(testCase, txn2)]); | |
| // Read the data back to verify that txn2 executed last. | |
| const txn4 = db.transaction(['books'], 'readonly'); | |
| const getRequest4 = txn4.objectStore('books').get('one'); | |
| await promiseForTransaction(testCase, txn4); | |
| assert_equals(getRequest4.result.title, 'title2'); | |
| db.close(); | |
| }, 'Transactions with same scope should stay in program order, even if one ' | |
| + 'calls commit.'); | |
| promise_test(async testCase => { | |
| const db = await createDatabase(testCase, db => { | |
| createBooksStore(testCase, db); | |
| }); | |
| // Txn1 creates the book 'one' so the 'add()' below fails. | |
| const txn1 = db.transaction(['books'], 'readwrite'); | |
| txn1.objectStore('books').add({isbn:'one', title:'title1'}); | |
| txn1.commit(); | |
| await promiseForTransaction(testCase, txn1); | |
| // Txn2 should abort, because the 'add' call is invalid, and commit() was | |
| // called. | |
| const txn2 = db.transaction(['books'], 'readwrite'); | |
| const objectStore2 = txn2.objectStore('books'); | |
| objectStore2.put({isbn:'two', title:'title2'}); | |
| const addRequest = objectStore2.add({isbn:'one', title:'title2'}); | |
| txn2.commit(); | |
| txn2.oncomplete = () => { assert_unreached( | |
| 'Transaction with invalid "add" call should not be completed.'); }; | |
| // Wait for the transaction to complete. We have to explicitly wait for the | |
| // error signal on the transaction because of the nature of the test tooling. | |
| await Promise.all([ | |
| requestWatcher(testCase, addRequest).wait_for('error'), | |
| transactionWatcher(testCase, txn2).wait_for(['error', 'abort']) | |
| ]); | |
| // Read the data back to verify that txn2 was aborted. | |
| const txn3 = db.transaction(['books'], 'readonly'); | |
| const objectStore3 = txn3.objectStore('books'); | |
| const getRequest1 = objectStore3.get('one'); | |
| const getRequest2 = objectStore3.count('two'); | |
| await promiseForTransaction(testCase, txn3); | |
| assert_equals(getRequest1.result.title, 'title1'); | |
| assert_equals(getRequest2.result, 0); | |
| db.close(); | |
| }, 'Transactions that explicitly commit and have errors should abort.'); | |
| promise_test(async testCase => { | |
| const db = await createDatabase(testCase, db => { | |
| createBooksStore(testCase, db); | |
| }); | |
| const txn1 = db.transaction(['books'], 'readwrite'); | |
| txn1.objectStore('books').add({isbn: 'one', title: 'title1'}); | |
| txn1.commit(); | |
| await promiseForTransaction(testCase, txn1); | |
| // The second add request will throw an error, but the onerror handler will | |
| // appropriately catch the error allowing the valid put request on the | |
| // transaction to commit. | |
| const txn2 = db.transaction(['books'], 'readwrite'); | |
| const objectStore2 = txn2.objectStore('books'); | |
| objectStore2.put({isbn: 'two', title:'title2'}); | |
| const addRequest = objectStore2.add({isbn: 'one', title:'unreached_title'}); | |
| addRequest.onerror = (event) => { | |
| event.preventDefault(); | |
| addRequest.transaction.commit(); | |
| }; | |
| // Wait for the transaction to complete. We have to explicitly wait for the | |
| // error signal on the transaction because of the nature of the test tooling. | |
| await transactionWatcher(testCase,txn2).wait_for(['error', 'complete']) | |
| // Read the data back to verify that txn2 was committed. | |
| const txn3 = db.transaction(['books'], 'readonly'); | |
| const objectStore3 = txn3.objectStore('books'); | |
| const getRequest1 = objectStore3.get('one'); | |
| const getRequest2 = objectStore3.get('two'); | |
| await promiseForTransaction(testCase, txn3); | |
| assert_equals(getRequest1.result.title, 'title1'); | |
| assert_equals(getRequest2.result.title, 'title2'); | |
| db.close(); | |
| }, 'Transactions that handle all errors properly should behave as ' + | |
| 'expected when an explicit commit is called in an onerror handler.'); |