12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487 |
- // Copyright 2012 The Closure Library Authors. All Rights Reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS-IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- goog.provide('goog.dbTest');
- goog.setTestOnly('goog.dbTest');
- goog.require('goog.Disposable');
- goog.require('goog.Promise');
- goog.require('goog.array');
- goog.require('goog.db');
- goog.require('goog.db.Cursor');
- goog.require('goog.db.Error');
- goog.require('goog.db.IndexedDb');
- goog.require('goog.db.KeyRange');
- goog.require('goog.db.Transaction');
- goog.require('goog.events');
- goog.require('goog.object');
- goog.require('goog.testing.PropertyReplacer');
- goog.require('goog.testing.asserts');
- goog.require('goog.testing.jsunit');
- goog.require('goog.userAgent.product');
- var idbSupported = goog.userAgent.product.CHROME;
- var dbName;
- var dbBaseName = 'testDb';
- var globalDb = null;
- var dbsToClose = [];
- var propertyReplacer;
- var baseVersion = 1;
- var dbVersion = 1;
- var TransactionMode = goog.db.Transaction.TransactionMode;
- var EventTypes = goog.db.Transaction.EventTypes;
- function openDatabase() {
- return goog.db.openDatabase(dbName).addCallback(function(db) {
- dbsToClose.push(db);
- });
- }
- function incrementVersion(db, onUpgradeNeeded) {
- db.close();
- var onBlocked = function(ev) {
- fail('Upgrade to version ' + dbVersion + ' is blocked.');
- };
- return goog.db.openDatabase(dbName, ++dbVersion, onUpgradeNeeded, onBlocked)
- .addCallback(function(db) {
- dbsToClose.push(db);
- assertEquals(dbVersion, db.getVersion());
- });
- }
- function addStore(db) {
- return incrementVersion(
- db, function(ev, db, tx) { db.createObjectStore('store'); });
- }
- function addStoreWithIndex(db) {
- return incrementVersion(db, function(ev, db, tx) {
- var store = db.createObjectStore('store', {keyPath: 'key'});
- store.createIndex('index', 'value');
- });
- }
- function populateStore(values, keys, db) {
- var putTx = db.createTransaction(['store'], TransactionMode.READ_WRITE);
- var store = putTx.objectStore('store');
- for (var i = 0; i < values.length; ++i) {
- store.put(values[i], keys[i]);
- }
- return putTx.wait();
- }
- function populateStoreWithObjects(values, keys, db) {
- var putTx = db.createTransaction(['store'], TransactionMode.READ_WRITE);
- var store = putTx.objectStore('store');
- goog.array.forEach(values, function(value, index) {
- store.put({'key': keys[index], 'value': value});
- });
- return putTx.wait();
- }
- function assertStoreValues(values, db) {
- var assertStoreTx = db.createTransaction(['store']);
- assertStoreTx.objectStore('store').getAll().addCallback(function(results) {
- assertSameElements(values, results);
- });
- }
- function assertStoreObjectValues(values, db) {
- var assertStoreTx = db.createTransaction(['store']);
- assertStoreTx.objectStore('store').getAll().addCallback(function(results) {
- var retrievedValues =
- goog.array.map(results, function(result) { return result['value']; });
- assertSameElements(values, retrievedValues);
- });
- }
- function assertStoreValuesAndCursorsDisposed(values, cursors, db) {
- var assertStoreTx = db.createTransaction(['store']);
- assertStoreTx.objectStore('store').getAll().addCallback(function(results) {
- assertSameElements(values, results);
- assertTrue(cursors.length > 0);
- goog.array.forEach(cursors, function(elem, index, array) {
- console.log(elem);
- assertTrue(
- 'array[' + index + '] (' + elem + ') is not disposed',
- goog.Disposable.isDisposed(elem));
- });
- });
- }
- function assertStoreDoesntExist(db) {
- try {
- db.createTransaction(['store']);
- fail('Create transaction with a non-existent store should have failed.');
- } catch (e) {
- // expected
- assertEquals(e.getName(), goog.db.Error.ErrorName.NOT_FOUND_ERR);
- }
- }
- function transactionToPromise(db, trx) {
- return new goog.Promise(function(resolve, reject) {
- goog.events.listen(trx, EventTypes.ERROR, reject);
- goog.events.listen(trx, EventTypes.COMPLETE, function() { resolve(db); });
- });
- }
- // Calls onRecordReady each time that a new record can be read by the
- // cursor with cursor.next(). Returns a promise that resolves or rejects
- // based on when the cursor is complete or errors out. If onRecordReady
- // returns a promise that promises is also waited on before the returned
- // promise resolves.
- function forEachRecord(cursor, onRecordReady) {
- var promises = [];
- return new goog
- .Promise(function(resolve, reject) {
- var key = goog.events.listen(
- cursor, goog.db.Cursor.EventType.NEW_DATA, function() {
- var result = onRecordReady();
- if (result && ('then' in result)) {
- promises.push(result);
- }
- });
- goog.events.listenOnce(
- cursor,
- [goog.db.Cursor.EventType.COMPLETE, goog.db.Cursor.EventType.ERROR],
- function(evt) {
- goog.events.unlistenByKey(key);
- if (evt.type == goog.db.Cursor.EventType.COMPLETE) {
- resolve();
- } else {
- reject(evt);
- }
- });
- })
- .then(function() { return goog.Promise.all(promises); });
- }
- function failOnErrorEvent(ev) {
- fail(ev.target.message);
- }
- function setUpPage() {
- propertyReplacer = new goog.testing.PropertyReplacer();
- }
- function setUp() {
- if (!idbSupported) {
- return;
- }
- // Always use a clean database by generating a new database name.
- dbName = dbBaseName + Date.now().toString();
- globalDb = openDatabase();
- }
- function tearDown() {
- for (var i = 0; i < dbsToClose.length; i++) {
- dbsToClose[i].close();
- }
- dbsToClose = [];
- propertyReplacer.reset();
- }
- function testDatabaseOpened() {
- if (!idbSupported) {
- return;
- }
- assertNotNull(globalDb);
- return globalDb.branch().addCallback(function(db) {
- assertTrue(db.isOpen());
- });
- }
- function testOpenWithNewVersion() {
- if (!idbSupported) {
- return;
- }
- var upgradeNeeded = false;
- return globalDb.branch()
- .addCallback(function(db) {
- assertEquals(baseVersion, db.getVersion());
- return incrementVersion(
- db, function(ev, db, tx) { upgradeNeeded = true; });
- })
- .addCallback(function(db) { assertTrue(upgradeNeeded); });
- }
- function testManipulateObjectStores() {
- if (!idbSupported) {
- return;
- }
- return globalDb.branch()
- .addCallback(function(db) {
- assertEquals(baseVersion, db.getVersion());
- return incrementVersion(db, function(ev, db, tx) {
- db.createObjectStore('basicStore');
- db.createObjectStore('keyPathStore', {keyPath: 'keyGoesHere'});
- db.createObjectStore('autoIncrementStore', {autoIncrement: true});
- });
- })
- .addCallback(function(db) {
- var storeNames = db.getObjectStoreNames();
- assertEquals(3, storeNames.length);
- assertTrue(storeNames.contains('basicStore'));
- assertTrue(storeNames.contains('keyPathStore'));
- assertTrue(storeNames.contains('autoIncrementStore'));
- return incrementVersion(
- db, function(ev, db, tx) { db.deleteObjectStore('basicStore'); });
- })
- .addCallback(function(db) {
- var storeNames = db.getObjectStoreNames();
- assertEquals(2, storeNames.length);
- assertFalse(storeNames.contains('basicStore'));
- assertTrue(storeNames.contains('keyPathStore'));
- assertTrue(storeNames.contains('autoIncrementStore'));
- });
- }
- function testBadObjectStoreManipulation() {
- if (!idbSupported) {
- return;
- }
- var expectedCode = goog.db.Error.ErrorName.INVALID_STATE_ERR;
- return globalDb.branch()
- .addCallback(function(db) {
- try {
- db.createObjectStore('diediedie');
- fail('Create object store outside transaction should have failed.');
- } catch (err) {
- // expected
- assertEquals(expectedCode, err.getName());
- }
- })
- .addCallback(addStore)
- .addCallback(function(db) {
- try {
- db.deleteObjectStore('store');
- fail('Delete object store outside transaction should have failed.');
- } catch (err) {
- // expected
- assertEquals(expectedCode, err.getName());
- }
- })
- .addCallback(function(db) {
- return incrementVersion(db, function(ev, db, tx) {
- try {
- db.deleteObjectStore('diediedie');
- fail('Delete non-existent store should have failed.');
- } catch (err) {
- // expected
- assertEquals(goog.db.Error.ErrorName.NOT_FOUND_ERR, err.getName());
- }
- });
- });
- }
- function testGetNonExistentObjectStore() {
- if (!idbSupported) {
- return;
- }
- return globalDb.branch().addCallback(addStore).addCallback(function(db) {
- var tx = db.createTransaction(['store']);
- try {
- tx.objectStore('diediedie');
- fail('getting non-existent object store should have failed');
- } catch (err) {
- assertEquals(goog.db.Error.ErrorName.NOT_FOUND_ERR, err.getName());
- }
- });
- }
- function testCreateTransaction() {
- if (!idbSupported) {
- return;
- }
- return globalDb.branch().addCallback(addStore).addCallback(function(db) {
- var tx = db.createTransaction(['store']);
- assertEquals('mode not READ_ONLY', TransactionMode.READ_ONLY, tx.getMode());
- tx = db.createTransaction(
- ['store'], goog.db.Transaction.TransactionMode.READ_WRITE);
- assertEquals(
- 'mode not READ_WRITE', TransactionMode.READ_WRITE, tx.getMode());
- });
- }
- function testPutRecord() {
- if (!idbSupported) {
- return;
- }
- return globalDb.branch()
- .addCallback(addStore)
- .addCallback(function(db) {
- var initialPutTx =
- db.createTransaction(['store'], TransactionMode.READ_WRITE);
- var putOperation = initialPutTx.objectStore('store').put(
- {key: 'initial', value: 'value1'}, 'putKey');
- putOperation.addCallback(function(key) {
- assertEquals('putKey', key);
- });
- return transactionToPromise(db, initialPutTx);
- })
- .addCallback(function(db) {
- var checkResultsTx = db.createTransaction(['store']);
- var getOperation = checkResultsTx.objectStore('store').get('putKey');
- getOperation.addCallback(function(result) {
- assertEquals('initial', result.key);
- assertEquals('value1', result.value);
- });
- return transactionToPromise(db, checkResultsTx);
- })
- .addCallback(function(db) {
- var overwriteTx =
- db.createTransaction(['store'], TransactionMode.READ_WRITE);
- var putOperation = overwriteTx.objectStore('store').put(
- {key: 'overwritten', value: 'value2'}, 'putKey');
- putOperation.addCallback(function(key) {
- assertEquals('putKey', key);
- });
- return transactionToPromise(db, overwriteTx);
- })
- .addCallback(function(db) {
- var checkOverwriteTx = db.createTransaction(['store']);
- checkOverwriteTx.objectStore('store').get('putKey').addCallback(
- function(result) {
- // this is guaranteed to run before the COMPLETE event fires on
- // the transaction
- assertEquals('overwritten', result.key);
- assertEquals('value2', result.value);
- });
- return transactionToPromise(db, checkOverwriteTx);
- });
- }
- function testAddRecord() {
- if (!idbSupported) {
- return;
- }
- return globalDb.branch()
- .addCallback(addStore)
- .addCallback(function(db) {
- var initialAddTx =
- db.createTransaction(['store'], TransactionMode.READ_WRITE);
- var addOperation = initialAddTx.objectStore('store').add(
- {key: 'hi', value: 'something'}, 'stuff');
- addOperation.addCallback(function(key) {
- assertEquals('stuff', key);
- });
- return transactionToPromise(db, initialAddTx);
- })
- .addCallback(function(db) {
- var successfulAddTx = db.createTransaction(['store']);
- var getOperation = successfulAddTx.objectStore('store').get('stuff');
- getOperation.addCallback(function(result) {
- assertEquals('hi', result.key);
- assertEquals('something', result.value);
- });
- return transactionToPromise(db, successfulAddTx);
- })
- .addCallback(function(db) {
- var addOverwriteTx =
- db.createTransaction(['store'], TransactionMode.READ_WRITE);
- addOverwriteTx.objectStore('store')
- .add({key: 'bye', value: 'nothing'}, 'stuff')
- .addErrback(function(err) {
- // expected
- assertEquals(
- goog.db.Error.ErrorName.CONSTRAINT_ERR, err.getName());
- });
- return transactionToPromise(db, addOverwriteTx)
- .then(
- function() {
- fail('adding existing record should not have succeeded');
- },
- function(ev) {
- // expected
- assertEquals(
- goog.db.Error.ErrorName.CONSTRAINT_ERR,
- ev.target.getName());
- });
- });
- }
- function testPutRecordKeyPathStore() {
- if (!idbSupported) {
- return;
- }
- return globalDb.branch()
- .addCallback(function(db) {
- return incrementVersion(db, function(ev, db, tx) {
- db.createObjectStore('keyStore', {keyPath: 'key'});
- });
- })
- .addCallback(function(db) {
- var putTx =
- db.createTransaction(['keyStore'], TransactionMode.READ_WRITE);
- var putOperation =
- putTx.objectStore('keyStore').put({key: 'hi', value: 'something'});
- putOperation.addCallback(function(key) {
- assertEquals('hi', key);
- });
- return transactionToPromise(db, putTx);
- })
- .addCallback(function(db) {
- var checkResultsTx = db.createTransaction(['keyStore']);
- checkResultsTx.objectStore('keyStore')
- .get('hi')
- .addCallback(function(result) {
- assertNotUndefined(result);
- assertEquals('hi', result.key);
- assertEquals('something', result.value);
- });
- return transactionToPromise(db, checkResultsTx);
- });
- }
- function testPutBadRecordKeyPathStore() {
- if (!idbSupported) {
- return;
- }
- return globalDb.branch()
- .addCallback(function(db) {
- return incrementVersion(db, function(ev, db, tx) {
- db.createObjectStore('keyStore', {keyPath: 'key'});
- });
- })
- .addCallback(function(db) {
- var badTx =
- db.createTransaction(['keyStore'], TransactionMode.READ_WRITE);
- return badTx.objectStore('keyStore')
- .put({key: 'diedie', value: 'anything'}, 'badKey')
- .then(
- function() {
- fail('inserting with explicit key should have failed');
- },
- function(err) {
- // expected
- assertEquals(goog.db.Error.ErrorName.DATA_ERR, err.getName());
- });
- });
- }
- function testPutRecordAutoIncrementStore() {
- if (!idbSupported) {
- return;
- }
- return globalDb.branch()
- .addCallback(function(db) {
- return incrementVersion(db, function(ev, db, tx) {
- db.createObjectStore('aiStore', {autoIncrement: true});
- });
- })
- .addCallback(function(db) {
- var tx = db.createTransaction(['aiStore'], TransactionMode.READ_WRITE);
- var putOperation1 = tx.objectStore('aiStore').put('1');
- var putOperation2 = tx.objectStore('aiStore').put('2');
- var putOperation3 = tx.objectStore('aiStore').put('3');
- putOperation1.addCallback(function(key) {
- assertNotUndefined(key);
- });
- putOperation2.addCallback(function(key) {
- assertNotUndefined(key);
- });
- putOperation3.addCallback(function(key) {
- assertNotUndefined(key);
- });
- return transactionToPromise(db, tx);
- })
- .addCallback(function(db) {
- var tx = db.createTransaction(['aiStore']);
- var getAllOperation = tx.objectStore('aiStore').getAll();
- return getAllOperation.addCallback(function(results) {
- assertEquals(3, results.length);
- // only checking to see if the results are included because the keys
- // are not specified
- assertNotEquals(-1, results.indexOf('1'));
- assertNotEquals(-1, results.indexOf('2'));
- assertNotEquals(-1, results.indexOf('3'));
- });
- });
- }
- function testPutRecordKeyPathAndAutoIncrementStore() {
- if (!idbSupported) {
- return;
- }
- return globalDb.branch()
- .addCallback(function(db) {
- return incrementVersion(db, function(ev, db, tx) {
- db.createObjectStore(
- 'hybridStore', {keyPath: 'key', autoIncrement: true});
- });
- })
- .addCallback(function(db) {
- var tx =
- db.createTransaction(['hybridStore'], TransactionMode.READ_WRITE);
- var putOperation =
- tx.objectStore('hybridStore').put({value: 'whatever'});
- putOperation.addCallback(function(key) {
- assertNotUndefined(key);
- });
- return putOperation.addCallback(function() {
- return db;
- });
- })
- .addCallback(function(db) {
- var tx = db.createTransaction(['hybridStore']);
- return tx.objectStore('hybridStore').getAll().then(function(results) {
- assertEquals(1, results.length);
- assertEquals('whatever', results[0].value);
- assertNotUndefined(results[0].key);
- });
- });
- }
- function testPutIllegalRecords() {
- if (!idbSupported) {
- return;
- }
- return globalDb.branch().addCallback(addStore).addCallback(function(db) {
- var tx = db.createTransaction(['store'], TransactionMode.READ_WRITE);
- var promises = [];
- var badKeyFail = function(keyKind) {
- return function() {
- return fail('putting with ' + keyKind + ' key should have failed');
- }
- };
- var assertExpectedError = function(err) {
- assertEquals(goog.db.Error.ErrorName.DATA_ERR, err.getName());
- };
- promises.push(
- tx.objectStore('store')
- .put('death', null)
- .then(badKeyFail('null'), assertExpectedError));
- promises.push(
- tx.objectStore('store')
- .put('death', NaN)
- .then(badKeyFail('NaN'), assertExpectedError));
- promises.push(
- tx.objectStore('store')
- .put('death', undefined)
- .then(badKeyFail('undefined'), assertExpectedError));
- return goog.Promise.all(promises);
- });
- }
- function testPutIllegalRecordsWithIndex() {
- if (!idbSupported) {
- return;
- }
- return globalDb.branch()
- .addCallback(addStoreWithIndex)
- .addCallback(function(db) {
- var tx = db.createTransaction(['store'], TransactionMode.READ_WRITE);
- var promises = [];
- var badKeyFail = function(keyKind) {
- return function() {
- fail('putting with ' + keyKind + ' key should have failed');
- }
- };
- var assertExpectedError = function(err) {
- // expected
- assertEquals(goog.db.Error.ErrorName.DATA_ERR, err.getName());
- };
- promises.push(
- tx.objectStore('store')
- .put({value: 'diediedie', key: null})
- .then(badKeyFail('null'), assertExpectedError));
- promises.push(
- tx.objectStore('store')
- .put({value: 'dietodeath', key: NaN})
- .then(badKeyFail('NaN'), assertExpectedError));
- promises.push(
- tx.objectStore('store')
- .put({value: 'dietodeath', key: undefined})
- .then(badKeyFail('undefined'), assertExpectedError));
- return goog.Promise.all(promises);
- });
- }
- function testDeleteRecord() {
- if (!idbSupported) {
- return;
- }
- var db;
- return globalDb.branch()
- .addCallback(addStore)
- .addCallback(function(openedDb) {
- db = openedDb;
- return db.createTransaction(['store'], TransactionMode.READ_WRITE)
- .objectStore('store')
- .put({key: 'hi', value: 'something'}, 'stuff');
- })
- .addCallback(function() {
- return db.createTransaction(['store'], TransactionMode.READ_WRITE)
- .objectStore('store')
- .remove('stuff');
- })
- .addCallback(function() {
- return db.createTransaction(['store']).objectStore('store').get(
- 'stuff');
- })
- .addCallback(function(result) { assertUndefined(result); });
- }
- function testDeleteRange() {
- if (!idbSupported) {
- return;
- }
- var values = ['1', '2', '3'];
- var keys = ['a', 'b', 'c'];
- var addData = goog.partial(populateStore, values, keys);
- var checkStore = goog.partial(assertStoreValues, ['1']);
- return globalDb.branch()
- .addCallback(addStore)
- .addCallback(addData)
- .addCallback(function(db) {
- return db.createTransaction(['store'], TransactionMode.READ_WRITE)
- .objectStore('store')
- .remove(goog.db.KeyRange.bound('b', 'c'))
- .then(function() {
- return db;
- });
- })
- .addCallback(checkStore);
- }
- function testGetAll() {
- if (!idbSupported) {
- return;
- }
- var values = ['1', '2', '3'];
- var keys = ['a', 'b', 'c'];
- var addData = goog.partial(populateStore, values, keys);
- var checkStore = goog.partial(assertStoreValues, values);
- return globalDb.branch()
- .addCallback(addStore)
- .addCallback(addData)
- .addCallback(checkStore);
- }
- function testGetAllFreesCursor() {
- if (!idbSupported) {
- return;
- }
- var values = ['1', '2', '3'];
- var keys = ['a', 'b', 'c'];
- var addData = goog.partial(populateStore, values, keys);
- var origCursor = goog.db.Cursor;
- var cursors = [];
- /** @constructor */
- var testCursor = function() {
- origCursor.call(this);
- cursors.push(this);
- };
- goog.object.extend(testCursor, origCursor);
- // We don't use goog.inherits here because we are going to be overwriting
- // goog.db.Cursor and we don't want a new "base" method as
- // goog.db.Cursor.base(this, 'constructor') would be a call to
- // testCursor.base(this, 'constructor') which would be goog.db.Cursor and be
- // an infinite loop.
- testCursor.prototype = origCursor.prototype;
- propertyReplacer.replace(goog.db, 'Cursor', testCursor);
- var checkStoreAndCursorDisposed =
- goog.partial(assertStoreValuesAndCursorsDisposed, values, cursors);
- return globalDb.branch()
- .addCallback(addStore)
- .addCallback(addData)
- .addCallback(checkStoreAndCursorDisposed);
- }
- function testObjectStoreCursorGet() {
- if (!idbSupported) {
- return;
- }
- var values = ['1', '2', '3', '4'];
- var keys = ['a', 'b', 'c', 'd'];
- var addData = goog.partial(populateStore, values, keys);
- var db;
- var resultValues = [];
- var resultKeys = [];
- // Open the cursor over range ['b', 'c'], move in backwards direction.
- return globalDb.branch()
- .addCallback(addStore)
- .addCallback(addData)
- .addCallback(function(theDb) {
- db = theDb;
- var cursorTx = db.createTransaction(['store']);
- var store = cursorTx.objectStore('store');
- var cursor = store.openCursor(
- goog.db.KeyRange.bound('b', 'c'), goog.db.Cursor.Direction.PREV);
- var whenCursorComplete = forEachRecord(cursor, function() {
- resultValues.push(cursor.getValue());
- resultKeys.push(cursor.getKey());
- cursor.next();
- });
- return goog.Promise.all([cursorTx.wait(), whenCursorComplete]);
- })
- .addCallback(function() {
- assertArrayEquals(['3', '2'], resultValues);
- assertArrayEquals(['c', 'b'], resultKeys);
- });
- }
- function testObjectStoreCursorReplace() {
- if (!idbSupported) {
- return;
- }
- var values = ['1', '2', '3', '4'];
- var keys = ['a', 'b', 'c', 'd'];
- var addData = goog.partial(populateStore, values, keys);
- // Store should contain ['1', '2', '5', '4'] after replacement.
- var checkStore = goog.partial(assertStoreValues, ['1', '2', '5', '4']);
- // Use a bounded cursor for ('b', 'c'] to update value '3' -> '5'.
- var openCursorAndReplace = function(db) {
- var cursorTx = db.createTransaction(['store'], TransactionMode.READ_WRITE);
- var store = cursorTx.objectStore('store');
- var cursor = store.openCursor(goog.db.KeyRange.bound('b', 'c', true));
- var whenCursorComplete = forEachRecord(cursor, function() {
- assertEquals('3', cursor.getValue());
- return cursor.update('5').addCallback(function() { cursor.next(); });
- });
- return goog.Promise.all([cursorTx.wait(), whenCursorComplete])
- .then(function() { return db; });
- };
- return globalDb.branch()
- .addCallback(addStore)
- .addCallback(addData)
- .addCallback(openCursorAndReplace)
- .addCallback(checkStore);
- }
- function testObjectStoreCursorRemove() {
- if (!idbSupported) {
- return;
- }
- var values = ['1', '2', '3', '4'];
- var keys = ['a', 'b', 'c', 'd'];
- var addData = goog.partial(populateStore, values, keys);
- // Store should contain ['1', '2'] after removing elements.
- var checkStore = goog.partial(assertStoreValues, ['1', '2']);
- // Use a bounded cursor for ('b', ...) to remove '3', '4'.
- var openCursorAndRemove = function(db) {
- var cursorTx = db.createTransaction(['store'], TransactionMode.READ_WRITE);
- var store = cursorTx.objectStore('store');
- var cursor = store.openCursor(goog.db.KeyRange.lowerBound('b', true));
- var whenCursorComplete = forEachRecord(cursor, function() {
- return cursor.remove('5').addCallback(function() { cursor.next(); });
- });
- return goog.Promise.all([cursorTx.wait(), whenCursorComplete])
- .then(function(results) { return db; });
- };
- // Setup and execute test case.
- return globalDb.branch()
- .addCallback(addStore)
- .addCallback(addData)
- .addCallback(openCursorAndRemove)
- .addCallback(checkStore);
- }
- function testClear() {
- if (!idbSupported) {
- return;
- }
- var db;
- return globalDb.branch()
- .addCallback(addStore)
- .addCallback(function(theDb) {
- db = theDb;
- var putTx = db.createTransaction(['store'], TransactionMode.READ_WRITE);
- putTx.objectStore('store').put('1', 'a');
- putTx.objectStore('store').put('2', 'b');
- putTx.objectStore('store').put('3', 'c');
- return putTx.wait();
- })
- .addCallback(function() {
- return db.createTransaction(['store']).objectStore('store').getAll();
- })
- .addCallback(function(results) {
- assertEquals(3, results.length);
- return db.createTransaction(['store'], TransactionMode.READ_WRITE)
- .objectStore('store')
- .clear();
- })
- .addCallback(function() {
- return db.createTransaction(['store']).objectStore('store').getAll();
- })
- .addCallback(function(results) { assertEquals(0, results.length); });
- }
- function testAbortTransaction() {
- if (!idbSupported) {
- return;
- }
- var db;
- return globalDb.branch()
- .addCallback(addStore)
- .addCallback(function(theDb) {
- db = theDb;
- return new Promise(function(resolve, reject) {
- var abortTx =
- db.createTransaction(['store'], TransactionMode.READ_WRITE);
- abortTx.objectStore('store')
- .put('data', 'stuff')
- .addCallback(function() { abortTx.abort(); });
- goog.events.listen(abortTx, EventTypes.ERROR, reject);
- goog.events.listen(abortTx, EventTypes.COMPLETE, function() {
- fail(
- 'transaction shouldn\'t have' +
- ' completed after being aborted');
- });
- goog.events.listen(abortTx, EventTypes.ABORT, resolve);
- });
- })
- .addCallback(function() {
- var checkResultsTx = db.createTransaction(['store']);
- return checkResultsTx.objectStore('store').get('stuff');
- })
- .addCallback(function(result) { assertUndefined(result); });
- }
- function testInactiveTransaction() {
- if (!idbSupported) {
- return;
- }
- var db;
- var store;
- var index;
- var createAndFinishTransaction = function(theDb) {
- db = theDb;
- var tx = db.createTransaction(['store'], TransactionMode.READ_WRITE);
- store = tx.objectStore('store');
- index = store.getIndex('index');
- store.put({key: 'something', value: 'anything'});
- return tx.wait();
- };
- var assertCantUseInactiveTransaction = function() {
- var expectedCode = goog.db.Error.ErrorName.TRANSACTION_INACTIVE_ERR;
- var promises = [];
- var failOp = function(op) {
- return function() {
- fail(op + ' with inactive transaction should have failed');
- };
- };
- var assertCorrectError = function(err) {
- assertEquals(expectedCode, err.getName());
- };
- promises.push(
- store.put({key: 'another', value: 'thing'})
- .then(failOp('putting'), assertCorrectError));
- promises.push(
- store.add({key: 'another', value: 'thing'})
- .then(failOp('adding'), assertCorrectError));
- promises.push(
- store.remove('something').then(failOp('deleting'), assertCorrectError));
- promises.push(
- store.get('something').then(failOp('getting'), assertCorrectError));
- promises.push(
- store.getAll().then(failOp('getting all'), assertCorrectError));
- promises.push(
- store.clear().then(failOp('clearing all'), assertCorrectError));
- promises.push(
- index.get('anything')
- .then(failOp('getting from index'), assertCorrectError));
- promises.push(
- index.getKey('anything')
- .then(failOp('getting key from index'), assertCorrectError));
- promises.push(
- index.getAll('anything')
- .then(failOp('getting all from index'), assertCorrectError));
- promises.push(
- index.getAllKeys('anything')
- .then(failOp('getting all keys from index'), assertCorrectError));
- return goog.Promise.all(promises);
- };
- return globalDb.branch()
- .addCallback(addStoreWithIndex)
- .addCallback(createAndFinishTransaction)
- .addCallback(assertCantUseInactiveTransaction);
- }
- function testWrongTransactionMode() {
- if (!idbSupported) {
- return;
- }
- return globalDb.branch().addCallback(addStore).addCallback(function(db) {
- var tx = db.createTransaction(['store']);
- assertEquals(goog.db.Transaction.TransactionMode.READ_ONLY, tx.getMode());
- var promises = [];
- promises.push(
- tx.objectStore('store')
- .put('KABOOM!', 'anything')
- .then(
- function() { fail('putting should have failed'); },
- function(err) {
- assertEquals(
- goog.db.Error.ErrorName.READ_ONLY_ERR, err.getName());
- }));
- promises.push(
- tx.objectStore('store')
- .add('EXPLODE!', 'die')
- .then(
- function() { fail('adding should have failed'); },
- function(err) {
- assertEquals(
- goog.db.Error.ErrorName.READ_ONLY_ERR, err.getName());
- }));
- promises.push(
- tx.objectStore('store')
- .remove('no key', 'nothing')
- .then(
- function() { fail('deleting should have failed'); },
- function(err) {
- assertEquals(
- goog.db.Error.ErrorName.READ_ONLY_ERR, err.getName());
- }));
- return goog.Promise.all(promises);
- });
- }
- function testManipulateIndexes() {
- if (!idbSupported) {
- return;
- }
- return globalDb.branch()
- .addCallback(function(db) {
- return incrementVersion(db, function(ev, db, tx) {
- var store = db.createObjectStore('store');
- store.createIndex('index', 'attr1');
- store.createIndex('uniqueIndex', 'attr2', {unique: true});
- store.createIndex('multirowIndex', 'attr3', {multirow: true});
- });
- })
- .addCallback(function(db) {
- var tx = db.createTransaction(['store']);
- var store = tx.objectStore('store');
- var index = store.getIndex('index');
- var uniqueIndex = store.getIndex('uniqueIndex');
- var multirowIndex = store.getIndex('multirowIndex');
- try {
- var dies = store.getIndex('diediedie');
- fail('getting non-existent index should have failed');
- } catch (err) {
- // expected
- assertEquals(goog.db.Error.ErrorName.NOT_FOUND_ERR, err.getName());
- }
- return tx.wait();
- })
- .addCallback(function(db) {
- return incrementVersion(db, function(ev, db, tx) {
- var store = tx.objectStore('store');
- store.deleteIndex('index');
- try {
- store.deleteIndex('diediedie');
- fail('deleting non-existent index should have failed');
- } catch (err) {
- // expected
- assertEquals(goog.db.Error.ErrorName.NOT_FOUND_ERR, err.getName());
- }
- });
- })
- .addCallback(function(db) {
- var tx = db.createTransaction(['store']);
- var store = tx.objectStore('store');
- try {
- var index = store.getIndex('index');
- fail('getting deleted index should have failed');
- } catch (err) {
- // expected
- assertEquals(goog.db.Error.ErrorName.NOT_FOUND_ERR, err.getName());
- }
- var uniqueIndex = store.getIndex('uniqueIndex');
- var multirowIndex = store.getIndex('multirowIndex');
- });
- }
- function testAddRecordWithIndex() {
- if (!idbSupported) {
- return;
- }
- var addData = function(db) {
- var store = db.createTransaction(['store'], TransactionMode.READ_WRITE)
- .objectStore('store');
- assertFalse(store.getIndex('index').isUnique());
- assertEquals('value', store.getIndex('index').getKeyPath());
- return store.add({key: 'someKey', value: 'lookUpThis'})
- .addCallback(function() { return db; });
- };
- var readAndAssertAboutData = function(db) {
- var index =
- db.createTransaction(['store']).objectStore('store').getIndex('index');
- var promises = [
- index.get('lookUpThis').addCallback(function(result) {
- assertNotUndefined(result);
- assertEquals('someKey', result.key);
- assertEquals('lookUpThis', result.value);
- }),
- index.getKey('lookUpThis').addCallback(function(result) {
- assertNotUndefined(result);
- assertEquals('someKey', result);
- })
- ];
- return goog.Promise.all(promises).then(function() { return db; });
- };
- return globalDb.branch()
- .addCallback(addStoreWithIndex)
- .addCallback(addData)
- .addCallback(readAndAssertAboutData);
- }
- function testGetMultipleRecordsFromIndex() {
- if (!idbSupported) {
- return;
- }
- var addData = function(db) {
- var addTx = db.createTransaction(['store'], TransactionMode.READ_WRITE);
- addTx.objectStore('store').add({key: '1', value: 'a'});
- addTx.objectStore('store').add({key: '2', value: 'a'});
- addTx.objectStore('store').add({key: '3', value: 'b'});
- return addTx.wait();
- };
- var readData = function(db) {
- var index =
- db.createTransaction(['store']).objectStore('store').getIndex('index');
- var promises = [];
- promises.push(index.getAll().addCallback(function(results) {
- assertNotUndefined(results);
- assertEquals(3, results.length);
- }));
- promises.push(index.getAll('a').addCallback(function(results) {
- assertNotUndefined(results);
- assertEquals(2, results.length);
- }));
- promises.push(index.getAllKeys().addCallback(function(results) {
- assertNotUndefined(results);
- assertEquals(3, results.length);
- assertArrayEquals(['1', '2', '3'], results);
- }));
- promises.push(index.getAllKeys('b').addCallback(function(results) {
- assertNotUndefined(results);
- assertEquals(1, results.length);
- assertArrayEquals(['3'], results);
- }));
- return goog.Promise.all(promises).then(function() { return db; });
- };
- return globalDb.branch()
- .addCallback(addStoreWithIndex)
- .addCallback(addData)
- .addCallback(readData);
- }
- function testUniqueIndex() {
- if (!idbSupported) {
- return;
- }
- var storeDuplicatesToUniqueIndex = function(db) {
- var tx = db.createTransaction(['store'], TransactionMode.READ_WRITE);
- assertTrue(tx.objectStore('store').getIndex('index').isUnique());
- tx.objectStore('store').add({key: '1', value: 'a'});
- tx.objectStore('store').add({key: '2', value: 'a'});
- return transactionToPromise(db, tx).then(
- function() {
- fail('Expected transaction violating unique constraint to fail');
- },
- function(ev) {
- // expected
- assertEquals(
- goog.db.Error.ErrorName.CONSTRAINT_ERR, ev.target.getName());
- });
- };
- return globalDb.branch()
- .addCallback(function(db) {
- return incrementVersion(db, function(ev, db, tx) {
- var store = db.createObjectStore('store', {keyPath: 'key'});
- store.createIndex('index', 'value', {unique: true});
- });
- })
- .addCallback(storeDuplicatesToUniqueIndex);
- }
- function testDeleteDatabase() {
- if (!idbSupported) {
- return;
- }
- return globalDb.branch()
- .addCallback(addStore)
- .addCallback(function(db) {
- db.close();
- return goog.db.deleteDatabase(dbName, function() {
- fail('didn\'t expect deleteDatabase to be blocked');
- });
- })
- .addCallback(openDatabase)
- .addCallback(assertStoreDoesntExist);
- }
- function testDeleteDatabaseIsBlocked() {
- if (!idbSupported) {
- return;
- }
- var wasBlocked = false;
- return globalDb.branch()
- .addCallback(addStore)
- .addCallback(function(db) {
- db.close();
- // Get a fresh connection, without any events registered on globalDb.
- return goog.db.openDatabase(dbName);
- })
- .addCallback(function(db) {
- dbsToClose.push(db);
- return goog.db.deleteDatabase(dbName, function(ev) {
- wasBlocked = true;
- db.close();
- });
- })
- .addCallback(function() {
- assertTrue(wasBlocked);
- return openDatabase();
- })
- .addCallback(assertStoreDoesntExist);
- }
- function testBlockedDeleteDatabaseWithVersionChangeEvent() {
- if (!idbSupported) {
- return;
- }
- var gotVersionChange = false;
- return globalDb.branch()
- .addCallback(addStore)
- .addCallback(function(db) {
- db.close();
- // Get a fresh connection, without any events registered on globalDb.
- return goog.db.openDatabase(dbName);
- })
- .addCallback(function(db) {
- dbsToClose.push(db);
- goog.events.listen(
- db, goog.db.IndexedDb.EventType.VERSION_CHANGE, function(ev) {
- gotVersionChange = true;
- db.close();
- });
- return goog.db.deleteDatabase(dbName);
- })
- .addCallback(function() {
- assertTrue(gotVersionChange);
- return openDatabase();
- })
- .addCallback(assertStoreDoesntExist);
- }
- function testDeleteNonExistentDatabase() {
- if (!idbSupported) {
- return;
- }
- // Deleting non-existent db is a no-op. Shall not throw anything.
- return globalDb.branch().addCallback(function(db) {
- db.close();
- return goog.db.deleteDatabase('non-existent-db');
- });
- }
- function testObjectStoreCountAll() {
- if (!idbSupported) {
- return;
- }
- var values = ['1', '2', '3', '4'];
- var keys = ['a', 'b', 'c', 'd'];
- var addData = goog.partial(populateStore, values, keys);
- return globalDb.branch()
- .addCallback(addStore)
- .addCallback(addData)
- .addCallback(function(db) {
- var tx = db.createTransaction(['store']);
- return tx.objectStore('store').count().addCallback(function(count) {
- assertEquals(values.length, count);
- });
- });
- }
- function testObjectStoreCountSome() {
- if (!idbSupported) {
- return;
- }
- var values = ['1', '2', '3', '4'];
- var keys = ['a', 'b', 'c', 'd'];
- var addData = goog.partial(populateStore, values, keys);
- var countData = function(db) {
- var tx = db.createTransaction(['store']);
- return tx.objectStore('store')
- .count(goog.db.KeyRange.bound('b', 'c'))
- .addCallback(function(count) { assertEquals(2, count); });
- };
- return globalDb.branch()
- .addCallback(addStore)
- .addCallback(addData)
- .addCallback(countData);
- }
- function testIndexCursorGet() {
- if (!idbSupported) {
- return;
- }
- var values = ['1', '2', '3', '4'];
- var keys = ['a', 'b', 'c', 'd'];
- var addData = goog.partial(populateStoreWithObjects, values, keys);
- var valuesResult = [];
- var keysResult = [];
- // Open the cursor over range ['b', 'c'], move in backwards direction.
- var walkBackwardsOverCursor = function(db) {
- var cursorTx = db.createTransaction(['store']);
- var index = cursorTx.objectStore('store').getIndex('index');
- var values = [];
- var keys = [];
- var cursor = index.openCursor(
- goog.db.KeyRange.bound('2', '3'), goog.db.Cursor.Direction.PREV);
- var cursorFinished = forEachRecord(cursor, function() {
- valuesResult.push(cursor.getValue()['value']);
- keysResult.push(cursor.getValue()['key']);
- cursor.next();
- });
- return goog.Promise.all([cursorFinished, cursorTx.wait()]).then(function() {
- return db
- });
- };
- return globalDb.branch()
- .addCallbacks(addStoreWithIndex)
- .addCallback(addData)
- .addCallback(walkBackwardsOverCursor)
- .addCallback(function(db) {
- assertArrayEquals(['3', '2'], valuesResult);
- assertArrayEquals(['c', 'b'], keysResult);
- });
- }
- function testIndexCursorReplace() {
- if (!idbSupported) {
- return;
- }
- var values = ['1', '2', '3', '4'];
- var keys = ['a', 'b', 'c', 'd'];
- var addData = goog.partial(populateStoreWithObjects, values, keys);
- var valuesResult = [];
- var keysResult = [];
- // Store should contain ['1', '2', '5', '4'] after replacement.
- var checkStore = goog.partial(assertStoreObjectValues, ['1', '2', '5', '4']);
- // Use a bounded cursor for ['3', '4') to update value '3' -> '5'.
- var openCursorAndReplace = function(db) {
- var cursorTx = db.createTransaction(['store'], TransactionMode.READ_WRITE);
- var index = cursorTx.objectStore('store').getIndex('index');
- var cursor =
- index.openCursor(goog.db.KeyRange.bound('3', '4', false, true));
- var cursorFinished = forEachRecord(cursor, function() {
- assertEquals('3', cursor.getValue()['value']);
- return cursor.update({'key': cursor.getValue()['key'], 'value': '5'})
- .addCallback(function() { cursor.next(); });
- });
- return goog.Promise.all([cursorFinished, cursorTx.wait()])
- .then(function(results) { return db; });
- };
- // Setup and execute test case.
- return globalDb.branch()
- .addCallback(addStoreWithIndex)
- .addCallback(addData)
- .addCallback(openCursorAndReplace)
- .addCallback(checkStore);
- }
- function testIndexCursorRemove() {
- if (!idbSupported) {
- return;
- }
- var values = ['1', '2', '3', '4'];
- var keys = ['a', 'b', 'c', 'd'];
- var addData = goog.partial(populateStoreWithObjects, values, keys);
- // Store should contain ['1', '2'] after removing elements.
- var checkStore = goog.partial(assertStoreObjectValues, ['1', '2']);
- // Use a bounded cursor for ('2', ...) to remove '3', '4'.
- var openCursorAndRemove = function(db) {
- var cursorTx = db.createTransaction(['store'], TransactionMode.READ_WRITE);
- var store = cursorTx.objectStore('store');
- var index = store.getIndex('index');
- var cursor = index.openCursor(goog.db.KeyRange.lowerBound('2', true));
- var cursorFinished = forEachRecord(cursor, function() {
- return cursor.remove('5').addCallback(function() { cursor.next(); });
- });
- return goog.Promise.all([cursorFinished, cursorTx.wait()])
- .then(function(results) { return db; });
- };
- // Setup and execute test case.
- return globalDb.branch()
- .addCallback(addStoreWithIndex)
- .addCallback(addData)
- .addCallback(openCursorAndRemove)
- .addCallback(checkStore);
- }
- function testCanWaitForTransactionToComplete() {
- if (!idbSupported) {
- return;
- }
- return globalDb.branch().addCallback(addStore).addCallback(function(db) {
- var tx = db.createTransaction(['store'], TransactionMode.READ_WRITE);
- tx.objectStore('store').add({key: 'hi', value: 'something'}, 'stuff');
- return tx.wait();
- });
- }
- function testWaitingOnTransactionThatHasAnError() {
- if (!idbSupported) {
- return;
- }
- return globalDb.branch()
- .addCallback(function(db) {
- return incrementVersion(db, function(ev, db, tx) {
- var store = db.createObjectStore('store', {keyPath: 'key'});
- store.createIndex('index', 'value', {unique: true});
- });
- })
- .addCallback(function(db) {
- var tx = db.createTransaction(['store'], TransactionMode.READ_WRITE);
- assertTrue(tx.objectStore('store').getIndex('index').isUnique());
- tx.objectStore('store').add({key: '1', value: 'a'});
- tx.objectStore('store').add({key: '2', value: 'a'});
- return transactionToPromise(db, tx).then(
- function() { fail('expected transaction to fail'); },
- function(ev) {
- // expected
- assertEquals(
- goog.db.Error.ErrorName.CONSTRAINT_ERR, ev.target.getName());
- });
- });
- }
- function testWaitingOnAnAbortedTransaction() {
- if (!idbSupported) {
- return;
- }
- return globalDb.addCallback(addStore).addCallback(function(db) {
- var tx = db.createTransaction(['store'], TransactionMode.READ_WRITE);
- var waiting = tx.wait().then(
- function() { fail('Wait result should have failed'); },
- function(e) {
- assertEquals(goog.db.Error.ErrorName.ABORT_ERR, e.getName());
- });
- tx.abort();
- return waiting;
- });
- }
|