db_test.js 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487
  1. // Copyright 2012 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. goog.provide('goog.dbTest');
  15. goog.setTestOnly('goog.dbTest');
  16. goog.require('goog.Disposable');
  17. goog.require('goog.Promise');
  18. goog.require('goog.array');
  19. goog.require('goog.db');
  20. goog.require('goog.db.Cursor');
  21. goog.require('goog.db.Error');
  22. goog.require('goog.db.IndexedDb');
  23. goog.require('goog.db.KeyRange');
  24. goog.require('goog.db.Transaction');
  25. goog.require('goog.events');
  26. goog.require('goog.object');
  27. goog.require('goog.testing.PropertyReplacer');
  28. goog.require('goog.testing.asserts');
  29. goog.require('goog.testing.jsunit');
  30. goog.require('goog.userAgent.product');
  31. var idbSupported = goog.userAgent.product.CHROME;
  32. var dbName;
  33. var dbBaseName = 'testDb';
  34. var globalDb = null;
  35. var dbsToClose = [];
  36. var propertyReplacer;
  37. var baseVersion = 1;
  38. var dbVersion = 1;
  39. var TransactionMode = goog.db.Transaction.TransactionMode;
  40. var EventTypes = goog.db.Transaction.EventTypes;
  41. function openDatabase() {
  42. return goog.db.openDatabase(dbName).addCallback(function(db) {
  43. dbsToClose.push(db);
  44. });
  45. }
  46. function incrementVersion(db, onUpgradeNeeded) {
  47. db.close();
  48. var onBlocked = function(ev) {
  49. fail('Upgrade to version ' + dbVersion + ' is blocked.');
  50. };
  51. return goog.db.openDatabase(dbName, ++dbVersion, onUpgradeNeeded, onBlocked)
  52. .addCallback(function(db) {
  53. dbsToClose.push(db);
  54. assertEquals(dbVersion, db.getVersion());
  55. });
  56. }
  57. function addStore(db) {
  58. return incrementVersion(
  59. db, function(ev, db, tx) { db.createObjectStore('store'); });
  60. }
  61. function addStoreWithIndex(db) {
  62. return incrementVersion(db, function(ev, db, tx) {
  63. var store = db.createObjectStore('store', {keyPath: 'key'});
  64. store.createIndex('index', 'value');
  65. });
  66. }
  67. function populateStore(values, keys, db) {
  68. var putTx = db.createTransaction(['store'], TransactionMode.READ_WRITE);
  69. var store = putTx.objectStore('store');
  70. for (var i = 0; i < values.length; ++i) {
  71. store.put(values[i], keys[i]);
  72. }
  73. return putTx.wait();
  74. }
  75. function populateStoreWithObjects(values, keys, db) {
  76. var putTx = db.createTransaction(['store'], TransactionMode.READ_WRITE);
  77. var store = putTx.objectStore('store');
  78. goog.array.forEach(values, function(value, index) {
  79. store.put({'key': keys[index], 'value': value});
  80. });
  81. return putTx.wait();
  82. }
  83. function assertStoreValues(values, db) {
  84. var assertStoreTx = db.createTransaction(['store']);
  85. assertStoreTx.objectStore('store').getAll().addCallback(function(results) {
  86. assertSameElements(values, results);
  87. });
  88. }
  89. function assertStoreObjectValues(values, db) {
  90. var assertStoreTx = db.createTransaction(['store']);
  91. assertStoreTx.objectStore('store').getAll().addCallback(function(results) {
  92. var retrievedValues =
  93. goog.array.map(results, function(result) { return result['value']; });
  94. assertSameElements(values, retrievedValues);
  95. });
  96. }
  97. function assertStoreValuesAndCursorsDisposed(values, cursors, db) {
  98. var assertStoreTx = db.createTransaction(['store']);
  99. assertStoreTx.objectStore('store').getAll().addCallback(function(results) {
  100. assertSameElements(values, results);
  101. assertTrue(cursors.length > 0);
  102. goog.array.forEach(cursors, function(elem, index, array) {
  103. console.log(elem);
  104. assertTrue(
  105. 'array[' + index + '] (' + elem + ') is not disposed',
  106. goog.Disposable.isDisposed(elem));
  107. });
  108. });
  109. }
  110. function assertStoreDoesntExist(db) {
  111. try {
  112. db.createTransaction(['store']);
  113. fail('Create transaction with a non-existent store should have failed.');
  114. } catch (e) {
  115. // expected
  116. assertEquals(e.getName(), goog.db.Error.ErrorName.NOT_FOUND_ERR);
  117. }
  118. }
  119. function transactionToPromise(db, trx) {
  120. return new goog.Promise(function(resolve, reject) {
  121. goog.events.listen(trx, EventTypes.ERROR, reject);
  122. goog.events.listen(trx, EventTypes.COMPLETE, function() { resolve(db); });
  123. });
  124. }
  125. // Calls onRecordReady each time that a new record can be read by the
  126. // cursor with cursor.next(). Returns a promise that resolves or rejects
  127. // based on when the cursor is complete or errors out. If onRecordReady
  128. // returns a promise that promises is also waited on before the returned
  129. // promise resolves.
  130. function forEachRecord(cursor, onRecordReady) {
  131. var promises = [];
  132. return new goog
  133. .Promise(function(resolve, reject) {
  134. var key = goog.events.listen(
  135. cursor, goog.db.Cursor.EventType.NEW_DATA, function() {
  136. var result = onRecordReady();
  137. if (result && ('then' in result)) {
  138. promises.push(result);
  139. }
  140. });
  141. goog.events.listenOnce(
  142. cursor,
  143. [goog.db.Cursor.EventType.COMPLETE, goog.db.Cursor.EventType.ERROR],
  144. function(evt) {
  145. goog.events.unlistenByKey(key);
  146. if (evt.type == goog.db.Cursor.EventType.COMPLETE) {
  147. resolve();
  148. } else {
  149. reject(evt);
  150. }
  151. });
  152. })
  153. .then(function() { return goog.Promise.all(promises); });
  154. }
  155. function failOnErrorEvent(ev) {
  156. fail(ev.target.message);
  157. }
  158. function setUpPage() {
  159. propertyReplacer = new goog.testing.PropertyReplacer();
  160. }
  161. function setUp() {
  162. if (!idbSupported) {
  163. return;
  164. }
  165. // Always use a clean database by generating a new database name.
  166. dbName = dbBaseName + Date.now().toString();
  167. globalDb = openDatabase();
  168. }
  169. function tearDown() {
  170. for (var i = 0; i < dbsToClose.length; i++) {
  171. dbsToClose[i].close();
  172. }
  173. dbsToClose = [];
  174. propertyReplacer.reset();
  175. }
  176. function testDatabaseOpened() {
  177. if (!idbSupported) {
  178. return;
  179. }
  180. assertNotNull(globalDb);
  181. return globalDb.branch().addCallback(function(db) {
  182. assertTrue(db.isOpen());
  183. });
  184. }
  185. function testOpenWithNewVersion() {
  186. if (!idbSupported) {
  187. return;
  188. }
  189. var upgradeNeeded = false;
  190. return globalDb.branch()
  191. .addCallback(function(db) {
  192. assertEquals(baseVersion, db.getVersion());
  193. return incrementVersion(
  194. db, function(ev, db, tx) { upgradeNeeded = true; });
  195. })
  196. .addCallback(function(db) { assertTrue(upgradeNeeded); });
  197. }
  198. function testManipulateObjectStores() {
  199. if (!idbSupported) {
  200. return;
  201. }
  202. return globalDb.branch()
  203. .addCallback(function(db) {
  204. assertEquals(baseVersion, db.getVersion());
  205. return incrementVersion(db, function(ev, db, tx) {
  206. db.createObjectStore('basicStore');
  207. db.createObjectStore('keyPathStore', {keyPath: 'keyGoesHere'});
  208. db.createObjectStore('autoIncrementStore', {autoIncrement: true});
  209. });
  210. })
  211. .addCallback(function(db) {
  212. var storeNames = db.getObjectStoreNames();
  213. assertEquals(3, storeNames.length);
  214. assertTrue(storeNames.contains('basicStore'));
  215. assertTrue(storeNames.contains('keyPathStore'));
  216. assertTrue(storeNames.contains('autoIncrementStore'));
  217. return incrementVersion(
  218. db, function(ev, db, tx) { db.deleteObjectStore('basicStore'); });
  219. })
  220. .addCallback(function(db) {
  221. var storeNames = db.getObjectStoreNames();
  222. assertEquals(2, storeNames.length);
  223. assertFalse(storeNames.contains('basicStore'));
  224. assertTrue(storeNames.contains('keyPathStore'));
  225. assertTrue(storeNames.contains('autoIncrementStore'));
  226. });
  227. }
  228. function testBadObjectStoreManipulation() {
  229. if (!idbSupported) {
  230. return;
  231. }
  232. var expectedCode = goog.db.Error.ErrorName.INVALID_STATE_ERR;
  233. return globalDb.branch()
  234. .addCallback(function(db) {
  235. try {
  236. db.createObjectStore('diediedie');
  237. fail('Create object store outside transaction should have failed.');
  238. } catch (err) {
  239. // expected
  240. assertEquals(expectedCode, err.getName());
  241. }
  242. })
  243. .addCallback(addStore)
  244. .addCallback(function(db) {
  245. try {
  246. db.deleteObjectStore('store');
  247. fail('Delete object store outside transaction should have failed.');
  248. } catch (err) {
  249. // expected
  250. assertEquals(expectedCode, err.getName());
  251. }
  252. })
  253. .addCallback(function(db) {
  254. return incrementVersion(db, function(ev, db, tx) {
  255. try {
  256. db.deleteObjectStore('diediedie');
  257. fail('Delete non-existent store should have failed.');
  258. } catch (err) {
  259. // expected
  260. assertEquals(goog.db.Error.ErrorName.NOT_FOUND_ERR, err.getName());
  261. }
  262. });
  263. });
  264. }
  265. function testGetNonExistentObjectStore() {
  266. if (!idbSupported) {
  267. return;
  268. }
  269. return globalDb.branch().addCallback(addStore).addCallback(function(db) {
  270. var tx = db.createTransaction(['store']);
  271. try {
  272. tx.objectStore('diediedie');
  273. fail('getting non-existent object store should have failed');
  274. } catch (err) {
  275. assertEquals(goog.db.Error.ErrorName.NOT_FOUND_ERR, err.getName());
  276. }
  277. });
  278. }
  279. function testCreateTransaction() {
  280. if (!idbSupported) {
  281. return;
  282. }
  283. return globalDb.branch().addCallback(addStore).addCallback(function(db) {
  284. var tx = db.createTransaction(['store']);
  285. assertEquals('mode not READ_ONLY', TransactionMode.READ_ONLY, tx.getMode());
  286. tx = db.createTransaction(
  287. ['store'], goog.db.Transaction.TransactionMode.READ_WRITE);
  288. assertEquals(
  289. 'mode not READ_WRITE', TransactionMode.READ_WRITE, tx.getMode());
  290. });
  291. }
  292. function testPutRecord() {
  293. if (!idbSupported) {
  294. return;
  295. }
  296. return globalDb.branch()
  297. .addCallback(addStore)
  298. .addCallback(function(db) {
  299. var initialPutTx =
  300. db.createTransaction(['store'], TransactionMode.READ_WRITE);
  301. var putOperation = initialPutTx.objectStore('store').put(
  302. {key: 'initial', value: 'value1'}, 'putKey');
  303. putOperation.addCallback(function(key) {
  304. assertEquals('putKey', key);
  305. });
  306. return transactionToPromise(db, initialPutTx);
  307. })
  308. .addCallback(function(db) {
  309. var checkResultsTx = db.createTransaction(['store']);
  310. var getOperation = checkResultsTx.objectStore('store').get('putKey');
  311. getOperation.addCallback(function(result) {
  312. assertEquals('initial', result.key);
  313. assertEquals('value1', result.value);
  314. });
  315. return transactionToPromise(db, checkResultsTx);
  316. })
  317. .addCallback(function(db) {
  318. var overwriteTx =
  319. db.createTransaction(['store'], TransactionMode.READ_WRITE);
  320. var putOperation = overwriteTx.objectStore('store').put(
  321. {key: 'overwritten', value: 'value2'}, 'putKey');
  322. putOperation.addCallback(function(key) {
  323. assertEquals('putKey', key);
  324. });
  325. return transactionToPromise(db, overwriteTx);
  326. })
  327. .addCallback(function(db) {
  328. var checkOverwriteTx = db.createTransaction(['store']);
  329. checkOverwriteTx.objectStore('store').get('putKey').addCallback(
  330. function(result) {
  331. // this is guaranteed to run before the COMPLETE event fires on
  332. // the transaction
  333. assertEquals('overwritten', result.key);
  334. assertEquals('value2', result.value);
  335. });
  336. return transactionToPromise(db, checkOverwriteTx);
  337. });
  338. }
  339. function testAddRecord() {
  340. if (!idbSupported) {
  341. return;
  342. }
  343. return globalDb.branch()
  344. .addCallback(addStore)
  345. .addCallback(function(db) {
  346. var initialAddTx =
  347. db.createTransaction(['store'], TransactionMode.READ_WRITE);
  348. var addOperation = initialAddTx.objectStore('store').add(
  349. {key: 'hi', value: 'something'}, 'stuff');
  350. addOperation.addCallback(function(key) {
  351. assertEquals('stuff', key);
  352. });
  353. return transactionToPromise(db, initialAddTx);
  354. })
  355. .addCallback(function(db) {
  356. var successfulAddTx = db.createTransaction(['store']);
  357. var getOperation = successfulAddTx.objectStore('store').get('stuff');
  358. getOperation.addCallback(function(result) {
  359. assertEquals('hi', result.key);
  360. assertEquals('something', result.value);
  361. });
  362. return transactionToPromise(db, successfulAddTx);
  363. })
  364. .addCallback(function(db) {
  365. var addOverwriteTx =
  366. db.createTransaction(['store'], TransactionMode.READ_WRITE);
  367. addOverwriteTx.objectStore('store')
  368. .add({key: 'bye', value: 'nothing'}, 'stuff')
  369. .addErrback(function(err) {
  370. // expected
  371. assertEquals(
  372. goog.db.Error.ErrorName.CONSTRAINT_ERR, err.getName());
  373. });
  374. return transactionToPromise(db, addOverwriteTx)
  375. .then(
  376. function() {
  377. fail('adding existing record should not have succeeded');
  378. },
  379. function(ev) {
  380. // expected
  381. assertEquals(
  382. goog.db.Error.ErrorName.CONSTRAINT_ERR,
  383. ev.target.getName());
  384. });
  385. });
  386. }
  387. function testPutRecordKeyPathStore() {
  388. if (!idbSupported) {
  389. return;
  390. }
  391. return globalDb.branch()
  392. .addCallback(function(db) {
  393. return incrementVersion(db, function(ev, db, tx) {
  394. db.createObjectStore('keyStore', {keyPath: 'key'});
  395. });
  396. })
  397. .addCallback(function(db) {
  398. var putTx =
  399. db.createTransaction(['keyStore'], TransactionMode.READ_WRITE);
  400. var putOperation =
  401. putTx.objectStore('keyStore').put({key: 'hi', value: 'something'});
  402. putOperation.addCallback(function(key) {
  403. assertEquals('hi', key);
  404. });
  405. return transactionToPromise(db, putTx);
  406. })
  407. .addCallback(function(db) {
  408. var checkResultsTx = db.createTransaction(['keyStore']);
  409. checkResultsTx.objectStore('keyStore')
  410. .get('hi')
  411. .addCallback(function(result) {
  412. assertNotUndefined(result);
  413. assertEquals('hi', result.key);
  414. assertEquals('something', result.value);
  415. });
  416. return transactionToPromise(db, checkResultsTx);
  417. });
  418. }
  419. function testPutBadRecordKeyPathStore() {
  420. if (!idbSupported) {
  421. return;
  422. }
  423. return globalDb.branch()
  424. .addCallback(function(db) {
  425. return incrementVersion(db, function(ev, db, tx) {
  426. db.createObjectStore('keyStore', {keyPath: 'key'});
  427. });
  428. })
  429. .addCallback(function(db) {
  430. var badTx =
  431. db.createTransaction(['keyStore'], TransactionMode.READ_WRITE);
  432. return badTx.objectStore('keyStore')
  433. .put({key: 'diedie', value: 'anything'}, 'badKey')
  434. .then(
  435. function() {
  436. fail('inserting with explicit key should have failed');
  437. },
  438. function(err) {
  439. // expected
  440. assertEquals(goog.db.Error.ErrorName.DATA_ERR, err.getName());
  441. });
  442. });
  443. }
  444. function testPutRecordAutoIncrementStore() {
  445. if (!idbSupported) {
  446. return;
  447. }
  448. return globalDb.branch()
  449. .addCallback(function(db) {
  450. return incrementVersion(db, function(ev, db, tx) {
  451. db.createObjectStore('aiStore', {autoIncrement: true});
  452. });
  453. })
  454. .addCallback(function(db) {
  455. var tx = db.createTransaction(['aiStore'], TransactionMode.READ_WRITE);
  456. var putOperation1 = tx.objectStore('aiStore').put('1');
  457. var putOperation2 = tx.objectStore('aiStore').put('2');
  458. var putOperation3 = tx.objectStore('aiStore').put('3');
  459. putOperation1.addCallback(function(key) {
  460. assertNotUndefined(key);
  461. });
  462. putOperation2.addCallback(function(key) {
  463. assertNotUndefined(key);
  464. });
  465. putOperation3.addCallback(function(key) {
  466. assertNotUndefined(key);
  467. });
  468. return transactionToPromise(db, tx);
  469. })
  470. .addCallback(function(db) {
  471. var tx = db.createTransaction(['aiStore']);
  472. var getAllOperation = tx.objectStore('aiStore').getAll();
  473. return getAllOperation.addCallback(function(results) {
  474. assertEquals(3, results.length);
  475. // only checking to see if the results are included because the keys
  476. // are not specified
  477. assertNotEquals(-1, results.indexOf('1'));
  478. assertNotEquals(-1, results.indexOf('2'));
  479. assertNotEquals(-1, results.indexOf('3'));
  480. });
  481. });
  482. }
  483. function testPutRecordKeyPathAndAutoIncrementStore() {
  484. if (!idbSupported) {
  485. return;
  486. }
  487. return globalDb.branch()
  488. .addCallback(function(db) {
  489. return incrementVersion(db, function(ev, db, tx) {
  490. db.createObjectStore(
  491. 'hybridStore', {keyPath: 'key', autoIncrement: true});
  492. });
  493. })
  494. .addCallback(function(db) {
  495. var tx =
  496. db.createTransaction(['hybridStore'], TransactionMode.READ_WRITE);
  497. var putOperation =
  498. tx.objectStore('hybridStore').put({value: 'whatever'});
  499. putOperation.addCallback(function(key) {
  500. assertNotUndefined(key);
  501. });
  502. return putOperation.addCallback(function() {
  503. return db;
  504. });
  505. })
  506. .addCallback(function(db) {
  507. var tx = db.createTransaction(['hybridStore']);
  508. return tx.objectStore('hybridStore').getAll().then(function(results) {
  509. assertEquals(1, results.length);
  510. assertEquals('whatever', results[0].value);
  511. assertNotUndefined(results[0].key);
  512. });
  513. });
  514. }
  515. function testPutIllegalRecords() {
  516. if (!idbSupported) {
  517. return;
  518. }
  519. return globalDb.branch().addCallback(addStore).addCallback(function(db) {
  520. var tx = db.createTransaction(['store'], TransactionMode.READ_WRITE);
  521. var promises = [];
  522. var badKeyFail = function(keyKind) {
  523. return function() {
  524. return fail('putting with ' + keyKind + ' key should have failed');
  525. }
  526. };
  527. var assertExpectedError = function(err) {
  528. assertEquals(goog.db.Error.ErrorName.DATA_ERR, err.getName());
  529. };
  530. promises.push(
  531. tx.objectStore('store')
  532. .put('death', null)
  533. .then(badKeyFail('null'), assertExpectedError));
  534. promises.push(
  535. tx.objectStore('store')
  536. .put('death', NaN)
  537. .then(badKeyFail('NaN'), assertExpectedError));
  538. promises.push(
  539. tx.objectStore('store')
  540. .put('death', undefined)
  541. .then(badKeyFail('undefined'), assertExpectedError));
  542. return goog.Promise.all(promises);
  543. });
  544. }
  545. function testPutIllegalRecordsWithIndex() {
  546. if (!idbSupported) {
  547. return;
  548. }
  549. return globalDb.branch()
  550. .addCallback(addStoreWithIndex)
  551. .addCallback(function(db) {
  552. var tx = db.createTransaction(['store'], TransactionMode.READ_WRITE);
  553. var promises = [];
  554. var badKeyFail = function(keyKind) {
  555. return function() {
  556. fail('putting with ' + keyKind + ' key should have failed');
  557. }
  558. };
  559. var assertExpectedError = function(err) {
  560. // expected
  561. assertEquals(goog.db.Error.ErrorName.DATA_ERR, err.getName());
  562. };
  563. promises.push(
  564. tx.objectStore('store')
  565. .put({value: 'diediedie', key: null})
  566. .then(badKeyFail('null'), assertExpectedError));
  567. promises.push(
  568. tx.objectStore('store')
  569. .put({value: 'dietodeath', key: NaN})
  570. .then(badKeyFail('NaN'), assertExpectedError));
  571. promises.push(
  572. tx.objectStore('store')
  573. .put({value: 'dietodeath', key: undefined})
  574. .then(badKeyFail('undefined'), assertExpectedError));
  575. return goog.Promise.all(promises);
  576. });
  577. }
  578. function testDeleteRecord() {
  579. if (!idbSupported) {
  580. return;
  581. }
  582. var db;
  583. return globalDb.branch()
  584. .addCallback(addStore)
  585. .addCallback(function(openedDb) {
  586. db = openedDb;
  587. return db.createTransaction(['store'], TransactionMode.READ_WRITE)
  588. .objectStore('store')
  589. .put({key: 'hi', value: 'something'}, 'stuff');
  590. })
  591. .addCallback(function() {
  592. return db.createTransaction(['store'], TransactionMode.READ_WRITE)
  593. .objectStore('store')
  594. .remove('stuff');
  595. })
  596. .addCallback(function() {
  597. return db.createTransaction(['store']).objectStore('store').get(
  598. 'stuff');
  599. })
  600. .addCallback(function(result) { assertUndefined(result); });
  601. }
  602. function testDeleteRange() {
  603. if (!idbSupported) {
  604. return;
  605. }
  606. var values = ['1', '2', '3'];
  607. var keys = ['a', 'b', 'c'];
  608. var addData = goog.partial(populateStore, values, keys);
  609. var checkStore = goog.partial(assertStoreValues, ['1']);
  610. return globalDb.branch()
  611. .addCallback(addStore)
  612. .addCallback(addData)
  613. .addCallback(function(db) {
  614. return db.createTransaction(['store'], TransactionMode.READ_WRITE)
  615. .objectStore('store')
  616. .remove(goog.db.KeyRange.bound('b', 'c'))
  617. .then(function() {
  618. return db;
  619. });
  620. })
  621. .addCallback(checkStore);
  622. }
  623. function testGetAll() {
  624. if (!idbSupported) {
  625. return;
  626. }
  627. var values = ['1', '2', '3'];
  628. var keys = ['a', 'b', 'c'];
  629. var addData = goog.partial(populateStore, values, keys);
  630. var checkStore = goog.partial(assertStoreValues, values);
  631. return globalDb.branch()
  632. .addCallback(addStore)
  633. .addCallback(addData)
  634. .addCallback(checkStore);
  635. }
  636. function testGetAllFreesCursor() {
  637. if (!idbSupported) {
  638. return;
  639. }
  640. var values = ['1', '2', '3'];
  641. var keys = ['a', 'b', 'c'];
  642. var addData = goog.partial(populateStore, values, keys);
  643. var origCursor = goog.db.Cursor;
  644. var cursors = [];
  645. /** @constructor */
  646. var testCursor = function() {
  647. origCursor.call(this);
  648. cursors.push(this);
  649. };
  650. goog.object.extend(testCursor, origCursor);
  651. // We don't use goog.inherits here because we are going to be overwriting
  652. // goog.db.Cursor and we don't want a new "base" method as
  653. // goog.db.Cursor.base(this, 'constructor') would be a call to
  654. // testCursor.base(this, 'constructor') which would be goog.db.Cursor and be
  655. // an infinite loop.
  656. testCursor.prototype = origCursor.prototype;
  657. propertyReplacer.replace(goog.db, 'Cursor', testCursor);
  658. var checkStoreAndCursorDisposed =
  659. goog.partial(assertStoreValuesAndCursorsDisposed, values, cursors);
  660. return globalDb.branch()
  661. .addCallback(addStore)
  662. .addCallback(addData)
  663. .addCallback(checkStoreAndCursorDisposed);
  664. }
  665. function testObjectStoreCursorGet() {
  666. if (!idbSupported) {
  667. return;
  668. }
  669. var values = ['1', '2', '3', '4'];
  670. var keys = ['a', 'b', 'c', 'd'];
  671. var addData = goog.partial(populateStore, values, keys);
  672. var db;
  673. var resultValues = [];
  674. var resultKeys = [];
  675. // Open the cursor over range ['b', 'c'], move in backwards direction.
  676. return globalDb.branch()
  677. .addCallback(addStore)
  678. .addCallback(addData)
  679. .addCallback(function(theDb) {
  680. db = theDb;
  681. var cursorTx = db.createTransaction(['store']);
  682. var store = cursorTx.objectStore('store');
  683. var cursor = store.openCursor(
  684. goog.db.KeyRange.bound('b', 'c'), goog.db.Cursor.Direction.PREV);
  685. var whenCursorComplete = forEachRecord(cursor, function() {
  686. resultValues.push(cursor.getValue());
  687. resultKeys.push(cursor.getKey());
  688. cursor.next();
  689. });
  690. return goog.Promise.all([cursorTx.wait(), whenCursorComplete]);
  691. })
  692. .addCallback(function() {
  693. assertArrayEquals(['3', '2'], resultValues);
  694. assertArrayEquals(['c', 'b'], resultKeys);
  695. });
  696. }
  697. function testObjectStoreCursorReplace() {
  698. if (!idbSupported) {
  699. return;
  700. }
  701. var values = ['1', '2', '3', '4'];
  702. var keys = ['a', 'b', 'c', 'd'];
  703. var addData = goog.partial(populateStore, values, keys);
  704. // Store should contain ['1', '2', '5', '4'] after replacement.
  705. var checkStore = goog.partial(assertStoreValues, ['1', '2', '5', '4']);
  706. // Use a bounded cursor for ('b', 'c'] to update value '3' -> '5'.
  707. var openCursorAndReplace = function(db) {
  708. var cursorTx = db.createTransaction(['store'], TransactionMode.READ_WRITE);
  709. var store = cursorTx.objectStore('store');
  710. var cursor = store.openCursor(goog.db.KeyRange.bound('b', 'c', true));
  711. var whenCursorComplete = forEachRecord(cursor, function() {
  712. assertEquals('3', cursor.getValue());
  713. return cursor.update('5').addCallback(function() { cursor.next(); });
  714. });
  715. return goog.Promise.all([cursorTx.wait(), whenCursorComplete])
  716. .then(function() { return db; });
  717. };
  718. return globalDb.branch()
  719. .addCallback(addStore)
  720. .addCallback(addData)
  721. .addCallback(openCursorAndReplace)
  722. .addCallback(checkStore);
  723. }
  724. function testObjectStoreCursorRemove() {
  725. if (!idbSupported) {
  726. return;
  727. }
  728. var values = ['1', '2', '3', '4'];
  729. var keys = ['a', 'b', 'c', 'd'];
  730. var addData = goog.partial(populateStore, values, keys);
  731. // Store should contain ['1', '2'] after removing elements.
  732. var checkStore = goog.partial(assertStoreValues, ['1', '2']);
  733. // Use a bounded cursor for ('b', ...) to remove '3', '4'.
  734. var openCursorAndRemove = function(db) {
  735. var cursorTx = db.createTransaction(['store'], TransactionMode.READ_WRITE);
  736. var store = cursorTx.objectStore('store');
  737. var cursor = store.openCursor(goog.db.KeyRange.lowerBound('b', true));
  738. var whenCursorComplete = forEachRecord(cursor, function() {
  739. return cursor.remove('5').addCallback(function() { cursor.next(); });
  740. });
  741. return goog.Promise.all([cursorTx.wait(), whenCursorComplete])
  742. .then(function(results) { return db; });
  743. };
  744. // Setup and execute test case.
  745. return globalDb.branch()
  746. .addCallback(addStore)
  747. .addCallback(addData)
  748. .addCallback(openCursorAndRemove)
  749. .addCallback(checkStore);
  750. }
  751. function testClear() {
  752. if (!idbSupported) {
  753. return;
  754. }
  755. var db;
  756. return globalDb.branch()
  757. .addCallback(addStore)
  758. .addCallback(function(theDb) {
  759. db = theDb;
  760. var putTx = db.createTransaction(['store'], TransactionMode.READ_WRITE);
  761. putTx.objectStore('store').put('1', 'a');
  762. putTx.objectStore('store').put('2', 'b');
  763. putTx.objectStore('store').put('3', 'c');
  764. return putTx.wait();
  765. })
  766. .addCallback(function() {
  767. return db.createTransaction(['store']).objectStore('store').getAll();
  768. })
  769. .addCallback(function(results) {
  770. assertEquals(3, results.length);
  771. return db.createTransaction(['store'], TransactionMode.READ_WRITE)
  772. .objectStore('store')
  773. .clear();
  774. })
  775. .addCallback(function() {
  776. return db.createTransaction(['store']).objectStore('store').getAll();
  777. })
  778. .addCallback(function(results) { assertEquals(0, results.length); });
  779. }
  780. function testAbortTransaction() {
  781. if (!idbSupported) {
  782. return;
  783. }
  784. var db;
  785. return globalDb.branch()
  786. .addCallback(addStore)
  787. .addCallback(function(theDb) {
  788. db = theDb;
  789. return new Promise(function(resolve, reject) {
  790. var abortTx =
  791. db.createTransaction(['store'], TransactionMode.READ_WRITE);
  792. abortTx.objectStore('store')
  793. .put('data', 'stuff')
  794. .addCallback(function() { abortTx.abort(); });
  795. goog.events.listen(abortTx, EventTypes.ERROR, reject);
  796. goog.events.listen(abortTx, EventTypes.COMPLETE, function() {
  797. fail(
  798. 'transaction shouldn\'t have' +
  799. ' completed after being aborted');
  800. });
  801. goog.events.listen(abortTx, EventTypes.ABORT, resolve);
  802. });
  803. })
  804. .addCallback(function() {
  805. var checkResultsTx = db.createTransaction(['store']);
  806. return checkResultsTx.objectStore('store').get('stuff');
  807. })
  808. .addCallback(function(result) { assertUndefined(result); });
  809. }
  810. function testInactiveTransaction() {
  811. if (!idbSupported) {
  812. return;
  813. }
  814. var db;
  815. var store;
  816. var index;
  817. var createAndFinishTransaction = function(theDb) {
  818. db = theDb;
  819. var tx = db.createTransaction(['store'], TransactionMode.READ_WRITE);
  820. store = tx.objectStore('store');
  821. index = store.getIndex('index');
  822. store.put({key: 'something', value: 'anything'});
  823. return tx.wait();
  824. };
  825. var assertCantUseInactiveTransaction = function() {
  826. var expectedCode = goog.db.Error.ErrorName.TRANSACTION_INACTIVE_ERR;
  827. var promises = [];
  828. var failOp = function(op) {
  829. return function() {
  830. fail(op + ' with inactive transaction should have failed');
  831. };
  832. };
  833. var assertCorrectError = function(err) {
  834. assertEquals(expectedCode, err.getName());
  835. };
  836. promises.push(
  837. store.put({key: 'another', value: 'thing'})
  838. .then(failOp('putting'), assertCorrectError));
  839. promises.push(
  840. store.add({key: 'another', value: 'thing'})
  841. .then(failOp('adding'), assertCorrectError));
  842. promises.push(
  843. store.remove('something').then(failOp('deleting'), assertCorrectError));
  844. promises.push(
  845. store.get('something').then(failOp('getting'), assertCorrectError));
  846. promises.push(
  847. store.getAll().then(failOp('getting all'), assertCorrectError));
  848. promises.push(
  849. store.clear().then(failOp('clearing all'), assertCorrectError));
  850. promises.push(
  851. index.get('anything')
  852. .then(failOp('getting from index'), assertCorrectError));
  853. promises.push(
  854. index.getKey('anything')
  855. .then(failOp('getting key from index'), assertCorrectError));
  856. promises.push(
  857. index.getAll('anything')
  858. .then(failOp('getting all from index'), assertCorrectError));
  859. promises.push(
  860. index.getAllKeys('anything')
  861. .then(failOp('getting all keys from index'), assertCorrectError));
  862. return goog.Promise.all(promises);
  863. };
  864. return globalDb.branch()
  865. .addCallback(addStoreWithIndex)
  866. .addCallback(createAndFinishTransaction)
  867. .addCallback(assertCantUseInactiveTransaction);
  868. }
  869. function testWrongTransactionMode() {
  870. if (!idbSupported) {
  871. return;
  872. }
  873. return globalDb.branch().addCallback(addStore).addCallback(function(db) {
  874. var tx = db.createTransaction(['store']);
  875. assertEquals(goog.db.Transaction.TransactionMode.READ_ONLY, tx.getMode());
  876. var promises = [];
  877. promises.push(
  878. tx.objectStore('store')
  879. .put('KABOOM!', 'anything')
  880. .then(
  881. function() { fail('putting should have failed'); },
  882. function(err) {
  883. assertEquals(
  884. goog.db.Error.ErrorName.READ_ONLY_ERR, err.getName());
  885. }));
  886. promises.push(
  887. tx.objectStore('store')
  888. .add('EXPLODE!', 'die')
  889. .then(
  890. function() { fail('adding should have failed'); },
  891. function(err) {
  892. assertEquals(
  893. goog.db.Error.ErrorName.READ_ONLY_ERR, err.getName());
  894. }));
  895. promises.push(
  896. tx.objectStore('store')
  897. .remove('no key', 'nothing')
  898. .then(
  899. function() { fail('deleting should have failed'); },
  900. function(err) {
  901. assertEquals(
  902. goog.db.Error.ErrorName.READ_ONLY_ERR, err.getName());
  903. }));
  904. return goog.Promise.all(promises);
  905. });
  906. }
  907. function testManipulateIndexes() {
  908. if (!idbSupported) {
  909. return;
  910. }
  911. return globalDb.branch()
  912. .addCallback(function(db) {
  913. return incrementVersion(db, function(ev, db, tx) {
  914. var store = db.createObjectStore('store');
  915. store.createIndex('index', 'attr1');
  916. store.createIndex('uniqueIndex', 'attr2', {unique: true});
  917. store.createIndex('multirowIndex', 'attr3', {multirow: true});
  918. });
  919. })
  920. .addCallback(function(db) {
  921. var tx = db.createTransaction(['store']);
  922. var store = tx.objectStore('store');
  923. var index = store.getIndex('index');
  924. var uniqueIndex = store.getIndex('uniqueIndex');
  925. var multirowIndex = store.getIndex('multirowIndex');
  926. try {
  927. var dies = store.getIndex('diediedie');
  928. fail('getting non-existent index should have failed');
  929. } catch (err) {
  930. // expected
  931. assertEquals(goog.db.Error.ErrorName.NOT_FOUND_ERR, err.getName());
  932. }
  933. return tx.wait();
  934. })
  935. .addCallback(function(db) {
  936. return incrementVersion(db, function(ev, db, tx) {
  937. var store = tx.objectStore('store');
  938. store.deleteIndex('index');
  939. try {
  940. store.deleteIndex('diediedie');
  941. fail('deleting non-existent index should have failed');
  942. } catch (err) {
  943. // expected
  944. assertEquals(goog.db.Error.ErrorName.NOT_FOUND_ERR, err.getName());
  945. }
  946. });
  947. })
  948. .addCallback(function(db) {
  949. var tx = db.createTransaction(['store']);
  950. var store = tx.objectStore('store');
  951. try {
  952. var index = store.getIndex('index');
  953. fail('getting deleted index should have failed');
  954. } catch (err) {
  955. // expected
  956. assertEquals(goog.db.Error.ErrorName.NOT_FOUND_ERR, err.getName());
  957. }
  958. var uniqueIndex = store.getIndex('uniqueIndex');
  959. var multirowIndex = store.getIndex('multirowIndex');
  960. });
  961. }
  962. function testAddRecordWithIndex() {
  963. if (!idbSupported) {
  964. return;
  965. }
  966. var addData = function(db) {
  967. var store = db.createTransaction(['store'], TransactionMode.READ_WRITE)
  968. .objectStore('store');
  969. assertFalse(store.getIndex('index').isUnique());
  970. assertEquals('value', store.getIndex('index').getKeyPath());
  971. return store.add({key: 'someKey', value: 'lookUpThis'})
  972. .addCallback(function() { return db; });
  973. };
  974. var readAndAssertAboutData = function(db) {
  975. var index =
  976. db.createTransaction(['store']).objectStore('store').getIndex('index');
  977. var promises = [
  978. index.get('lookUpThis').addCallback(function(result) {
  979. assertNotUndefined(result);
  980. assertEquals('someKey', result.key);
  981. assertEquals('lookUpThis', result.value);
  982. }),
  983. index.getKey('lookUpThis').addCallback(function(result) {
  984. assertNotUndefined(result);
  985. assertEquals('someKey', result);
  986. })
  987. ];
  988. return goog.Promise.all(promises).then(function() { return db; });
  989. };
  990. return globalDb.branch()
  991. .addCallback(addStoreWithIndex)
  992. .addCallback(addData)
  993. .addCallback(readAndAssertAboutData);
  994. }
  995. function testGetMultipleRecordsFromIndex() {
  996. if (!idbSupported) {
  997. return;
  998. }
  999. var addData = function(db) {
  1000. var addTx = db.createTransaction(['store'], TransactionMode.READ_WRITE);
  1001. addTx.objectStore('store').add({key: '1', value: 'a'});
  1002. addTx.objectStore('store').add({key: '2', value: 'a'});
  1003. addTx.objectStore('store').add({key: '3', value: 'b'});
  1004. return addTx.wait();
  1005. };
  1006. var readData = function(db) {
  1007. var index =
  1008. db.createTransaction(['store']).objectStore('store').getIndex('index');
  1009. var promises = [];
  1010. promises.push(index.getAll().addCallback(function(results) {
  1011. assertNotUndefined(results);
  1012. assertEquals(3, results.length);
  1013. }));
  1014. promises.push(index.getAll('a').addCallback(function(results) {
  1015. assertNotUndefined(results);
  1016. assertEquals(2, results.length);
  1017. }));
  1018. promises.push(index.getAllKeys().addCallback(function(results) {
  1019. assertNotUndefined(results);
  1020. assertEquals(3, results.length);
  1021. assertArrayEquals(['1', '2', '3'], results);
  1022. }));
  1023. promises.push(index.getAllKeys('b').addCallback(function(results) {
  1024. assertNotUndefined(results);
  1025. assertEquals(1, results.length);
  1026. assertArrayEquals(['3'], results);
  1027. }));
  1028. return goog.Promise.all(promises).then(function() { return db; });
  1029. };
  1030. return globalDb.branch()
  1031. .addCallback(addStoreWithIndex)
  1032. .addCallback(addData)
  1033. .addCallback(readData);
  1034. }
  1035. function testUniqueIndex() {
  1036. if (!idbSupported) {
  1037. return;
  1038. }
  1039. var storeDuplicatesToUniqueIndex = function(db) {
  1040. var tx = db.createTransaction(['store'], TransactionMode.READ_WRITE);
  1041. assertTrue(tx.objectStore('store').getIndex('index').isUnique());
  1042. tx.objectStore('store').add({key: '1', value: 'a'});
  1043. tx.objectStore('store').add({key: '2', value: 'a'});
  1044. return transactionToPromise(db, tx).then(
  1045. function() {
  1046. fail('Expected transaction violating unique constraint to fail');
  1047. },
  1048. function(ev) {
  1049. // expected
  1050. assertEquals(
  1051. goog.db.Error.ErrorName.CONSTRAINT_ERR, ev.target.getName());
  1052. });
  1053. };
  1054. return globalDb.branch()
  1055. .addCallback(function(db) {
  1056. return incrementVersion(db, function(ev, db, tx) {
  1057. var store = db.createObjectStore('store', {keyPath: 'key'});
  1058. store.createIndex('index', 'value', {unique: true});
  1059. });
  1060. })
  1061. .addCallback(storeDuplicatesToUniqueIndex);
  1062. }
  1063. function testDeleteDatabase() {
  1064. if (!idbSupported) {
  1065. return;
  1066. }
  1067. return globalDb.branch()
  1068. .addCallback(addStore)
  1069. .addCallback(function(db) {
  1070. db.close();
  1071. return goog.db.deleteDatabase(dbName, function() {
  1072. fail('didn\'t expect deleteDatabase to be blocked');
  1073. });
  1074. })
  1075. .addCallback(openDatabase)
  1076. .addCallback(assertStoreDoesntExist);
  1077. }
  1078. function testDeleteDatabaseIsBlocked() {
  1079. if (!idbSupported) {
  1080. return;
  1081. }
  1082. var wasBlocked = false;
  1083. return globalDb.branch()
  1084. .addCallback(addStore)
  1085. .addCallback(function(db) {
  1086. db.close();
  1087. // Get a fresh connection, without any events registered on globalDb.
  1088. return goog.db.openDatabase(dbName);
  1089. })
  1090. .addCallback(function(db) {
  1091. dbsToClose.push(db);
  1092. return goog.db.deleteDatabase(dbName, function(ev) {
  1093. wasBlocked = true;
  1094. db.close();
  1095. });
  1096. })
  1097. .addCallback(function() {
  1098. assertTrue(wasBlocked);
  1099. return openDatabase();
  1100. })
  1101. .addCallback(assertStoreDoesntExist);
  1102. }
  1103. function testBlockedDeleteDatabaseWithVersionChangeEvent() {
  1104. if (!idbSupported) {
  1105. return;
  1106. }
  1107. var gotVersionChange = false;
  1108. return globalDb.branch()
  1109. .addCallback(addStore)
  1110. .addCallback(function(db) {
  1111. db.close();
  1112. // Get a fresh connection, without any events registered on globalDb.
  1113. return goog.db.openDatabase(dbName);
  1114. })
  1115. .addCallback(function(db) {
  1116. dbsToClose.push(db);
  1117. goog.events.listen(
  1118. db, goog.db.IndexedDb.EventType.VERSION_CHANGE, function(ev) {
  1119. gotVersionChange = true;
  1120. db.close();
  1121. });
  1122. return goog.db.deleteDatabase(dbName);
  1123. })
  1124. .addCallback(function() {
  1125. assertTrue(gotVersionChange);
  1126. return openDatabase();
  1127. })
  1128. .addCallback(assertStoreDoesntExist);
  1129. }
  1130. function testDeleteNonExistentDatabase() {
  1131. if (!idbSupported) {
  1132. return;
  1133. }
  1134. // Deleting non-existent db is a no-op. Shall not throw anything.
  1135. return globalDb.branch().addCallback(function(db) {
  1136. db.close();
  1137. return goog.db.deleteDatabase('non-existent-db');
  1138. });
  1139. }
  1140. function testObjectStoreCountAll() {
  1141. if (!idbSupported) {
  1142. return;
  1143. }
  1144. var values = ['1', '2', '3', '4'];
  1145. var keys = ['a', 'b', 'c', 'd'];
  1146. var addData = goog.partial(populateStore, values, keys);
  1147. return globalDb.branch()
  1148. .addCallback(addStore)
  1149. .addCallback(addData)
  1150. .addCallback(function(db) {
  1151. var tx = db.createTransaction(['store']);
  1152. return tx.objectStore('store').count().addCallback(function(count) {
  1153. assertEquals(values.length, count);
  1154. });
  1155. });
  1156. }
  1157. function testObjectStoreCountSome() {
  1158. if (!idbSupported) {
  1159. return;
  1160. }
  1161. var values = ['1', '2', '3', '4'];
  1162. var keys = ['a', 'b', 'c', 'd'];
  1163. var addData = goog.partial(populateStore, values, keys);
  1164. var countData = function(db) {
  1165. var tx = db.createTransaction(['store']);
  1166. return tx.objectStore('store')
  1167. .count(goog.db.KeyRange.bound('b', 'c'))
  1168. .addCallback(function(count) { assertEquals(2, count); });
  1169. };
  1170. return globalDb.branch()
  1171. .addCallback(addStore)
  1172. .addCallback(addData)
  1173. .addCallback(countData);
  1174. }
  1175. function testIndexCursorGet() {
  1176. if (!idbSupported) {
  1177. return;
  1178. }
  1179. var values = ['1', '2', '3', '4'];
  1180. var keys = ['a', 'b', 'c', 'd'];
  1181. var addData = goog.partial(populateStoreWithObjects, values, keys);
  1182. var valuesResult = [];
  1183. var keysResult = [];
  1184. // Open the cursor over range ['b', 'c'], move in backwards direction.
  1185. var walkBackwardsOverCursor = function(db) {
  1186. var cursorTx = db.createTransaction(['store']);
  1187. var index = cursorTx.objectStore('store').getIndex('index');
  1188. var values = [];
  1189. var keys = [];
  1190. var cursor = index.openCursor(
  1191. goog.db.KeyRange.bound('2', '3'), goog.db.Cursor.Direction.PREV);
  1192. var cursorFinished = forEachRecord(cursor, function() {
  1193. valuesResult.push(cursor.getValue()['value']);
  1194. keysResult.push(cursor.getValue()['key']);
  1195. cursor.next();
  1196. });
  1197. return goog.Promise.all([cursorFinished, cursorTx.wait()]).then(function() {
  1198. return db
  1199. });
  1200. };
  1201. return globalDb.branch()
  1202. .addCallbacks(addStoreWithIndex)
  1203. .addCallback(addData)
  1204. .addCallback(walkBackwardsOverCursor)
  1205. .addCallback(function(db) {
  1206. assertArrayEquals(['3', '2'], valuesResult);
  1207. assertArrayEquals(['c', 'b'], keysResult);
  1208. });
  1209. }
  1210. function testIndexCursorReplace() {
  1211. if (!idbSupported) {
  1212. return;
  1213. }
  1214. var values = ['1', '2', '3', '4'];
  1215. var keys = ['a', 'b', 'c', 'd'];
  1216. var addData = goog.partial(populateStoreWithObjects, values, keys);
  1217. var valuesResult = [];
  1218. var keysResult = [];
  1219. // Store should contain ['1', '2', '5', '4'] after replacement.
  1220. var checkStore = goog.partial(assertStoreObjectValues, ['1', '2', '5', '4']);
  1221. // Use a bounded cursor for ['3', '4') to update value '3' -> '5'.
  1222. var openCursorAndReplace = function(db) {
  1223. var cursorTx = db.createTransaction(['store'], TransactionMode.READ_WRITE);
  1224. var index = cursorTx.objectStore('store').getIndex('index');
  1225. var cursor =
  1226. index.openCursor(goog.db.KeyRange.bound('3', '4', false, true));
  1227. var cursorFinished = forEachRecord(cursor, function() {
  1228. assertEquals('3', cursor.getValue()['value']);
  1229. return cursor.update({'key': cursor.getValue()['key'], 'value': '5'})
  1230. .addCallback(function() { cursor.next(); });
  1231. });
  1232. return goog.Promise.all([cursorFinished, cursorTx.wait()])
  1233. .then(function(results) { return db; });
  1234. };
  1235. // Setup and execute test case.
  1236. return globalDb.branch()
  1237. .addCallback(addStoreWithIndex)
  1238. .addCallback(addData)
  1239. .addCallback(openCursorAndReplace)
  1240. .addCallback(checkStore);
  1241. }
  1242. function testIndexCursorRemove() {
  1243. if (!idbSupported) {
  1244. return;
  1245. }
  1246. var values = ['1', '2', '3', '4'];
  1247. var keys = ['a', 'b', 'c', 'd'];
  1248. var addData = goog.partial(populateStoreWithObjects, values, keys);
  1249. // Store should contain ['1', '2'] after removing elements.
  1250. var checkStore = goog.partial(assertStoreObjectValues, ['1', '2']);
  1251. // Use a bounded cursor for ('2', ...) to remove '3', '4'.
  1252. var openCursorAndRemove = function(db) {
  1253. var cursorTx = db.createTransaction(['store'], TransactionMode.READ_WRITE);
  1254. var store = cursorTx.objectStore('store');
  1255. var index = store.getIndex('index');
  1256. var cursor = index.openCursor(goog.db.KeyRange.lowerBound('2', true));
  1257. var cursorFinished = forEachRecord(cursor, function() {
  1258. return cursor.remove('5').addCallback(function() { cursor.next(); });
  1259. });
  1260. return goog.Promise.all([cursorFinished, cursorTx.wait()])
  1261. .then(function(results) { return db; });
  1262. };
  1263. // Setup and execute test case.
  1264. return globalDb.branch()
  1265. .addCallback(addStoreWithIndex)
  1266. .addCallback(addData)
  1267. .addCallback(openCursorAndRemove)
  1268. .addCallback(checkStore);
  1269. }
  1270. function testCanWaitForTransactionToComplete() {
  1271. if (!idbSupported) {
  1272. return;
  1273. }
  1274. return globalDb.branch().addCallback(addStore).addCallback(function(db) {
  1275. var tx = db.createTransaction(['store'], TransactionMode.READ_WRITE);
  1276. tx.objectStore('store').add({key: 'hi', value: 'something'}, 'stuff');
  1277. return tx.wait();
  1278. });
  1279. }
  1280. function testWaitingOnTransactionThatHasAnError() {
  1281. if (!idbSupported) {
  1282. return;
  1283. }
  1284. return globalDb.branch()
  1285. .addCallback(function(db) {
  1286. return incrementVersion(db, function(ev, db, tx) {
  1287. var store = db.createObjectStore('store', {keyPath: 'key'});
  1288. store.createIndex('index', 'value', {unique: true});
  1289. });
  1290. })
  1291. .addCallback(function(db) {
  1292. var tx = db.createTransaction(['store'], TransactionMode.READ_WRITE);
  1293. assertTrue(tx.objectStore('store').getIndex('index').isUnique());
  1294. tx.objectStore('store').add({key: '1', value: 'a'});
  1295. tx.objectStore('store').add({key: '2', value: 'a'});
  1296. return transactionToPromise(db, tx).then(
  1297. function() { fail('expected transaction to fail'); },
  1298. function(ev) {
  1299. // expected
  1300. assertEquals(
  1301. goog.db.Error.ErrorName.CONSTRAINT_ERR, ev.target.getName());
  1302. });
  1303. });
  1304. }
  1305. function testWaitingOnAnAbortedTransaction() {
  1306. if (!idbSupported) {
  1307. return;
  1308. }
  1309. return globalDb.addCallback(addStore).addCallback(function(db) {
  1310. var tx = db.createTransaction(['store'], TransactionMode.READ_WRITE);
  1311. var waiting = tx.wait().then(
  1312. function() { fail('Wait result should have failed'); },
  1313. function(e) {
  1314. assertEquals(goog.db.Error.ErrorName.ABORT_ERR, e.getName());
  1315. });
  1316. tx.abort();
  1317. return waiting;
  1318. });
  1319. }