request-event-ordering-common.js (9860B)
1 'use strict'; 2 3 // Should be large enough to trigger large value handling in the IndexedDB 4 // engines that have special code paths for large values. 5 const wrapThreshold = 128 * 1024; 6 7 function populateStore(store) { 8 store.put({id: 1, key: 'k1', value: largeValue(wrapThreshold, 1)}); 9 store.put({id: 2, key: 'k2', value: ['small-2']}); 10 store.put({id: 3, key: 'k3', value: largeValue(wrapThreshold, 3)}); 11 store.put({id: 4, key: 'k4', value: ['small-4']}); 12 } 13 14 // Assigns cursor indexes for operations that require open cursors. 15 // 16 // Returns the number of open cursors required to perform all operations. 17 function assignCursors(operations) { 18 return cursorCount; 19 } 20 21 // Opens index cursors for operations that require open cursors. 22 // 23 // onsuccess is called if all cursors are opened successfully. Otherwise, 24 // onerror will be called at least once. 25 function openCursors(testCase, index, operations, onerror, onsuccess) { 26 let pendingCursors = 0; 27 28 for (let operation of operations) { 29 const opcode = operation[0]; 30 const primaryKey = operation[1]; 31 let request; 32 switch (opcode) { 33 case 'continue': 34 request = 35 index.openCursor(IDBKeyRange.lowerBound(`k${primaryKey - 1}`)); 36 break; 37 case 'continue-empty': 38 // k4 is the last key in the data set, so calling continue() will get 39 // the cursor past the end of the store. 40 request = index.openCursor(IDBKeyRange.lowerBound('k4')); 41 break; 42 default: 43 continue; 44 } 45 46 operation[2] = request; 47 ++pendingCursors; 48 49 request.onsuccess = testCase.step_func(() => { 50 --pendingCursors; 51 if (!pendingCursors) 52 onsuccess(); 53 }); 54 request.onerror = testCase.step_func(onerror); 55 } 56 57 if (!pendingCursors) 58 onsuccess(); 59 } 60 61 function doOperation(testCase, store, index, operation, requestId, results) { 62 const opcode = operation[0]; 63 const primaryKey = operation[1]; 64 const cursor = operation[2]; 65 66 return new Promise((resolve, reject) => { 67 let request; 68 switch (opcode) { 69 case 'add': // Tests returning a primary key. 70 request = 71 store.add({key: `k${primaryKey}`, value: [`small-${primaryKey}`]}); 72 break; 73 case 'put': // Tests returning a primary key. 74 request = 75 store.put({key: `k${primaryKey}`, value: [`small-${primaryKey}`]}); 76 break; 77 case 'put-with-id': // Tests returning success or a primary key. 78 request = store.put({ 79 key: `k${primaryKey}`, 80 value: [`small-${primaryKey}`], 81 id: primaryKey 82 }); 83 break; 84 case 'get': // Tests returning a value. 85 case 'get-empty': // Tests returning undefined. 86 request = store.get(primaryKey); 87 break; 88 case 'getall': // Tests returning an array of values. 89 request = store.getAll(); 90 break; 91 case 'error': // Tests returning an error. 92 request = 93 store.put({key: `k${primaryKey}`, value: [`small-${primaryKey}`]}); 94 request.onerror = testCase.step_func(event => { 95 event.preventDefault(); 96 results.push([requestId, request.error]); 97 resolve(); 98 }); 99 request.onsuccess = testCase.step_func(() => { 100 reject(new Error('put with duplicate primary key succeded')); 101 }); 102 break; 103 case 'continue': // Tests returning a key, primary key, and value. 104 request = cursor; 105 cursor.result.continue(); 106 request.onsuccess = testCase.step_func(() => { 107 const result = request.result; 108 results.push( 109 [requestId, result.key, result.primaryKey, result.value]); 110 resolve(); 111 }); 112 request.onerror = null; 113 break; 114 case 'open': // Tests returning a cursor, key, primary key, and value. 115 request = index.openCursor(IDBKeyRange.lowerBound(`k${primaryKey}`)); 116 request.onsuccess = testCase.step_func(() => { 117 const result = request.result; 118 results.push( 119 [requestId, result.key, result.primaryKey, result.value]); 120 resolve(); 121 }); 122 break; 123 case 'continue-empty': // Tests returning a null result. 124 request = cursor; 125 cursor.result.continue(); 126 request.onsuccess = testCase.step_func(() => { 127 results.push([requestId, request.result]); 128 resolve(); 129 }); 130 request.onerror = null; 131 break; 132 case 'open-empty': // Tests returning a null cursor. 133 request = index.openCursor(IDBKeyRange.lowerBound(`k${primaryKey}`)); 134 request.onsuccess = testCase.step_func(() => { 135 const result = request.result; 136 results.push([requestId, request.result]); 137 resolve(); 138 }); 139 break; 140 case 'count': // Tests returning a numeric result. 141 request = index.count(); 142 request.onsuccess = testCase.step_func(() => { 143 results.push([requestId, request.result]); 144 resolve(); 145 }); 146 break; 147 }; 148 149 if (!request.onsuccess) { 150 request.onsuccess = testCase.step_func(() => { 151 results.push([requestId, request.result]); 152 resolve(); 153 }); 154 } 155 if (!request.onerror) 156 request.onerror = testCase.step_func(event => { 157 event.preventDefault(); 158 reject(request.error); 159 }); 160 }); 161 } 162 163 function checkOperationResult(operation, result, requestId) { 164 const opcode = operation[0]; 165 const primaryKey = operation[1]; 166 167 const expectedValue = (primaryKey == 1 || primaryKey == 3) ? 168 largeValue(wrapThreshold, primaryKey) : 169 [`small-${primaryKey}`]; 170 171 const requestIndex = result[0]; 172 assert_equals( 173 requestIndex, requestId, 'result event order should match request order'); 174 switch (opcode) { 175 case 'put': 176 case 'put-with-id': 177 case 'add': 178 assert_equals( 179 result[1], primaryKey, 180 `${opcode} result should be the new object's primary key`); 181 break; 182 case 'get': 183 assert_equals( 184 result[1].id, primaryKey, 185 'get result should match put value (primary key)'); 186 assert_equals( 187 result[1].key, `k${primaryKey}`, 188 'get result should match put value (key)'); 189 assert_equals( 190 result[1].value.join(','), expectedValue.join(','), 191 'get result should match put value (nested value)'); 192 break; 193 case 'getall': 194 assert_equals( 195 result[1].length, primaryKey, 196 'getAll should return all the objects in the store'); 197 for (let i = 0; i < primaryKey; ++i) { 198 const object = result[1][i]; 199 assert_equals( 200 object.id, i + 1, 201 `getAll result ${i + 1} should match put value (primary key)`); 202 assert_equals( 203 object.key, `k${i + 1}`, 204 `get result ${i + 1} should match put value (key)`); 205 206 const expectedValue = (i == 0 || i == 2) ? 207 largeValue(wrapThreshold, i + 1) : 208 [`small-${i + 1}`]; 209 assert_equals( 210 object.value.join(','), object.value.join(','), 211 `get result ${i + 1} should match put value (nested value)`); 212 } 213 break; 214 case 'get-empty': 215 assert_equals( 216 result[1], undefined, 'get-empty result should be undefined'); 217 break; 218 case 'error': 219 assert_equals( 220 result[1].name, 'ConstraintError', 221 'incorrect error from put with duplicate primary key'); 222 break; 223 case 'continue': 224 case 'open': 225 assert_equals( 226 result[1], `k${primaryKey}`, 227 `${opcode} key should match the key in the put value`); 228 assert_equals( 229 result[2], primaryKey, 230 `${opcode} primary key should match the put value's primary key`); 231 assert_equals( 232 result[3].id, primaryKey, 233 `${opcode} value should match put value (primary key)`); 234 assert_equals( 235 result[3].key, `k${primaryKey}`, 236 `${opcode} value should match put value (key)`); 237 assert_equals( 238 result[3].value.join(','), expectedValue.join(','), 239 `${opcode} value should match put value (nested value)`); 240 break; 241 case 'continue-empty': 242 case 'open-empty': 243 assert_equals(result[1], null, `${opcode} result should be null`); 244 break; 245 } 246 } 247 248 function eventsTest(label, operations) { 249 promise_test(testCase => { 250 return createDatabase( 251 testCase, 252 (database, transaction) => { 253 const store = database.createObjectStore( 254 'test-store', {autoIncrement: true, keyPath: 'id'}); 255 store.createIndex('test-index', 'key', {unique: true}); 256 populateStore(store); 257 }) 258 .then(database => { 259 const transaction = database.transaction(['test-store'], 'readwrite'); 260 const store = transaction.objectStore('test-store'); 261 const index = store.index('test-index'); 262 return new Promise((resolve, reject) => { 263 openCursors(testCase, index, operations, reject, () => { 264 const results = []; 265 const promises = []; 266 for (let i = 0; i < operations.length; ++i) { 267 const promise = doOperation( 268 testCase, store, index, operations[i], i, results); 269 promises.push(promise); 270 }; 271 resolve(Promise.all(promises).then(() => results)); 272 }); 273 }); 274 }) 275 .then(results => { 276 assert_equals( 277 results.length, operations.length, 278 'Promise.all should resolve after all sub-promises resolve'); 279 for (let i = 0; i < operations.length; ++i) 280 checkOperationResult(operations[i], results[i], i); 281 }); 282 }, label); 283 }