OLD | NEW |
(Empty) | |
| 1 <!DOCTYPE html> |
| 2 <title>IndexedDB: object store renaming support</title> |
| 3 <script src='../../resources/testharness.js'></script> |
| 4 <link rel="help" |
| 5 href="https://w3c.github.io/IndexedDB/#dom-idbobjectstore-name"> |
| 6 <link rel="author" href="pwnall@chromium.org" title="Victor Costan"> |
| 7 <script src='../../resources/testharnessreport.js'></script> |
| 8 <script> |
| 9 |
| 10 // Returns an IndexedDB database name likely to be unique to the test case. |
| 11 const databaseName = function(testCase) { |
| 12 return 'db' + self.location.pathname + '-' + testCase.name; |
| 13 }; |
| 14 |
| 15 // Creates an EventWatcher covering all the events that can be issued by |
| 16 // IndexedDB requests and transactions. |
| 17 const requestWatcher = function(testCase, request) { |
| 18 return new EventWatcher(testCase, request, |
| 19 ['error', 'success', 'upgradeneeded']); |
| 20 }; |
| 21 |
| 22 // Migrates an IndexedDB database whose name is unique for the test case. |
| 23 // |
| 24 // setupCallback will be called during a versionchange transaction, and will be |
| 25 // given the created database and the versionchange transaction. |
| 26 // |
| 27 // Returns a promise that resolves to an IndexedDB database. The caller must |
| 28 // close the database. |
| 29 const migrateDatabase = function(testCase, newVersion, setupCallback) { |
| 30 // We cannot use eventWatcher.wait_for('upgradeneeded') here, because |
| 31 // the versionchange transaction auto-commits before the Promise's then |
| 32 // callback gets called. |
| 33 return new Promise((resolve, reject) => { |
| 34 const request = indexedDB.open(databaseName(testCase), newVersion); |
| 35 request.onupgradeneeded = event => { |
| 36 const eventWatcher = requestWatcher(testCase, request); |
| 37 const database = event.target.result; |
| 38 const transaction = event.target.transaction; |
| 39 setupCallback(database, transaction); |
| 40 resolve(eventWatcher.wait_for('success')); |
| 41 }; |
| 42 request.onerror = (event) => reject(event.error); |
| 43 }).then(event => event.target.result); |
| 44 }; |
| 45 |
| 46 // Creates an IndexedDB database whose name is unique for the test case. |
| 47 // |
| 48 // setupCallback will be called during a versionchange transaction, and will be |
| 49 // given the created database and the versionchange transaction. |
| 50 // |
| 51 // Returns a promise that resolves to an IndexedDB database. The caller must |
| 52 // close the database. |
| 53 const createDatabase = function(testCase, setupCallback) { |
| 54 const request = indexedDB.deleteDatabase(databaseName(testCase)); |
| 55 const eventWatcher = requestWatcher(testCase, request); |
| 56 |
| 57 return eventWatcher.wait_for('success').then(event => |
| 58 migrateDatabase(testCase, 1, setupCallback)); |
| 59 }; |
| 60 |
| 61 // Creates a 'books' object store whose contents closely resembles the first |
| 62 // example in the IndexedDB specification. |
| 63 const createBooksStore = function(testCase, database) { |
| 64 const store = database.createObjectStore('books', |
| 65 { keyPath: 'isbn', autoIncrement: true }); |
| 66 store.createIndex('by_author', 'author'); |
| 67 store.createIndex('by_title', 'title', { unique: true }); |
| 68 store.put({ title: 'Quarry Memories', author: 'Fred', isbn: 123456 }); |
| 69 store.put({ title: 'Water Buffaloes', author: 'Fred', isbn: 234567 }); |
| 70 store.put({ title: 'Bedrock Nights', author: 'Barney', isbn: 345678 }); |
| 71 return store; |
| 72 }; |
| 73 |
| 74 // Creates a 'not_books' object store used to test renaming into existing or |
| 75 // deleted store names. |
| 76 const createNotBooksStore = function(testCase, database) { |
| 77 const store = database.createObjectStore('not_books'); |
| 78 return store; |
| 79 }; |
| 80 |
| 81 // Renames the 'books' store to 'renamed_books'. |
| 82 // |
| 83 // Returns a promise that resolves to an IndexedDB database. The caller must |
| 84 // close the database. |
| 85 const renameBooksStore = function(testCase) { |
| 86 return migrateDatabase(testCase, 2, (database, transaction) => { |
| 87 const store = transaction.objectStore('books'); |
| 88 store.name = 'renamed_books'; |
| 89 }); |
| 90 }; |
| 91 |
| 92 // Verifies that an object store's contents matches the contents used to create |
| 93 // the books store in the test database's version 1. |
| 94 const checkStoreContents = function(testCase, store) { |
| 95 const request = store.get(123456); |
| 96 const eventWatcher = requestWatcher(testCase, request); |
| 97 return eventWatcher.wait_for('success').then(() => { |
| 98 let result = request.result; |
| 99 testCase.step(() => { |
| 100 assert_equals(result.isbn, 123456); |
| 101 assert_equals(result.author, 'Fred'); |
| 102 assert_equals(result.title, 'Quarry Memories'); |
| 103 }); |
| 104 }); |
| 105 }; |
| 106 |
| 107 // Verifies that an object store's index matches the index used to create the |
| 108 // books store in the test database's version 1. |
| 109 const checkStoreIndex = function(testCase, store) { |
| 110 testCase.step(() => { |
| 111 assert_array_equals(store.indexNames, ['by_author', 'by_title']); |
| 112 }); |
| 113 const index = store.index('by_author'); |
| 114 const request = index.get('Barney'); |
| 115 const eventWatcher = requestWatcher(testCase, request); |
| 116 return eventWatcher.wait_for('success').then(() => { |
| 117 let result = request.result; |
| 118 testCase.step(() => { |
| 119 assert_equals(result.isbn, 345678); |
| 120 assert_equals(result.title, 'Bedrock Nights'); |
| 121 }); |
| 122 }); |
| 123 }; |
| 124 |
| 125 // Verifies that an object store's key generator is in the same state as the |
| 126 // key generator created for the books store in the test database's version 1. |
| 127 const checkStoreGenerator = function(testCase, store, expectedKey) { |
| 128 const request = store.put( |
| 129 { title: 'Bedrock Nights ' + expectedKey, author: 'Barney' }); |
| 130 const eventWatcher = requestWatcher(testCase, request); |
| 131 return eventWatcher.wait_for('success').then(() => { |
| 132 let result = request.result; |
| 133 testCase.step(() => { |
| 134 assert_equals(result, expectedKey); |
| 135 }); |
| 136 }); |
| 137 }; |
| 138 |
| 139 promise_test(testCase => { |
| 140 let bookStore = null, bookStore2 = null; |
| 141 let renamedBookStore = null, renamedBookStore2 = null; |
| 142 return createDatabase(testCase, (database, transaction) => { |
| 143 bookStore = createBooksStore(testCase, database); |
| 144 }).then(database => { |
| 145 testCase.step(() => { |
| 146 assert_array_equals(database.objectStoreNames, ['books']); |
| 147 }); |
| 148 const transaction = database.transaction('books', 'readonly'); |
| 149 bookStore2 = transaction.objectStore('books'); |
| 150 // If checkStoreContents fails here, its implementation is incorrect. |
| 151 return checkStoreContents(testCase, bookStore2).then(() => { |
| 152 database.close(); |
| 153 }); |
| 154 }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { |
| 155 renamedBookStore = transaction.objectStore('books'); |
| 156 renamedBookStore.name = 'renamed_books'; |
| 157 testCase.step(() => { |
| 158 assert_equals(renamedBookStore.name, 'renamed_books'); |
| 159 }); |
| 160 })).then(database => { |
| 161 testCase.step(() => { |
| 162 assert_array_equals(database.objectStoreNames, ['renamed_books']); |
| 163 }); |
| 164 const transaction = database.transaction('renamed_books', 'readonly'); |
| 165 renamedBookStore2 = transaction.objectStore('renamed_books'); |
| 166 return checkStoreContents(testCase, renamedBookStore2).then(() => { |
| 167 database.close(); |
| 168 }); |
| 169 }).then(() => { |
| 170 testCase.step(() => { |
| 171 assert_equals(bookStore.name, 'books'); |
| 172 assert_equals(bookStore2.name, 'books'); |
| 173 assert_equals(renamedBookStore.name, 'renamed_books'); |
| 174 assert_equals(renamedBookStore2.name, 'renamed_books'); |
| 175 }); |
| 176 }); |
| 177 }, 'IndexedDB object store rename in new transaction'); |
| 178 |
| 179 promise_test(testCase => { |
| 180 let renamedBookStore = null, renamedBookStore2 = null; |
| 181 return createDatabase(testCase, (database, transaction) => { |
| 182 renamedBookStore = createBooksStore(testCase, database); |
| 183 renamedBookStore.name = 'renamed_books'; |
| 184 testCase.step(() => { |
| 185 assert_equals(renamedBookStore.name, 'renamed_books'); |
| 186 }); |
| 187 }).then(database => { |
| 188 testCase.step(() => { |
| 189 assert_array_equals(database.objectStoreNames, ['renamed_books']); |
| 190 }); |
| 191 const transaction = database.transaction('renamed_books', 'readonly'); |
| 192 renamedBookStore2 = transaction.objectStore('renamed_books'); |
| 193 return checkStoreContents(testCase, renamedBookStore2).then(() => { |
| 194 database.close(); |
| 195 }); |
| 196 }).then(() => { |
| 197 testCase.step(() => { |
| 198 assert_equals(renamedBookStore.name, 'renamed_books'); |
| 199 assert_equals(renamedBookStore2.name, 'renamed_books'); |
| 200 }); |
| 201 }); |
| 202 }, 'IndexedDB object store rename in the transaction where it is created'); |
| 203 |
| 204 promise_test(testCase => { |
| 205 return createDatabase(testCase, (database, transaction) => { |
| 206 createBooksStore(testCase, database); |
| 207 }).then(database => { |
| 208 const transaction = database.transaction('books', 'readonly'); |
| 209 const store = transaction.objectStore('books'); |
| 210 // If checkStoreIndex fails here, its implementation is incorrect. |
| 211 return checkStoreIndex(testCase, store).then(() => { |
| 212 database.close(); |
| 213 }); |
| 214 }).then(() => renameBooksStore(testCase) |
| 215 ).then(database => { |
| 216 const transaction = database.transaction('renamed_books', 'readonly'); |
| 217 const store = transaction.objectStore('renamed_books'); |
| 218 return checkStoreIndex(testCase, store).then(() => { |
| 219 database.close(); |
| 220 }); |
| 221 }); |
| 222 }, 'IndexedDB object store rename covers index'); |
| 223 |
| 224 promise_test(testCase => { |
| 225 return createDatabase(testCase, (database, transaction) => { |
| 226 createBooksStore(testCase, database); |
| 227 }).then(database => { |
| 228 const transaction = database.transaction('books', 'readwrite'); |
| 229 // If checkStoreGenerator fails here, its implementation is incorrect. |
| 230 const store = transaction.objectStore('books'); |
| 231 return checkStoreGenerator(testCase, store, 345679).then(() => { |
| 232 database.close(); |
| 233 }); |
| 234 }).then(() => renameBooksStore(testCase) |
| 235 ).then(database => { |
| 236 const transaction = database.transaction('renamed_books', 'readwrite'); |
| 237 const store = transaction.objectStore('renamed_books'); |
| 238 return checkStoreGenerator(testCase, store, 345680).then(() => { |
| 239 database.close(); |
| 240 }); |
| 241 }); |
| 242 }, 'IndexedDB object store rename covers key generator'); |
| 243 |
| 244 promise_test(testCase => { |
| 245 const dbName = databaseName(testCase); |
| 246 let bookStore = null, bookStore2 = null, bookStore3 = null; |
| 247 return createDatabase(testCase, (database, transaction) => { |
| 248 bookStore = createBooksStore(testCase, database); |
| 249 }).then(database => { |
| 250 database.close(); |
| 251 }).then(() => new Promise((resolve, reject) => { |
| 252 const request = indexedDB.open(dbName, 2); |
| 253 request.onupgradeneeded = (event) => { |
| 254 const database = event.target.result; |
| 255 const transaction = event.target.transaction; |
| 256 bookStore2 = transaction.objectStore('books'); |
| 257 bookStore2.name = 'renamed_books'; |
| 258 testCase.step(() => { |
| 259 assert_equals(bookStore.name, 'books'); |
| 260 assert_equals(bookStore2.name, 'renamed_books'); |
| 261 }); |
| 262 request.onerror = (event) => { |
| 263 event.preventDefault(); |
| 264 resolve(event); |
| 265 } |
| 266 transaction.onabort = () => null; |
| 267 transaction.onerror = () => null; |
| 268 transaction.abort(); |
| 269 }; |
| 270 request.onerror = (event) => reject(event.error); |
| 271 request.onsuccess = () => reject(new Error( |
| 272 'indexedDB.open was not supposed to succeed')); |
| 273 })).then(event => { |
| 274 testCase.step(() => { |
| 275 assert_equals(bookStore.name, 'books', |
| 276 'object store rename not reverted after transaction abort'); |
| 277 }); |
| 278 |
| 279 const request = indexedDB.open(dbName, 1); |
| 280 const eventWatcher = requestWatcher(testCase, request); |
| 281 return eventWatcher.wait_for('success'); |
| 282 }).then(event => { |
| 283 const database = event.target.result; |
| 284 const transaction = database.transaction('books', 'readonly'); |
| 285 bookStore3 = transaction.objectStore('books'); |
| 286 return checkStoreContents(testCase, bookStore3).then(() => { |
| 287 database.close(); |
| 288 }); |
| 289 }).then(() => { |
| 290 testCase.step(() => { |
| 291 assert_equals(bookStore.name, 'books'); |
| 292 assert_equals(bookStore2.name, 'books'); |
| 293 assert_equals(bookStore3.name, 'books'); |
| 294 }); |
| 295 }); |
| 296 }, 'IndexedDB object store rename in aborted transaction'); |
| 297 |
| 298 promise_test(testCase => { |
| 299 return createDatabase(testCase, (database, transaction) => { |
| 300 createBooksStore(testCase, database); |
| 301 }).then(database => { |
| 302 database.close(); |
| 303 }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { |
| 304 const store = transaction.objectStore('books'); |
| 305 database.deleteObjectStore('books'); |
| 306 testCase.step(() => { |
| 307 assert_throws('InvalidStateError', |
| 308 () => { store.name = 'renamed_books'; }); |
| 309 }); |
| 310 })).then(database => { |
| 311 database.close(); |
| 312 }); |
| 313 }, 'IndexedDB deleted object store rename throws'); |
| 314 |
| 315 promise_test(testCase => { |
| 316 return createDatabase(testCase, (database, transaction) => { |
| 317 createBooksStore(testCase, database); |
| 318 }).then(database => { |
| 319 const transaction = database.transaction('books', 'readonly'); |
| 320 const store = transaction.objectStore('books'); |
| 321 |
| 322 testCase.step(() => { |
| 323 assert_throws('InvalidStateError', |
| 324 () => { store.name = 'renamed_books'; }); |
| 325 }); |
| 326 database.close(); |
| 327 }); |
| 328 }, 'IndexedDB object store rename throws in a readonly transaction'); |
| 329 |
| 330 promise_test(testCase => { |
| 331 return createDatabase(testCase, (database, transaction) => { |
| 332 createBooksStore(testCase, database); |
| 333 }).then(database => { |
| 334 const transaction = database.transaction('books', 'readwrite'); |
| 335 const store = transaction.objectStore('books'); |
| 336 |
| 337 testCase.step(() => { |
| 338 assert_throws('InvalidStateError', |
| 339 () => { store.name = 'renamed_books'; }); |
| 340 }); |
| 341 database.close(); |
| 342 }); |
| 343 }, 'IndexedDB object store rename throws in a readwrite transaction'); |
| 344 |
| 345 promise_test(testCase => { |
| 346 let bookStore = null; |
| 347 return createDatabase(testCase, (database, transaction) => { |
| 348 bookStore = createBooksStore(testCase, database); |
| 349 }).then(database => { |
| 350 testCase.step(() => { |
| 351 assert_throws('TransactionInactiveError', |
| 352 () => { bookStore.name = 'renamed_books'; }); |
| 353 }); |
| 354 database.close(); |
| 355 }); |
| 356 }, 'IndexedDB object store rename throws in an inactive transaction'); |
| 357 |
| 358 promise_test(testCase => { |
| 359 return createDatabase(testCase, (database, transaction) => { |
| 360 createBooksStore(testCase, database); |
| 361 }).then(database => { |
| 362 database.close(); |
| 363 }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { |
| 364 const store = transaction.objectStore('books'); |
| 365 store.name = 'books'; |
| 366 })).then(database => { |
| 367 testCase.step(() => { |
| 368 assert_array_equals(database.objectStoreNames, ['books']); |
| 369 }); |
| 370 const transaction = database.transaction('books', 'readonly'); |
| 371 const store = transaction.objectStore('books'); |
| 372 return checkStoreContents(testCase, store).then(() => { |
| 373 database.close(); |
| 374 }); |
| 375 }); |
| 376 }, 'IndexedDB object store rename to the same name succeeds'); |
| 377 |
| 378 promise_test(testCase => { |
| 379 return createDatabase(testCase, (database, transaction) => { |
| 380 createBooksStore(testCase, database); |
| 381 createNotBooksStore(testCase, database); |
| 382 }).then(database => { |
| 383 database.close(); |
| 384 }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { |
| 385 const store = transaction.objectStore('books'); |
| 386 testCase.step(() => { |
| 387 assert_throws('ConstraintError', |
| 388 () => { store.name = 'not_books'; }); |
| 389 }); |
| 390 })).then(database => { |
| 391 testCase.step(() => { |
| 392 assert_array_equals(database.objectStoreNames, |
| 393 ['books', 'not_books']); |
| 394 }); |
| 395 const transaction = database.transaction('books', 'readonly'); |
| 396 const store = transaction.objectStore('books'); |
| 397 return checkStoreContents(testCase, store).then(() => { |
| 398 database.close(); |
| 399 }); |
| 400 }); |
| 401 }, 'IndexedDB object store rename to the name of another store throws'); |
| 402 |
| 403 promise_test(testCase => { |
| 404 return createDatabase(testCase, (database, transaction) => { |
| 405 createBooksStore(testCase, database); |
| 406 createNotBooksStore(testCase, database); |
| 407 }).then(database => { |
| 408 database.close(); |
| 409 }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { |
| 410 const store = transaction.objectStore('books'); |
| 411 database.deleteObjectStore('not_books'); |
| 412 store.name = 'not_books'; |
| 413 })).then(database => { |
| 414 testCase.step(() => { |
| 415 assert_array_equals(database.objectStoreNames, ['not_books']); |
| 416 }); |
| 417 const transaction = database.transaction('not_books', 'readonly'); |
| 418 const store = transaction.objectStore('not_books'); |
| 419 return checkStoreContents(testCase, store).then(() => { |
| 420 database.close(); |
| 421 }); |
| 422 }); |
| 423 }, 'IndexedDB object store rename to the name of a deleted store succeeds'); |
| 424 |
| 425 promise_test(testCase => { |
| 426 return createDatabase(testCase, (database, transaction) => { |
| 427 createBooksStore(testCase, database); |
| 428 }).then(database => { |
| 429 database.close(); |
| 430 }).then(() => migrateDatabase(testCase, 2, (database, transaction) => { |
| 431 const store = transaction.objectStore('books'); |
| 432 testCase.step(() => { |
| 433 store.name = 42; |
| 434 assert_equals(store.name, "42"); |
| 435 store.name = true; |
| 436 assert_equals(store.name, "true"); |
| 437 store.name = () => null; |
| 438 assert_equals(store.name, "() => null"); |
| 439 store.name = undefined; |
| 440 assert_equals(store.name, "undefined"); |
| 441 }); |
| 442 })).then(database => { |
| 443 testCase.step(() => { |
| 444 assert_array_equals(database.objectStoreNames, ['undefined']); |
| 445 }); |
| 446 const transaction = database.transaction('undefined', 'readonly'); |
| 447 const store = transaction.objectStore('undefined'); |
| 448 return checkStoreContents(testCase, store).then(() => { |
| 449 database.close(); |
| 450 }); |
| 451 }); |
| 452 }, 'IndexedDB object store rename stringifies non-string names'); |
| 453 </script> |
OLD | NEW |