Chromium Code Reviews| Index: third_party/WebKit/LayoutTests/storage/indexeddb/rename-index.html |
| diff --git a/third_party/WebKit/LayoutTests/storage/indexeddb/rename-index.html b/third_party/WebKit/LayoutTests/storage/indexeddb/rename-index.html |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..e59efaaa7298c1281168b803b5254698457845ea |
| --- /dev/null |
| +++ b/third_party/WebKit/LayoutTests/storage/indexeddb/rename-index.html |
| @@ -0,0 +1,384 @@ |
| +<!DOCTYPE html> |
| +<title>IndexedDB: object store renaming support</title> |
| +<script src='../../resources/testharness.js'></script> |
| +<link rel="help" |
| + href="https://w3c.github.io/IndexedDB/#dom-idbobjectstore-name"> |
| +<link rel="author" href="pwnall@chromium.org" title="Victor Costan"> |
| +<script src='../../resources/testharnessreport.js'></script> |
| +<script> |
| + |
| +// Returns an IndexedDB database name likely to be unique to the test case. |
| +const databaseName = function(testCase) { |
| + return 'db' + self.location.pathname + '-' + testCase.name; |
| +}; |
| + |
| +// Creates an EventWatcher covering all the events that can be issued by |
| +// IndexedDB requests and transactions. |
| +const requestWatcher = function(testCase, request) { |
| + return new EventWatcher(testCase, request, |
| + ['error', 'success', 'upgradeneeded']); |
| +}; |
| + |
| +// Migrates an IndexedDB database whose name is unique for the test case. |
| +// |
| +// setupCallback will be called during a versionchange transaction, and will be |
| +// given the created database and the versionchange transaction. |
| +// |
| +// Returns a promise that resolves to an IndexedDB database. The caller must |
| +// close the database. |
| +const migrateDatabase = function(testCase, newVersion, setupCallback) { |
| + // We cannot use eventWatcher.wait_for('upgradeneeded') here, because |
| + // the versionchange transaction auto-commits before the Promise's then |
| + // callback gets called. |
| + return new Promise((resolve, reject) => { |
| + const request = indexedDB.open(databaseName(testCase), newVersion); |
| + request.onupgradeneeded = event => { |
| + const eventWatcher = requestWatcher(testCase, request); |
| + const database = event.target.result; |
| + const transaction = event.target.transaction; |
| + setupCallback(database, transaction); |
| + resolve(eventWatcher.wait_for('success')); |
| + }; |
| + request.onerror = (event) => reject(event.error); |
| + }).then(event => event.target.result); |
| +}; |
| + |
| +// Creates an IndexedDB database whose name is unique for the test case. |
| +// |
| +// setupCallback will be called during a versionchange transaction, and will be |
| +// given the created database and the versionchange transaction. |
| +// |
| +// Returns a promise that resolves to an IndexedDB database. The caller must |
| +// close the database. |
| +const createDatabase = function(testCase, setupCallback) { |
| + const request = indexedDB.deleteDatabase(databaseName(testCase)); |
| + const eventWatcher = requestWatcher(testCase, request); |
| + |
| + return eventWatcher.wait_for('success').then((event) => |
| + migrateDatabase(testCase, 1, setupCallback)); |
| +}; |
| + |
| +// Creates a 'books' object store whose contents closely resembles the first |
| +// example in the IndexedDB specification. |
| +const createBooksStore = function(testCase, database) { |
| + const store = database.createObjectStore('books', |
| + { keyPath: 'isbn', autoIncrement: true }); |
| + store.createIndex('by_author', 'author'); |
| + 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 }); |
| + return store; |
| +}; |
| + |
| +// Verifies that an object store's index matches the index used to create the |
| +// books store in the test database's version 1. |
| +const checkIndexContents = function(testCase, index) { |
| + const request = index.get('Barney'); |
| + const eventWatcher = requestWatcher(testCase, request); |
| + return eventWatcher.wait_for('success').then(() => { |
| + let result = request.result; |
|
jsbell
2016/08/29 20:13:48
nit: const
pwnall
2016/09/01 23:34:15
Done.
I went through my other lets as well.
|
| + testCase.step(() => { |
|
jsbell
2016/08/29 20:13:48
testCase.step() should not be necessary in promise
pwnall
2016/09/01 23:34:15
Done. Removed.
|
| + assert_equals(result.isbn, 345678); |
| + assert_equals(result.title, 'Bedrock Nights'); |
| + }); |
| + }); |
| +}; |
| + |
| +promise_test(testCase => { |
| + let authorIndex = null, authorIndex2 = null; |
| + let renamedAuthorIndex = null, renamedAuthorIndex2 = null; |
| + return createDatabase(testCase, (database, transaction) => { |
| + const store = createBooksStore(testCase, database); |
| + authorIndex = store.index('by_author'); |
| + }).then(database => { |
| + const transaction = database.transaction('books', 'readonly'); |
| + const store = transaction.objectStore('books'); |
| + testCase.step(() => { |
| + assert_array_equals(store.indexNames, ['by_author', 'by_title']); |
| + }); |
| + authorIndex2 = store.index('by_author'); |
| + // If checkIndexContents fails here, its implementation is incorrect. |
|
jsbell
2016/08/29 20:13:49
Ideally, turn comments like this into assertion me
pwnall
2016/09/01 23:34:15
Done.
|
| + return checkIndexContents(testCase, authorIndex2).then(() => { |
| + database.close(); |
| + }); |
| + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { |
| + const store = transaction.objectStore('books'); |
| + renamedAuthorIndex = store.index('by_author'); |
| + renamedAuthorIndex.name = 'renamed_by_author'; |
| + testCase.step(() => { |
| + assert_equals(renamedAuthorIndex.name, 'renamed_by_author'); |
|
jsbell
2016/08/29 20:13:49
Try to have a message for each assert (we're not c
pwnall
2016/09/01 23:34:15
Done.
|
| + }); |
| + })).then(database => { |
| + const transaction = database.transaction('books', 'readonly'); |
| + const store = transaction.objectStore('books'); |
| + testCase.step(() => { |
| + assert_array_equals(store.indexNames, |
| + ['by_title', 'renamed_by_author']); |
| + }); |
| + renamedAuthorIndex2 = store.index('renamed_by_author'); |
| + return checkIndexContents(testCase, renamedAuthorIndex2).then(() => { |
| + database.close(); |
| + }); |
| + }).then(() => { |
| + testCase.step(() => { |
| + assert_equals(authorIndex.name, 'by_author'); |
|
jsbell
2016/08/29 20:13:49
sample message: "IDBIndex from earlier transaction
pwnall
2016/09/01 23:34:15
Done.
I ended up with a somewhat different messag
|
| + assert_equals(authorIndex2.name, 'by_author'); |
| + assert_equals(renamedAuthorIndex.name, 'renamed_by_author'); |
| + assert_equals(renamedAuthorIndex2.name, 'renamed_by_author'); |
| + }); |
| + }); |
| +}, 'IndexedDB index rename in new transaction'); |
| + |
| +promise_test(testCase => { |
| + let renamedAuthorIndex = null, renamedAuthorIndex2 = null; |
| + return createDatabase(testCase, (database, transaction) => { |
| + const store = createBooksStore(testCase, database); |
| + renamedAuthorIndex = store.index('by_author'); |
| + renamedAuthorIndex.name = 'renamed_by_author'; |
| + testCase.step(() => { |
| + assert_equals(renamedAuthorIndex.name, 'renamed_by_author'); |
| + }); |
| + }).then(database => { |
| + const transaction = database.transaction('books', 'readonly'); |
| + const store = transaction.objectStore('books'); |
| + testCase.step(() => { |
| + assert_array_equals(store.indexNames, |
| + ['by_title', 'renamed_by_author']); |
| + }); |
| + renamedAuthorIndex2 = store.index('renamed_by_author'); |
| + return checkIndexContents(testCase, renamedAuthorIndex2).then(() => { |
| + database.close(); |
| + }); |
| + }).then(() => { |
| + testCase.step(() => { |
| + assert_equals(renamedAuthorIndex.name, 'renamed_by_author'); |
| + assert_equals(renamedAuthorIndex2.name, 'renamed_by_author'); |
| + }); |
| + }); |
| +}, 'IndexedDB index rename in the transaction where it is created'); |
| + |
| +promise_test(testCase => { |
| + const dbName = databaseName(testCase); |
| + let authorIndex = null, authorIndex2 = null, authorIndex3 = null; |
| + return createDatabase(testCase, (database, transaction) => { |
| + const store = createBooksStore(testCase, database); |
| + authorIndex = store.index('by_author'); |
| + }).then(database => { |
| + database.close(); |
| + }).then(() => new Promise((resolve, reject) => { |
| + const request = indexedDB.open(dbName, 2); |
| + request.onupgradeneeded = (event) => { |
|
jsbell
2016/08/29 20:13:48
nit: Don't need ()
pwnall
2016/09/01 23:34:15
Done.
I made a cleaning pass for these.
|
| + const database = event.target.result; |
| + const transaction = event.target.transaction; |
| + const store = transaction.objectStore('books'); |
| + authorIndex2 = store.index('by_author'); |
| + authorIndex2.name = 'renamed_by_author'; |
| + testCase.step(() => { |
| + assert_equals(authorIndex.name, 'by_author'); |
| + assert_equals(authorIndex2.name, 'renamed_by_author'); |
| + }); |
| + request.onerror = (event) => { |
| + event.preventDefault(); |
| + resolve(event); |
| + } |
| + transaction.onabort = () => null; |
|
jsbell
2016/08/29 20:13:48
Needed?
pwnall
2016/09/01 23:34:15
Done. Removed :)
|
| + transaction.onerror = () => null; |
|
jsbell
2016/08/29 20:13:49
Needed?
pwnall
2016/09/01 23:34:15
Done.
|
| + transaction.abort(); |
|
jsbell
2016/08/29 20:13:49
Do we expect the index name to synchronously rever
pwnall
2016/09/01 23:34:15
Done.
Added tests.
|
| + }; |
| + request.onerror = (event) => reject(event.error); |
|
jsbell
2016/08/29 20:13:49
nit: Don't need ()
pwnall
2016/09/01 23:34:15
Done.
|
| + request.onsuccess = () => reject(new Error( |
| + 'indexedDB.open was not supposed to succeed')); |
| + })).then(event => { |
| + testCase.step(() => { |
| + assert_equals(authorIndex2.name, 'by_author', |
| + 'index rename not reverted after transaction abort'); |
|
jsbell
2016/08/29 20:13:49
Message should describe the *expected* behavior, i
pwnall
2016/09/01 23:34:15
Done.
|
| + }); |
| + |
| + const request = indexedDB.open(dbName, 1); |
| + const eventWatcher = requestWatcher(testCase, request); |
|
jsbell
2016/08/29 20:13:49
These two lines could be combined, e.g.:
return r
pwnall
2016/09/01 23:34:15
Done.
I did this change in the two places where it
|
| + return eventWatcher.wait_for('success'); |
| + }).then(event => { |
| + const database = event.target.result; |
| + const transaction = database.transaction('books', 'readonly'); |
| + const store = transaction.objectStore('books'); |
| + authorIndex3 = store.index('by_author'); |
| + return checkIndexContents(testCase, authorIndex3).then(() => { |
| + database.close(); |
| + }); |
| + }).then(() => { |
| + testCase.step(() => { |
| + assert_equals(authorIndex.name, 'by_author'); |
| + assert_equals(authorIndex2.name, 'by_author'); |
| + assert_equals(authorIndex3.name, 'by_author'); |
| + }); |
| + }); |
| +}, 'IndexedDB object store rename in aborted transaction'); |
| + |
| +promise_test(testCase => { |
| + return createDatabase(testCase, (database, transaction) => { |
| + createBooksStore(testCase, database); |
| + }).then(database => { |
| + database.close(); |
| + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { |
| + const store = transaction.objectStore('books'); |
| + const index = store.index('by_author'); |
| + store.deleteIndex('by_author'); |
| + testCase.step(() => { |
| + assert_throws('InvalidStateError', |
| + () => { index.name = 'renamed_by_author'; }); |
| + }); |
| + })).then(database => { |
| + database.close(); |
| + }); |
| +}, 'IndexedDB deleted index rename throws'); |
| + |
| +promise_test(testCase => { |
| + return createDatabase(testCase, (database, transaction) => { |
| + createBooksStore(testCase, database); |
| + }).then(database => { |
| + const transaction = database.transaction('books', 'readonly'); |
| + const store = transaction.objectStore('books'); |
| + const index = store.index('by_author'); |
| + |
| + testCase.step(() => { |
| + assert_throws('InvalidStateError', |
| + () => { index.name = 'renamed_by_author'; }); |
| + }); |
| + database.close(); |
| + }); |
| +}, 'IndexedDB index rename throws in a readonly transaction'); |
| + |
| +promise_test(testCase => { |
| + return createDatabase(testCase, (database, transaction) => { |
| + createBooksStore(testCase, database); |
| + }).then(database => { |
| + const transaction = database.transaction('books', 'readwrite'); |
| + const store = transaction.objectStore('books'); |
| + const index = store.index('by_author'); |
| + |
| + testCase.step(() => { |
| + assert_throws('InvalidStateError', |
| + () => { index.name = 'renamed_books'; }); |
| + }); |
| + database.close(); |
| + }); |
| +}, 'IndexedDB index rename throws in a readwrite transaction'); |
| + |
| +promise_test(testCase => { |
| + let authorIndex = null; |
| + return createDatabase(testCase, (database, transaction) => { |
| + const store = createBooksStore(testCase, database); |
| + authorIndex = store.index('by_author'); |
| + }).then(database => { |
| + testCase.step(() => { |
| + assert_throws('TransactionInactiveError', |
| + () => { authorIndex.name = 'renamed_by_author'; }); |
| + }); |
| + database.close(); |
| + }); |
| +}, 'IndexedDB index rename throws in an inactive transaction'); |
| + |
| +promise_test(testCase => { |
| + return createDatabase(testCase, (database, transaction) => { |
| + createBooksStore(testCase, database); |
| + }).then(database => { |
| + database.close(); |
| + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { |
| + const store = transaction.objectStore('books'); |
| + const index = store.index('by_author'); |
| + index.name = 'by_author'; |
| + })).then(database => { |
| + const transaction = database.transaction('books', 'readonly'); |
| + const store = transaction.objectStore('books'); |
| + testCase.step(() => { |
| + assert_array_equals(store.indexNames, ['by_author', 'by_title']); |
| + }); |
| + const index = store.index('by_author'); |
| + return checkIndexContents(testCase, index).then(() => { |
| + database.close(); |
| + }); |
| + }); |
| +}, 'IndexedDB index rename to the same name succeeds'); |
| + |
| +promise_test(testCase => { |
| + return createDatabase(testCase, (database, transaction) => { |
| + createBooksStore(testCase, database); |
| + }).then(database => { |
| + database.close(); |
| + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { |
| + const store = transaction.objectStore('books'); |
| + const index = store.index('by_author'); |
| + |
| + testCase.step(() => { |
| + assert_throws('ConstraintError', |
| + () => { index.name = 'by_title'; }); |
| + }); |
| + })).then(database => { |
| + const transaction = database.transaction('books', 'readonly'); |
| + const store = transaction.objectStore('books'); |
| + testCase.step(() => { |
| + assert_array_equals(store.indexNames, ['by_author', 'by_title']); |
| + }); |
| + const index = store.index('by_author'); |
| + return checkIndexContents(testCase, index).then(() => { |
| + database.close(); |
| + }); |
| + }); |
| +}, 'IndexedDB index rename to the name of another index throws'); |
|
jsbell
2016/08/29 20:13:49
Can you add a test case for swapping names, i.e.:
pwnall
2016/09/01 23:34:15
Done.
|
| + |
| +promise_test(testCase => { |
| + return createDatabase(testCase, (database, transaction) => { |
| + createBooksStore(testCase, database); |
| + }).then(database => { |
| + database.close(); |
| + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { |
| + const store = transaction.objectStore('books'); |
| + const index = store.index('by_author'); |
| + store.deleteIndex('by_title'); |
| + index.name = 'by_title'; |
| + })).then(database => { |
| + const transaction = database.transaction('books', 'readonly'); |
| + const store = transaction.objectStore('books'); |
| + testCase.step(() => { |
| + assert_array_equals(store.indexNames, ['by_title']); |
| + }); |
| + const index = store.index('by_title'); |
| + return checkIndexContents(testCase, index).then(() => { |
| + database.close(); |
| + }); |
| + }); |
| +}, 'IndexedDB index rename to the name of a deleted index succeeds'); |
| + |
| +promise_test(testCase => { |
| + return createDatabase(testCase, (database, transaction) => { |
| + createBooksStore(testCase, database); |
| + }).then(database => { |
| + database.close(); |
| + }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { |
| + const store = transaction.objectStore('books'); |
| + const index = store.index('by_author'); |
| + |
| + testCase.step(() => { |
| + index.name = 42; |
| + assert_equals(index.name, "42"); |
| + index.name = true; |
| + assert_equals(index.name, "true"); |
| + index.name = () => null; |
| + assert_equals(index.name, "() => null"); |
|
jsbell
2016/08/29 20:13:48
Other fun cases:
index.name = {toString: () => {
pwnall
2016/09/01 23:34:15
Done.
Firefox fails with '\uDC00\uD800'. Should I
jsbell
2016/09/01 23:47:09
In what way does it fail?
pwnall
2016/09/03 06:01:19
Sorry I forgot to answer this question! https://bu
|
| + index.name = undefined; |
| + assert_equals(index.name, "undefined"); |
| + }); |
| + })).then(database => { |
| + const transaction = database.transaction('books', 'readonly'); |
| + const store = transaction.objectStore('books'); |
| + testCase.step(() => { |
| + assert_array_equals(store.indexNames, ['by_title', 'undefined']); |
| + }); |
| + const index = store.index('undefined'); |
| + return checkIndexContents(testCase, index).then(() => { |
| + database.close(); |
| + }); |
| + }); |
| +}, 'IndexedDB object store rename stringifies non-string names'); |
| +</script> |