Index: third_party/WebKit/LayoutTests/storage/indexeddb/rename-object-store.html |
diff --git a/third_party/WebKit/LayoutTests/storage/indexeddb/rename-object-store.html b/third_party/WebKit/LayoutTests/storage/indexeddb/rename-object-store.html |
new file mode 100644 |
index 0000000000000000000000000000000000000000..558cec6e4bb15bd7d0fe8373e8338d1601def84a |
--- /dev/null |
+++ b/third_party/WebKit/LayoutTests/storage/indexeddb/rename-object-store.html |
@@ -0,0 +1,453 @@ |
+<!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; |
+}; |
+ |
+// Creates a 'not_books' object store used to test renaming into existing or |
+// deleted store names. |
+const createNotBooksStore = function(testCase, database) { |
+ const store = database.createObjectStore('not_books'); |
+ return store; |
+}; |
+ |
+// Renames the 'books' store to 'renamed_books'. |
+// |
+// Returns a promise that resolves to an IndexedDB database. The caller must |
+// close the database. |
+const renameBooksStore = function(testCase) { |
+ return migrateDatabase(testCase, 2, (database, transaction) => { |
+ const store = transaction.objectStore('books'); |
+ store.name = 'renamed_books'; |
+ }); |
+}; |
+ |
+// Verifies that an object store's contents matches the contents used to create |
+// the books store in the test database's version 1. |
+const checkStoreContents = function(testCase, store) { |
+ const request = store.get(123456); |
+ const eventWatcher = requestWatcher(testCase, request); |
+ return eventWatcher.wait_for('success').then(() => { |
+ let result = request.result; |
+ testCase.step(() => { |
+ assert_equals(result.isbn, 123456); |
+ assert_equals(result.author, 'Fred'); |
+ assert_equals(result.title, 'Quarry Memories'); |
+ }); |
+ }); |
+}; |
+ |
+// Verifies that an object store's index matches the index used to create the |
+// books store in the test database's version 1. |
+const checkStoreIndex = function(testCase, store) { |
+ testCase.step(() => { |
+ assert_array_equals(store.indexNames, ['by_author', 'by_title']); |
+ }); |
+ const index = store.index('by_author'); |
+ const request = index.get('Barney'); |
+ const eventWatcher = requestWatcher(testCase, request); |
+ return eventWatcher.wait_for('success').then(() => { |
+ let result = request.result; |
+ testCase.step(() => { |
+ assert_equals(result.isbn, 345678); |
+ assert_equals(result.title, 'Bedrock Nights'); |
+ }); |
+ }); |
+}; |
+ |
+// Verifies that an object store's key generator is in the same state as the |
+// key generator created for the books store in the test database's version 1. |
+const checkStoreGenerator = function(testCase, store, expectedKey) { |
+ const request = store.put( |
+ { title: 'Bedrock Nights ' + expectedKey, author: 'Barney' }); |
+ const eventWatcher = requestWatcher(testCase, request); |
+ return eventWatcher.wait_for('success').then(() => { |
+ let result = request.result; |
+ testCase.step(() => { |
+ assert_equals(result, expectedKey); |
+ }); |
+ }); |
+}; |
+ |
+promise_test(testCase => { |
+ let bookStore = null, bookStore2 = null; |
+ let renamedBookStore = null, renamedBookStore2 = null; |
+ return createDatabase(testCase, (database, transaction) => { |
+ bookStore = createBooksStore(testCase, database); |
+ }).then(database => { |
+ testCase.step(() => { |
+ assert_array_equals(database.objectStoreNames, ['books']); |
+ }); |
+ const transaction = database.transaction('books', 'readonly'); |
+ bookStore2 = transaction.objectStore('books'); |
+ // If checkStoreContents fails here, its implementation is incorrect. |
+ return checkStoreContents(testCase, bookStore2).then(() => { |
+ database.close(); |
+ }); |
+ }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { |
+ renamedBookStore = transaction.objectStore('books'); |
+ renamedBookStore.name = 'renamed_books'; |
+ testCase.step(() => { |
+ assert_equals(renamedBookStore.name, 'renamed_books'); |
+ }); |
+ })).then(database => { |
+ testCase.step(() => { |
+ assert_array_equals(database.objectStoreNames, ['renamed_books']); |
+ }); |
+ const transaction = database.transaction('renamed_books', 'readonly'); |
+ renamedBookStore2 = transaction.objectStore('renamed_books'); |
+ return checkStoreContents(testCase, renamedBookStore2).then(() => { |
+ database.close(); |
+ }); |
+ }).then(() => { |
+ testCase.step(() => { |
+ assert_equals(bookStore.name, 'books'); |
+ assert_equals(bookStore2.name, 'books'); |
+ assert_equals(renamedBookStore.name, 'renamed_books'); |
+ assert_equals(renamedBookStore2.name, 'renamed_books'); |
+ }); |
+ }); |
+}, 'IndexedDB object store rename in new transaction'); |
+ |
+promise_test(testCase => { |
+ let renamedBookStore = null, renamedBookStore2 = null; |
+ return createDatabase(testCase, (database, transaction) => { |
+ renamedBookStore = createBooksStore(testCase, database); |
+ renamedBookStore.name = 'renamed_books'; |
+ testCase.step(() => { |
+ assert_equals(renamedBookStore.name, 'renamed_books'); |
+ }); |
+ }).then(database => { |
+ testCase.step(() => { |
+ assert_array_equals(database.objectStoreNames, ['renamed_books']); |
+ }); |
+ const transaction = database.transaction('renamed_books', 'readonly'); |
+ renamedBookStore2 = transaction.objectStore('renamed_books'); |
+ return checkStoreContents(testCase, renamedBookStore2).then(() => { |
+ database.close(); |
+ }); |
+ }).then(() => { |
+ testCase.step(() => { |
+ assert_equals(renamedBookStore.name, 'renamed_books'); |
+ assert_equals(renamedBookStore2.name, 'renamed_books'); |
+ }); |
+ }); |
+}, 'IndexedDB object store rename in the transaction where it is created'); |
+ |
+promise_test(testCase => { |
+ return createDatabase(testCase, (database, transaction) => { |
+ createBooksStore(testCase, database); |
+ }).then(database => { |
+ const transaction = database.transaction('books', 'readonly'); |
+ const store = transaction.objectStore('books'); |
+ // If checkStoreIndex fails here, its implementation is incorrect. |
+ return checkStoreIndex(testCase, store).then(() => { |
+ database.close(); |
+ }); |
+ }).then(() => renameBooksStore(testCase) |
+ ).then(database => { |
+ const transaction = database.transaction('renamed_books', 'readonly'); |
+ const store = transaction.objectStore('renamed_books'); |
+ return checkStoreIndex(testCase, store).then(() => { |
+ database.close(); |
+ }); |
+ }); |
+}, 'IndexedDB object store rename covers index'); |
+ |
+promise_test(testCase => { |
+ return createDatabase(testCase, (database, transaction) => { |
+ createBooksStore(testCase, database); |
+ }).then(database => { |
+ const transaction = database.transaction('books', 'readwrite'); |
+ // If checkStoreGenerator fails here, its implementation is incorrect. |
+ const store = transaction.objectStore('books'); |
+ return checkStoreGenerator(testCase, store, 345679).then(() => { |
+ database.close(); |
+ }); |
+ }).then(() => renameBooksStore(testCase) |
+ ).then(database => { |
+ const transaction = database.transaction('renamed_books', 'readwrite'); |
+ const store = transaction.objectStore('renamed_books'); |
+ return checkStoreGenerator(testCase, store, 345680).then(() => { |
+ database.close(); |
+ }); |
+ }); |
+}, 'IndexedDB object store rename covers key generator'); |
+ |
+promise_test(testCase => { |
+ const dbName = databaseName(testCase); |
+ let bookStore = null, bookStore2 = null, bookStore3 = null; |
+ return createDatabase(testCase, (database, transaction) => { |
+ bookStore = createBooksStore(testCase, database); |
+ }).then(database => { |
+ database.close(); |
+ }).then(() => new Promise((resolve, reject) => { |
+ const request = indexedDB.open(dbName, 2); |
+ request.onupgradeneeded = (event) => { |
+ const database = event.target.result; |
+ const transaction = event.target.transaction; |
+ bookStore2 = transaction.objectStore('books'); |
+ bookStore2.name = 'renamed_books'; |
+ testCase.step(() => { |
+ assert_equals(bookStore.name, 'books'); |
+ assert_equals(bookStore2.name, 'renamed_books'); |
+ }); |
+ request.onerror = (event) => { |
+ event.preventDefault(); |
+ resolve(event); |
+ } |
+ transaction.onabort = () => null; |
+ transaction.onerror = () => null; |
+ transaction.abort(); |
+ }; |
+ request.onerror = (event) => reject(event.error); |
+ request.onsuccess = () => reject(new Error( |
+ 'indexedDB.open was not supposed to succeed')); |
+ })).then(event => { |
+ testCase.step(() => { |
+ assert_equals(bookStore.name, 'books', |
+ 'object store rename not reverted after transaction abort'); |
+ }); |
+ |
+ const request = indexedDB.open(dbName, 1); |
+ const eventWatcher = requestWatcher(testCase, request); |
+ return eventWatcher.wait_for('success'); |
+ }).then(event => { |
+ const database = event.target.result; |
+ const transaction = database.transaction('books', 'readonly'); |
+ bookStore3 = transaction.objectStore('books'); |
+ return checkStoreContents(testCase, bookStore3).then(() => { |
+ database.close(); |
+ }); |
+ }).then(() => { |
+ testCase.step(() => { |
+ assert_equals(bookStore.name, 'books'); |
+ assert_equals(bookStore2.name, 'books'); |
+ assert_equals(bookStore3.name, 'books'); |
+ }); |
+ }); |
+}, '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'); |
+ database.deleteObjectStore('books'); |
+ testCase.step(() => { |
+ assert_throws('InvalidStateError', |
+ () => { store.name = 'renamed_books'; }); |
+ }); |
+ })).then(database => { |
+ database.close(); |
+ }); |
+}, 'IndexedDB deleted object store 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'); |
+ |
+ testCase.step(() => { |
+ assert_throws('InvalidStateError', |
+ () => { store.name = 'renamed_books'; }); |
+ }); |
+ database.close(); |
+ }); |
+}, 'IndexedDB object store 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'); |
+ |
+ testCase.step(() => { |
+ assert_throws('InvalidStateError', |
+ () => { store.name = 'renamed_books'; }); |
+ }); |
+ database.close(); |
+ }); |
+}, 'IndexedDB object store rename throws in a readwrite transaction'); |
+ |
+promise_test(testCase => { |
+ let bookStore = null; |
+ return createDatabase(testCase, (database, transaction) => { |
+ bookStore = createBooksStore(testCase, database); |
+ }).then(database => { |
+ testCase.step(() => { |
+ assert_throws('TransactionInactiveError', |
+ () => { bookStore.name = 'renamed_books'; }); |
+ }); |
+ database.close(); |
+ }); |
+}, 'IndexedDB object store 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'); |
+ store.name = 'books'; |
+ })).then(database => { |
+ testCase.step(() => { |
+ assert_array_equals(database.objectStoreNames, ['books']); |
+ }); |
+ const transaction = database.transaction('books', 'readonly'); |
+ const store = transaction.objectStore('books'); |
+ return checkStoreContents(testCase, store).then(() => { |
+ database.close(); |
+ }); |
+ }); |
+}, 'IndexedDB object store rename to the same name succeeds'); |
+ |
+promise_test(testCase => { |
+ return createDatabase(testCase, (database, transaction) => { |
+ createBooksStore(testCase, database); |
+ createNotBooksStore(testCase, database); |
+ }).then(database => { |
+ database.close(); |
+ }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { |
+ const store = transaction.objectStore('books'); |
+ testCase.step(() => { |
+ assert_throws('ConstraintError', |
+ () => { store.name = 'not_books'; }); |
+ }); |
+ })).then(database => { |
+ testCase.step(() => { |
+ assert_array_equals(database.objectStoreNames, |
+ ['books', 'not_books']); |
+ }); |
+ const transaction = database.transaction('books', 'readonly'); |
+ const store = transaction.objectStore('books'); |
+ return checkStoreContents(testCase, store).then(() => { |
+ database.close(); |
+ }); |
+ }); |
+}, 'IndexedDB object store rename to the name of another store throws'); |
+ |
+promise_test(testCase => { |
+ return createDatabase(testCase, (database, transaction) => { |
+ createBooksStore(testCase, database); |
+ createNotBooksStore(testCase, database); |
+ }).then(database => { |
+ database.close(); |
+ }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { |
+ const store = transaction.objectStore('books'); |
+ database.deleteObjectStore('not_books'); |
+ store.name = 'not_books'; |
+ })).then(database => { |
+ testCase.step(() => { |
+ assert_array_equals(database.objectStoreNames, ['not_books']); |
+ }); |
+ const transaction = database.transaction('not_books', 'readonly'); |
+ const store = transaction.objectStore('not_books'); |
+ return checkStoreContents(testCase, store).then(() => { |
+ database.close(); |
+ }); |
+ }); |
+}, 'IndexedDB object store rename to the name of a deleted store 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'); |
+ testCase.step(() => { |
+ store.name = 42; |
+ assert_equals(store.name, "42"); |
+ store.name = true; |
+ assert_equals(store.name, "true"); |
+ store.name = () => null; |
+ assert_equals(store.name, "() => null"); |
+ store.name = undefined; |
+ assert_equals(store.name, "undefined"); |
+ }); |
+ })).then(database => { |
+ testCase.step(() => { |
+ assert_array_equals(database.objectStoreNames, ['undefined']); |
+ }); |
+ const transaction = database.transaction('undefined', 'readonly'); |
+ const store = transaction.objectStore('undefined'); |
+ return checkStoreContents(testCase, store).then(() => { |
+ database.close(); |
+ }); |
+ }); |
+}, 'IndexedDB object store rename stringifies non-string names'); |
+</script> |