OLD | NEW |
---|---|
(Empty) | |
1 'use strict'; | |
2 | |
3 // Returns an IndexedDB database name likely to be unique to the test case. | |
jsbell
2017/01/04 23:38:49
I'd drop 'likely to be'
pwnall
2017/01/05 06:52:12
Done.
| |
4 const databaseName = testCase => { | |
jsbell
2017/01/04 23:38:49
Is there an advantage to `const f = a => { ... };`
pwnall
2017/01/05 06:52:12
Done.
Replaced everywhere in the file.
| |
5 return 'db' + self.location.pathname + '-' + testCase.name; | |
6 }; | |
7 | |
8 // Creates an EventWatcher covering all the events that can be issued by | |
9 // IndexedDB requests and transactions. | |
jsbell
2017/01/04 23:38:49
no longer true - transactions have 'complete' and
pwnall
2017/01/05 06:52:12
Done.
I added the other events. Thank you for noti
| |
10 const requestWatcher = (testCase, request) => { | |
11 return new EventWatcher(testCase, request, | |
12 ['blocked', 'error', 'success', 'upgradeneeded']); | |
13 }; | |
14 | |
15 // Migrates an IndexedDB database whose name is unique for the test case. | |
16 // | |
17 // newVersion must be greater than the database's current version. | |
18 // | |
19 // migrationCallback will be called during a versionchange transaction and will | |
20 // given the created database, the versionchange transaction, and the database | |
21 // open request. | |
22 // | |
23 // Returns a promise. If the versionchange transaction goes through, the promise | |
24 // resolves to an IndexedDB database that must be closed by the caller. If the | |
25 // versionchange transaction is aborted, the promise resolves to an error. | |
26 const migrateDatabase = (testCase, newVersion, migrationCallback) => { | |
27 return migrateNamedDatabase( | |
28 testCase, databaseName(testCase), newVersion, migrationCallback); | |
29 }; | |
30 | |
31 // Migrates an IndexedDB database. | |
32 // | |
33 // newVersion must be greater than the database's current version. | |
34 // | |
35 // migrationCallback will be called during a versionchange transaction and will | |
36 // given the created database, the versionchange transaction, and the database | |
37 // open request. | |
38 // | |
39 // Returns a promise. If the versionchange transaction goes through, the promise | |
40 // resolves to an IndexedDB database that must be closed by the caller. If the | |
41 // versionchange transaction is aborted, the promise resolves to an error. | |
42 const migrateNamedDatabase = | |
43 (testCase, databaseName, newVersion, migrationCallback) => { | |
44 // We cannot use eventWatcher.wait_for('upgradeneeded') here, because | |
45 // the versionchange transaction auto-commits before the Promise's then | |
46 // callback gets called. | |
47 return new Promise((resolve, reject) => { | |
48 const request = indexedDB.open(databaseName, newVersion); | |
49 request.onupgradeneeded = testCase.step_func(event => { | |
50 const database = event.target.result; | |
51 const transaction = event.target.transaction; | |
52 let shouldBeAborted = false; | |
53 let requestEventPromise = null; | |
54 | |
55 // We wrap IDBTransaction.abort so we can set up the correct event | |
56 // listeners and expectations if the test chooses to abort the | |
57 // versionchange transaction. | |
58 const transactionAbort = transaction.abort.bind(transaction); | |
59 transaction.abort = () => { | |
60 transaction._willBeAborted(); | |
61 transactionAbort(); | |
62 } | |
63 transaction._willBeAborted = () => { | |
64 requestEventPromise = new Promise((resolve, reject) => { | |
65 request.onerror = event => { | |
66 event.preventDefault(); | |
67 resolve(event); | |
68 }; | |
69 request.onsuccess = () => reject(new Error( | |
70 'indexedDB.open should not succeed for an aborted ' + | |
71 'versionchange transaction')); | |
72 }); | |
73 shouldBeAborted = true; | |
74 } | |
75 | |
76 // If migration callback returns a promise, we'll wait for it to resolve. | |
77 // This simplifies some tests. | |
78 const callbackResult = migrationCallback(database, transaction, request); | |
79 if (!shouldBeAborted) { | |
80 request.onerror = null; | |
81 request.onsuccess = null; | |
82 requestEventPromise = | |
83 requestWatcher(testCase, request).wait_for('success'); | |
84 } | |
85 | |
86 // requestEventPromise needs to be the last promise in the chain, because | |
87 // we want the event that it resolves to. | |
88 resolve(Promise.resolve(callbackResult).then(() => requestEventPromise)); | |
89 }); | |
90 request.onerror = event => reject(event.target.error); | |
91 request.onsuccess = () => reject(new Error( | |
92 'indexedDB.open should not succeed without creating a ' + | |
93 'versionchange transaction')); | |
94 }).then(event => event.target.result || event.target.error); | |
95 }; | |
96 | |
97 // Creates an IndexedDB database whose name is unique for the test case. | |
98 // | |
99 // setupCallback will be called during a versionchange transaction, and will be | |
100 // given the created database, the versionchange transaction, and the database | |
101 // open request. | |
102 // | |
103 // Returns a promise that resolves to an IndexedDB database. The caller must | |
104 // close the database. | |
105 const createDatabase = (testCase, setupCallback) => { | |
106 return createNamedDatabase(testCase, databaseName(testCase), setupCallback); | |
107 }; | |
108 | |
109 // Creates an IndexedDB database. | |
110 // | |
111 // setupCallback will be called during a versionchange transaction, and will be | |
112 // given the created database, the versionchange transaction, and the database | |
113 // open request. | |
114 // | |
115 // Returns a promise that resolves to an IndexedDB database. The caller must | |
116 // close the database. | |
117 const createNamedDatabase = (testCase, databaseName, setupCallback) => { | |
118 const request = indexedDB.deleteDatabase(databaseName); | |
119 const eventWatcher = requestWatcher(testCase, request); | |
120 | |
121 return eventWatcher.wait_for('success').then(event => | |
122 migrateNamedDatabase(testCase, databaseName, 1, setupCallback)); | |
123 }; | |
124 | |
125 // Opens an IndexedDB database without performing schema changes. | |
126 // | |
127 // The given version number must match the database's current version. | |
128 // | |
129 // Returns a promise that resolves to an IndexedDB database. The caller must | |
130 // close the database. | |
131 const openDatabase = (testCase, version) => { | |
132 return openNamedDatabase(testCase, databaseName(testCase), version); | |
133 } | |
134 | |
135 // Opens an IndexedDB database without performing schema changes. | |
136 // | |
137 // The given version number must match the database's current version. | |
138 // | |
139 // Returns a promise that resolves to an IndexedDB database. The caller must | |
140 // close the database. | |
141 const openNamedDatabase = (testCase, databaseName, version) => { | |
142 const request = indexedDB.open(databaseName, version); | |
143 const eventWatcher = requestWatcher(testCase, request); | |
144 return eventWatcher.wait_for('success').then(event => event.target.result); | |
145 } | |
146 | |
147 // The data in the 'books' object store records in the first example of the | |
148 // IndexedDB specification. | |
149 const BOOKS_RECORD_DATA = [ | |
150 { title: 'Quarry Memories', author: 'Fred', isbn: 123456 }, | |
151 { title: 'Water Buffaloes', author: 'Fred', isbn: 234567 }, | |
152 { title: 'Bedrock Nights', author: 'Barney', isbn: 345678 }, | |
153 ]; | |
154 | |
155 // Creates a 'books' object store whose contents closely resembles the first | |
156 // example in the IndexedDB specification. | |
157 const createBooksStore = (testCase, database) => { | |
158 const store = database.createObjectStore('books', | |
159 { keyPath: 'isbn', autoIncrement: true }); | |
160 store.createIndex('by_author', 'author'); | |
161 store.createIndex('by_title', 'title', { unique: true }); | |
162 for (let record of BOOKS_RECORD_DATA) | |
163 store.put(record); | |
164 return store; | |
165 }; | |
166 | |
167 // Creates a 'not_books' object store used to test renaming into existing or | |
168 // deleted store names. | |
169 const createNotBooksStore = (testCase, database) => { | |
170 const store = database.createObjectStore('not_books'); | |
171 store.createIndex('not_by_author', 'author'); | |
172 store.createIndex('not_by_title', 'title', { unique: true }); | |
173 return store; | |
174 }; | |
175 | |
176 // Verifies that an object store's indexes match the indexes used to create the | |
177 // books store in the test database's version 1. | |
178 // | |
179 // The errorMessage is used if the assertions fail. It can state that the | |
180 // IndexedDB implementation being tested is incorrect, or that the testing code | |
181 // is using it incorrectly. | |
182 const checkStoreIndexes = (testCase, store, errorMessage) => { | |
183 assert_array_equals( | |
184 store.indexNames, ['by_author', 'by_title'], errorMessage); | |
185 const authorIndex = store.index('by_author'); | |
186 const titleIndex = store.index('by_title'); | |
187 return Promise.all([ | |
188 checkAuthorIndexContents(testCase, authorIndex, errorMessage), | |
189 checkTitleIndexContents(testCase, titleIndex, errorMessage), | |
190 ]); | |
191 }; | |
192 | |
193 // Verifies that an object store's key generator is in the same state as the | |
194 // key generator created for the books store in the test database's version 1. | |
195 // | |
196 // The errorMessage is used if the assertions fail. It can state that the | |
197 // IndexedDB implementation being tested is incorrect, or that the testing code | |
198 // is using it incorrectly. | |
199 const checkStoreGenerator = (testCase, store, expectedKey, errorMessage) => { | |
200 const request = store.put( | |
201 { title: 'Bedrock Nights ' + expectedKey, author: 'Barney' }); | |
202 const eventWatcher = requestWatcher(testCase, request); | |
203 return eventWatcher.wait_for('success').then(() => { | |
204 const result = request.result; | |
205 assert_equals(result, expectedKey, errorMessage); | |
206 }); | |
207 }; | |
208 | |
209 // Verifies that an object store's contents matches the contents used to create | |
210 // the books store in the test database's version 1. | |
211 // | |
212 // The errorMessage is used if the assertions fail. It can state that the | |
213 // IndexedDB implementation being tested is incorrect, or that the testing code | |
214 // is using it incorrectly. | |
215 const checkStoreContents = (testCase, store, errorMessage) => { | |
216 const request = store.get(123456); | |
217 const eventWatcher = requestWatcher(testCase, request); | |
218 return eventWatcher.wait_for('success').then(() => { | |
219 const result = request.result; | |
220 assert_equals(result.isbn, BOOKS_RECORD_DATA[0].isbn, errorMessage); | |
221 assert_equals(result.author, BOOKS_RECORD_DATA[0].author, errorMessage); | |
222 assert_equals(result.title, BOOKS_RECORD_DATA[0].title, errorMessage); | |
223 }); | |
224 }; | |
225 | |
226 // Verifies that index matches the 'by_author' index used to create the | |
227 // by_author books store in the test database's version 1. | |
228 // | |
229 // The errorMessage is used if the assertions fail. It can state that the | |
230 // IndexedDB implementation being tested is incorrect, or that the testing code | |
231 // is using it incorrectly. | |
232 const checkAuthorIndexContents = (testCase, index, errorMessage) => { | |
233 const request = index.get(BOOKS_RECORD_DATA[2].author); | |
234 const eventWatcher = requestWatcher(testCase, request); | |
235 return eventWatcher.wait_for('success').then(() => { | |
236 const result = request.result; | |
237 assert_equals(result.isbn, BOOKS_RECORD_DATA[2].isbn, errorMessage); | |
238 assert_equals(result.title, BOOKS_RECORD_DATA[2].title, errorMessage); | |
239 }); | |
240 }; | |
241 | |
242 // Verifies that an index matches the 'by_title' index used to create the books | |
243 // store in the test database's version 1. | |
244 // | |
245 // The errorMessage is used if the assertions fail. It can state that the | |
246 // IndexedDB implementation being tested is incorrect, or that the testing code | |
247 // is using it incorrectly. | |
248 const checkTitleIndexContents = (testCase, index, errorMessage) => { | |
249 const request = index.get(BOOKS_RECORD_DATA[2].title); | |
250 const eventWatcher = requestWatcher(testCase, request); | |
251 return eventWatcher.wait_for('success').then(() => { | |
252 const result = request.result; | |
253 assert_equals(result.isbn, BOOKS_RECORD_DATA[2].isbn, errorMessage); | |
254 assert_equals(result.author, BOOKS_RECORD_DATA[2].author, errorMessage); | |
255 }); | |
256 }; | |
OLD | NEW |