blob: 8fd34d2f309bee98b0e6c180c36dd992fb7e4448 [file] [log] [blame]
// 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.array');
goog.require('goog.async.Deferred');
goog.require('goog.async.DeferredList');
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.AsyncTestCase');
goog.require('goog.testing.PropertyReplacer');
goog.require('goog.testing.asserts');
goog.require('goog.testing.jsunit');
goog.require('goog.userAgent.product');
goog.require('goog.userAgent.product.isVersion');
var idbSupported = goog.userAgent.product.CHROME &&
goog.userAgent.product.isVersion('22');
var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall();
asyncTestCase.stepTimeout = 5000;
var dbName;
var dbBaseName = 'testDb';
var globalDb = null;
var propertyReplacer;
// On Chrome 24+, the database reports its default version as 1 as opposed to
// the empty string (as per the new spec).
var baseVersion = goog.userAgent.product.isVersion('24') ? 1 : '';
var dbVersion = 1;
function unblockDatabase(db) {
// If a test goes wrong, the database connection may not be closed reliably.
// This listens for a version change event (e.g. from deleting it in
// preparation for the next test) and closes the existing connection when one
// is received.
goog.events.listen(
db, goog.db.IndexedDb.EventType.VERSION_CHANGE,
function(ev) { db.close(); });
}
function openDatabase() {
return goog.db.openDatabase(dbName).addCallback(unblockDatabase);
}
function incrementVersion(db, onUpgradeNeeded) {
if (db.isOpen()) {
db.close();
}
var onBlocked = function(ev) {
fail('Upgrade to version ' + dbVersion + ' is blocked.');
};
return goog.db.openDatabase(dbName, ++dbVersion, onUpgradeNeeded, onBlocked).
addCallback(unblockDatabase).addCallback(function(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'],
goog.db.Transaction.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'],
goog.db.Transaction.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);
closeAndContinue(db);
});
}
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);
closeAndContinue(db);
});
}
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));
});
closeAndContinue(db);
});
}
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);
closeAndContinue(db);
}
}
function failOnError(err) {
fail(err.message);
}
function failOnErrorEvent(ev) {
fail(ev.target.message);
}
function closeAndContinue(db) {
db.close();
asyncTestCase.continueTesting();
}
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() {
propertyReplacer.reset();
}
function testDatabaseOpened() {
if (!idbSupported) {
return;
}
asyncTestCase.waitForAsync('check database is open');
assertNotNull(globalDb);
globalDb.branch().addCallback(function(db) {
assertTrue(db.isOpen());
closeAndContinue(db);
}).addErrback(failOnError);
}
function testOpenWithNewVersion() {
if (!idbSupported) {
return;
}
var upgradeNeeded = false;
asyncTestCase.waitForAsync('open with new version');
globalDb.branch().addCallback(function(db) {
assertEquals(baseVersion, db.getVersion());
db.close();
return incrementVersion(db, function(ev, db, tx) {
upgradeNeeded = true;
});
}).addCallback(function(db) {
assertTrue(upgradeNeeded);
closeAndContinue(db);
}).addErrback(failOnError);
}
function testManipulateObjectStores() {
if (!idbSupported) {
return;
}
asyncTestCase.waitForAsync('manipulate object stores');
globalDb.branch().addCallback(function(db) {
assertEquals(baseVersion, db.getVersion());
db.close();
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'));
closeAndContinue(db);
}).addErrback(failOnError);
}
function testBadObjectStoreManipulation() {
if (!idbSupported) {
return;
}
asyncTestCase.waitForAsync('bad object store manipulation');
var expectedCode = goog.db.Error.ErrorName.INVALID_STATE_ERR;
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());
}
});
}).addCallback(closeAndContinue).addErrback(failOnError);
}
function testGetNonExistentObjectStore() {
if (!idbSupported) {
return;
}
asyncTestCase.waitForAsync('get non-existent object store');
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());
}
closeAndContinue(db);
}).addErrback(failOnError);
}
function testCreateTransaction() {
if (!idbSupported) {
return;
}
asyncTestCase.waitForAsync('create transactions');
globalDb.branch().addCallback(addStore).addCallback(function(db) {
var tx = db.createTransaction(['store']);
assertEquals(
'mode not READ_ONLY',
goog.db.Transaction.TransactionMode.READ_ONLY,
tx.getMode());
tx = db.createTransaction(
['store'],
goog.db.Transaction.TransactionMode.READ_WRITE);
assertEquals(
'mode not READ_WRITE',
goog.db.Transaction.TransactionMode.READ_WRITE,
tx.getMode());
closeAndContinue(db);
}).addErrback(failOnError);
}
function testPutRecord() {
if (!idbSupported) {
return;
}
asyncTestCase.waitForAsync('putting record');
globalDb.branch().addCallback(addStore).addCallback(function(db) {
var rw = goog.db.Transaction.TransactionMode.READ_WRITE;
var COMPLETE = goog.db.Transaction.EventTypes.COMPLETE;
var ERROR = goog.db.Transaction.EventTypes.ERROR;
function checkForOverwrittenValue() {
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);
});
goog.events.listen(checkOverwriteTx, ERROR, failOnErrorEvent);
goog.events.listen(checkOverwriteTx, COMPLETE, function() {
closeAndContinue(db);
});
}
function overwriteValue() {
var overwriteTx = db.createTransaction(['store'], rw);
overwriteTx.objectStore('store').put(
{key: 'overwritten', value: 'value2'},
'putKey');
goog.events.listen(overwriteTx, ERROR, failOnErrorEvent);
goog.events.listen(overwriteTx, COMPLETE, checkForOverwrittenValue);
}
function checkForInitialValue() {
var checkResultsTx = db.createTransaction(['store']);
checkResultsTx.objectStore('store').get('putKey').addCallback(
function(result) {
assertEquals('initial', result.key);
assertEquals('value1', result.value);
});
goog.events.listen(checkResultsTx, ERROR, failOnErrorEvent);
goog.events.listen(checkResultsTx, COMPLETE, overwriteValue);
}
var initialPutTx = db.createTransaction(['store'], rw);
initialPutTx.objectStore('store').put(
{key: 'initial', value: 'value1'},
'putKey');
goog.events.listen(initialPutTx, ERROR, failOnErrorEvent);
goog.events.listen(initialPutTx, COMPLETE, checkForInitialValue);
}).addErrback(failOnError);
}
function testAddRecord() {
if (!idbSupported) {
return;
}
asyncTestCase.waitForAsync('adding record');
globalDb.branch().addCallback(addStore).addCallback(function(db) {
var rw = goog.db.Transaction.TransactionMode.READ_WRITE;
var COMPLETE = goog.db.Transaction.EventTypes.COMPLETE;
var ERROR = goog.db.Transaction.EventTypes.ERROR;
var initialAddTx = db.createTransaction(['store'], rw);
initialAddTx.objectStore('store').add(
{key: 'hi', value: 'something'},
'stuff');
goog.events.listen(initialAddTx, ERROR, failOnErrorEvent);
goog.events.listen(initialAddTx, COMPLETE, function() {
var successfulAddTx = db.createTransaction(['store']);
successfulAddTx.objectStore('store').get('stuff').addCallback(
function(result) {
assertEquals('hi', result.key);
assertEquals('something', result.value);
});
goog.events.listen(successfulAddTx, ERROR, failOnErrorEvent);
goog.events.listen(successfulAddTx, COMPLETE, function() {
var addOverwriteTx = db.createTransaction(['store'], rw);
addOverwriteTx.objectStore('store').add(
{key: 'bye', value: 'nothing'}, 'stuff').addErrback(function(err) {
// expected
assertEquals(
goog.db.Error.ErrorName.CONSTRAINT_ERR,
err.getName());
});
goog.events.listen(addOverwriteTx, COMPLETE, function() {
fail('adding existing record should not have succeeded');
});
goog.events.listen(addOverwriteTx, ERROR, function(ev) {
// expected
assertEquals(
goog.db.Error.ErrorName.CONSTRAINT_ERR,
ev.target.getName());
closeAndContinue(db);
});
});
});
}).addErrback(failOnError);
}
function testPutRecordKeyPathStore() {
if (!idbSupported) {
return;
}
asyncTestCase.waitForAsync('adding record, key path store');
globalDb.branch().addCallback(function(db) {
return incrementVersion(db, function(ev, db, tx) {
db.createObjectStore('keyStore', {keyPath: 'key'});
});
}).addCallback(function(db) {
var COMPLETE = goog.db.Transaction.EventTypes.COMPLETE;
var ERROR = goog.db.Transaction.EventTypes.ERROR;
var putTx = db.createTransaction(
['keyStore'],
goog.db.Transaction.TransactionMode.READ_WRITE);
putTx.objectStore('keyStore').put({key: 'hi', value: 'something'});
goog.events.listen(putTx, ERROR, failOnErrorEvent);
goog.events.listen(putTx, COMPLETE, function() {
var checkResultsTx = db.createTransaction(['keyStore']);
checkResultsTx.objectStore('keyStore').get('hi').addCallback(
function(result) {
assertNotUndefined(result);
assertEquals('hi', result.key);
assertEquals('something', result.value);
});
goog.events.listen(checkResultsTx, ERROR, failOnErrorEvent);
goog.events.listen(checkResultsTx, COMPLETE, function() {
closeAndContinue(db);
});
});
}).addErrback(failOnError);
}
function testPutBadRecordKeyPathStore() {
if (!idbSupported) {
return;
}
asyncTestCase.waitForAsync('adding bad record, key path store');
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'],
goog.db.Transaction.TransactionMode.READ_WRITE);
badTx.objectStore('keyStore').put(
{key: 'diedie', value: 'anything'},
'badKey').addCallback(function() {
fail('inserting with explicit key should have failed');
}).addErrback(function(err) {
// expected
assertEquals(goog.db.Error.ErrorName.DATA_ERR, err.getName());
closeAndContinue(db);
});
}).addErrback(failOnError);
}
function testPutRecordAutoIncrementStore() {
if (!idbSupported) {
return;
}
asyncTestCase.waitForAsync('adding record, auto increment store');
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'],
goog.db.Transaction.TransactionMode.READ_WRITE);
tx.objectStore('aiStore').put('1');
tx.objectStore('aiStore').put('2');
tx.objectStore('aiStore').put('3');
goog.events.listen(
tx,
goog.db.Transaction.EventTypes.ERROR,
failOnErrorEvent);
goog.events.listen(tx, goog.db.Transaction.EventTypes.COMPLETE, function() {
var tx = db.createTransaction(['aiStore']);
tx.objectStore('aiStore').getAll().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'));
closeAndContinue(db);
});
});
}).addErrback(failOnError);
}
function testPutRecordKeyPathAndAutoIncrementStore() {
if (!idbSupported) {
return;
}
asyncTestCase.waitForAsync('adding record, key path + auto increment store');
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'],
goog.db.Transaction.TransactionMode.READ_WRITE);
tx.objectStore('hybridStore').put({value: 'whatever'});
goog.events.listen(
tx,
goog.db.Transaction.EventTypes.ERROR,
failOnErrorEvent);
goog.events.listen(tx, goog.db.Transaction.EventTypes.COMPLETE, function() {
var tx = db.createTransaction(['hybridStore']);
tx.objectStore('hybridStore').getAll().addCallback(function(results) {
assertEquals(1, results.length);
assertEquals('whatever', results[0].value);
assertNotUndefined(results[0].key);
closeAndContinue(db);
});
});
}).addErrback(failOnError);
}
function testPutIllegalRecords() {
if (!idbSupported) {
return;
}
asyncTestCase.waitForAsync('adding illegal records');
globalDb.branch().addCallback(addStore).addCallback(function(db) {
var tx = db.createTransaction(
['store'],
goog.db.Transaction.TransactionMode.READ_WRITE);
tx.objectStore('store').put('death', null).addCallback(function() {
fail('putting with null key should have failed');
}).addErrback(function(err) {
assertEquals(goog.db.Error.ErrorName.DATA_ERR, err.getName());
});
tx.objectStore('store').put('death', NaN).addCallback(function() {
fail('putting with NaN key should have failed');
}).addErrback(function(err) {
assertEquals(goog.db.Error.ErrorName.DATA_ERR, err.getName());
});
tx.objectStore('store').put('death', undefined).addCallback(function() {
fail('putting with undefined key should have failed');
}).addErrback(function(err) {
assertEquals(goog.db.Error.ErrorName.DATA_ERR, err.getName());
});
closeAndContinue(db);
}).addErrback(failOnError);
}
function testPutIllegalRecordsWithIndex() {
if (!idbSupported) {
return;
}
asyncTestCase.waitForAsync('adding illegal records');
globalDb.branch().addCallback(addStoreWithIndex).addCallback(function(db) {
var tx = db.createTransaction(
['store'],
goog.db.Transaction.TransactionMode.READ_WRITE);
tx.objectStore('store').put({key: 'diediedie', value: null}).
addErrback(function(err) {
assertEquals(goog.db.Error.ErrorCode.DATA_ERR, err.code);
});
tx.objectStore('store').put({key: 'dietodeath', value: NaN}).
addErrback(function(err) {
assertEquals(goog.db.Error.ErrorCode.DATA_ERR, err.code);
});
tx.objectStore('store').put({key: 'dietodeath', value: undefined}).
addErrback(function(err) {
assertEquals(goog.db.Error.ErrorCode.DATA_ERR, err.code);
});
closeAndContinue(db);
}).addErrback(failOnError);
}
function testDeleteRecord() {
if (!idbSupported) {
return;
}
asyncTestCase.waitForAsync('deleting record');
globalDb.branch().addCallback(addStore).addCallback(function(db) {
var rw = goog.db.Transaction.TransactionMode.READ_WRITE;
var COMPLETE = goog.db.Transaction.EventTypes.COMPLETE;
var ERROR = goog.db.Transaction.EventTypes.ERROR;
var putTx = db.createTransaction(['store'], rw);
putTx.objectStore('store').put({key: 'hi', value: 'something'}, 'stuff');
goog.events.listen(putTx, ERROR, failOnErrorEvent);
goog.events.listen(putTx, COMPLETE, function() {
var deleteTx = db.createTransaction(['store'], rw);
deleteTx.objectStore('store').remove('stuff');
goog.events.listen(deleteTx, ERROR, failOnErrorEvent);
goog.events.listen(deleteTx, COMPLETE, function() {
var checkResultsTx = db.createTransaction(['store']);
checkResultsTx.objectStore('store').get('stuff').addCallback(
function(result) {
assertUndefined(result);
});
goog.events.listen(checkResultsTx, ERROR, failOnErrorEvent);
goog.events.listen(checkResultsTx, COMPLETE, function() {
closeAndContinue(db);
});
});
});
}).addErrback(failOnError);
}
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);
asyncTestCase.waitForAsync('getting all');
globalDb.branch().addCallbacks(addStore, failOnError).
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);
asyncTestCase.waitForAsync('getting all');
globalDb.branch().addCallbacks(addStore, failOnError).
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);
// Open the cursor over range ['b', 'c'], move in backwards direction.
var openCursorAndCheck = function(db) {
var cursorTx = db.createTransaction(['store']);
var store = cursorTx.objectStore('store');
var values = [];
var keys = [];
var whenTxComplete = new goog.async.Deferred();
goog.events.listen(
cursorTx, goog.db.Transaction.EventTypes.ERROR, failOnError);
goog.events.listen(
cursorTx, goog.db.Transaction.EventTypes.COMPLETE, function() {
whenTxComplete.callback();
});
var whenCursorComplete = new goog.async.Deferred();
var cursor = store.openCursor(
goog.db.KeyRange.bound('b', 'c'),
goog.db.Cursor.Direction.PREV);
var key = goog.events.listen(
cursor, goog.db.Cursor.EventType.NEW_DATA, function() {
values.push(cursor.getValue());
keys.push(cursor.getKey());
cursor.next();
});
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) {
whenCursorComplete.callback(db);
} else {
whenCursorComplete.errback();
}
});
var whenAllComplete = goog.async.DeferredList.gatherResults([
whenCursorComplete,
whenTxComplete
]);
whenAllComplete.addCallback(function(results) {
var db = results[0];
assertArrayEquals(['3', '2'], values);
assertArrayEquals(['c', 'b'], keys);
closeAndContinue(db);
});
};
asyncTestCase.waitForAsync('getting range');
globalDb.branch().addCallbacks(addStore, failOnError).
addCallback(addData).
addCallback(openCursorAndCheck);
}
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 d = new goog.async.Deferred();
var cursorTx = db.createTransaction(
['store'],
goog.db.Transaction.TransactionMode.READ_WRITE);
var whenTxComplete = new goog.async.Deferred();
goog.events.listen(
cursorTx, goog.db.Transaction.EventTypes.ERROR, failOnError);
goog.events.listen(
cursorTx, goog.db.Transaction.EventTypes.COMPLETE, function() {
whenTxComplete.callback();
});
var store = cursorTx.objectStore('store');
var whenCursorComplete = new goog.async.Deferred();
var cursor = store.openCursor(goog.db.KeyRange.bound('b', 'c', true));
var key = goog.events.listen(
cursor, goog.db.Cursor.EventType.NEW_DATA, function() {
assertEquals('3', cursor.getValue());
cursor.update('5').addCallback(function() {
cursor.next();
}).addErrback(failOnError);
});
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) {
whenCursorComplete.callback(db);
} else {
whenCursorComplete.errback();
}
});
goog.async.DeferredList.gatherResults([
whenCursorComplete,
whenTxComplete
]).addCallbacks(function(results) {
d.callback(results[0]);
}, failOnError);
return d;
};
// Setup and execute test case.
asyncTestCase.waitForAsync('replacing value by cursor');
globalDb.branch().addCallbacks(addStore, failOnError).
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 d = new goog.async.Deferred();
var cursorTx = db.createTransaction(
['store'],
goog.db.Transaction.TransactionMode.READ_WRITE);
var whenTxComplete = new goog.async.Deferred();
goog.events.listen(
cursorTx, goog.db.Transaction.EventTypes.ERROR, failOnError);
goog.events.listen(
cursorTx, goog.db.Transaction.EventTypes.COMPLETE, function() {
whenTxComplete.callback();
});
var store = cursorTx.objectStore('store');
var whenCursorComplete = new goog.async.Deferred();
var cursor = store.openCursor(goog.db.KeyRange.lowerBound('b', true));
var key = goog.events.listen(
cursor, goog.db.Cursor.EventType.NEW_DATA, function() {
cursor.remove('5').addCallback(function() {
cursor.next();
}).addErrback(failOnError);
});
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) {
whenCursorComplete.callback(db);
} else {
whenCursorComplete.errback();
}
});
goog.async.DeferredList.gatherResults([
whenCursorComplete,
whenTxComplete
]).addCallbacks(function(results) {
d.callback(results[0]);
}, failOnError);
return d;
};
// Setup and execute test case.
asyncTestCase.waitForAsync('removing value by cursor');
globalDb.branch().addCallbacks(addStore, failOnError).
addCallback(addData).
addCallback(openCursorAndRemove).
addCallback(checkStore);
}
function testClear() {
if (!idbSupported) {
return;
}
asyncTestCase.waitForAsync('clearing');
globalDb.branch().addCallback(addStore).addCallback(function(db) {
var rw = goog.db.Transaction.TransactionMode.READ_WRITE;
var COMPLETE = goog.db.Transaction.EventTypes.COMPLETE;
var ERROR = goog.db.Transaction.EventTypes.ERROR;
var putTx = db.createTransaction(['store'], rw);
putTx.objectStore('store').put('1', 'a');
putTx.objectStore('store').put('2', 'b');
putTx.objectStore('store').put('3', 'c');
goog.events.listen(putTx, ERROR, failOnErrorEvent);
goog.events.listen(putTx, COMPLETE, function() {
var clearTx = db.createTransaction(['store'], rw);
clearTx.objectStore('store').clear();
goog.events.listen(clearTx, ERROR, failOnErrorEvent);
goog.events.listen(clearTx, COMPLETE, function() {
var checkResultsTx = db.createTransaction(['store']);
checkResultsTx.objectStore('store').getAll().addCallback(
function(results) {
assertEquals(0, results.length);
}).addErrback(failOnError);
goog.events.listen(checkResultsTx, ERROR, failOnErrorEvent);
goog.events.listen(checkResultsTx, COMPLETE, function() {
closeAndContinue(db);
});
});
});
}).addErrback(failOnError);
}
function testAbortTransaction() {
if (!idbSupported) {
return;
}
asyncTestCase.waitForAsync('abort transaction');
globalDb.branch().addCallback(addStore).addCallback(function(db) {
var abortTx = db.createTransaction(
['store'],
goog.db.Transaction.TransactionMode.READ_WRITE);
abortTx.objectStore('store').put('data', 'stuff').addCallback(function() {
abortTx.abort();
});
goog.events.listen(
abortTx,
goog.db.Transaction.EventTypes.ERROR,
failOnErrorEvent);
goog.events.listen(
abortTx,
goog.db.Transaction.EventTypes.COMPLETE,
function() {
fail('transaction shouldn\'t have completed after being aborted');
});
goog.events.listen(
abortTx,
goog.db.Transaction.EventTypes.ABORT,
function() {
var checkResultsTx = db.createTransaction(['store']);
checkResultsTx.objectStore('store').get('stuff').addCallback(
function(result) {
assertUndefined(result);
});
goog.events.listen(
checkResultsTx,
goog.db.Transaction.EventTypes.COMPLETE,
function() {
closeAndContinue(db);
});
});
}).addErrback(failOnError);
}
function testInactiveTransaction() {
if (!idbSupported) {
return;
}
asyncTestCase.waitForAsync('inactive transaction');
globalDb.branch().addCallback(addStoreWithIndex).addCallback(function(db) {
var tx = db.createTransaction(
['store'],
goog.db.Transaction.TransactionMode.READ_WRITE);
var store = tx.objectStore('store');
var index = store.getIndex('index');
store.put({key: 'something', value: 'anything'});
goog.events.listen(tx, goog.db.Transaction.EventTypes.COMPLETE, function() {
var expectedCode = goog.db.Error.ErrorName.TRANSACTION_INACTIVE_ERR;
store.put({
key: 'another',
value: 'thing'
}).addCallback(function() {
fail('putting with inactive transaction should have failed');
}).addErrback(function(err) {
assertEquals(expectedCode, err.getName());
});
store.add({
key: 'another',
value: 'thing'
}).addCallback(function() {
fail('adding with inactive transaction should have failed');
}).addErrback(function(err) {
assertEquals(expectedCode, err.getName());
});
store.remove('something').addCallback(function() {
fail('deleting with inactive transaction should have failed');
}).addErrback(function(err) {
assertEquals(expectedCode, err.getName());
});
store.get('something').addCallback(function() {
fail('getting with inactive transaction should have failed');
}).addErrback(function(err) {
assertEquals(expectedCode, err.getName());
});
store.getAll().addCallback(function() {
fail('getting all with inactive transaction should have failed');
}).addErrback(function(err) {
assertEquals(expectedCode, err.getName());
});
store.clear().addCallback(function() {
fail('clearing all with inactive transaction should have failed');
}).addErrback(function(err) {
assertEquals(expectedCode, err.getName());
});
index.get('anything').
addCallback(function() {
fail('getting from index with inactive transaction should have ' +
'failed');
}).addErrback(function(err) {
assertEquals(expectedCode, err.getName());
});
index.getKey('anything').
addCallback(function() {
fail('getting key from index with inactive transaction ' +
'should have failed');
}).addErrback(function(err) {
assertEquals(expectedCode, err.getName());
});
index.getAll('anything').
addCallback(function() {
fail('getting all from index with inactive transaction ' +
'should have failed');
}).addErrback(function(err) {
assertEquals(expectedCode, err.getName());
});
index.getAllKeys('anything').
addCallback(function() {
fail('getting all keys from index with inactive transaction ' +
'should have failed');
}).addErrback(function(err) {
assertEquals(expectedCode, err.getName());
});
closeAndContinue(db);
});
}).addErrback(failOnError);
}
function testWrongTransactionMode() {
if (!idbSupported) {
return;
}
asyncTestCase.waitForAsync('wrong transaction mode');
globalDb.branch().addCallback(addStore).addCallback(function(db) {
var tx = db.createTransaction(['store']);
assertEquals(goog.db.Transaction.TransactionMode.READ_ONLY, tx.getMode());
tx.objectStore('store').put('KABOOM!', 'anything').addCallback(function() {
fail('putting should have failed');
}).addErrback(function(err) {
assertEquals(goog.db.Error.ErrorName.READ_ONLY_ERR, err.getName());
});
tx.objectStore('store').add('EXPLODE!', 'die').addCallback(function() {
fail('adding should have failed');
}).addErrback(function(err) {
assertEquals(goog.db.Error.ErrorName.READ_ONLY_ERR, err.getName());
});
tx.objectStore('store').remove('no key', 'nothing').addCallback(function() {
fail('deleting should have failed');
}).addErrback(function(err) {
assertEquals(goog.db.Error.ErrorName.READ_ONLY_ERR, err.getName());
});
closeAndContinue(db);
}).addErrback(failOnError);
}
function testManipulateIndexes() {
if (!idbSupported) {
return;
}
asyncTestCase.waitForAsync('index manipulation');
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) {
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) {
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) {
assertEquals(goog.db.Error.ErrorName.NOT_FOUND_ERR, err.getName());
}
var uniqueIndex = store.getIndex('uniqueIndex');
var multirowIndex = store.getIndex('multirowIndex');
}).addCallback(closeAndContinue).addErrback(failOnError);
}
function testAddRecordWithIndex() {
if (!idbSupported) {
return;
}
asyncTestCase.waitForAsync('add record with index');
globalDb.branch().addCallback(addStoreWithIndex).addCallback(function(db) {
var COMPLETE = goog.db.Transaction.EventTypes.COMPLETE;
var addTx = db.createTransaction(
['store'],
goog.db.Transaction.TransactionMode.READ_WRITE);
var store = addTx.objectStore('store');
assertFalse(store.getIndex('index').isUnique());
assertEquals('value', store.getIndex('index').getKeyPath());
store.add({key: 'someKey', value: 'lookUpThis'});
goog.events.listen(addTx, COMPLETE, function() {
var checkResultsTx = db.createTransaction(['store']);
var index = checkResultsTx.objectStore('store').getIndex('index');
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);
});
goog.events.listen(
checkResultsTx,
goog.db.Transaction.EventTypes.ERROR,
failOnErrorEvent);
goog.events.listen(checkResultsTx, COMPLETE, function() {
closeAndContinue(db);
});
});
}).addErrback(failOnError);
}
function testGetMultipleRecordsFromIndex() {
if (!idbSupported) {
return;
}
asyncTestCase.waitForAsync('get multiple records from index');
globalDb.branch().addCallback(addStoreWithIndex).addCallback(function(db) {
var COMPLETE = goog.db.Transaction.EventTypes.COMPLETE;
var addTx = db.createTransaction(
['store'],
goog.db.Transaction.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'});
// The following line breaks Chrome 14, but not Chrome 15:
// addTx.objectStore('store').add({key: '4'});
goog.events.listen(
addTx,
goog.db.Transaction.EventTypes.ERROR,
failOnErrorEvent);
goog.events.listen(addTx, COMPLETE, function() {
var checkResultsTx = db.createTransaction(['store']);
var index = checkResultsTx.objectStore('store').getIndex('index');
index.getAll().addCallback(function(results) {
assertNotUndefined(results);
assertEquals(3, results.length);
});
index.getAll('a').addCallback(function(results) {
assertNotUndefined(results);
assertEquals(2, results.length);
});
index.getAllKeys().addCallback(function(results) {
assertNotUndefined(results);
assertEquals(3, results.length);
});
index.getAllKeys('b').addCallback(function(results) {
assertNotUndefined(results);
assertEquals(1, results.length);
});
goog.events.listen(
checkResultsTx,
goog.db.Transaction.EventTypes.ERROR,
failOnErrorEvent);
goog.events.listen(checkResultsTx, COMPLETE, function() {
closeAndContinue(db);
});
});
}).addErrback(failOnError);
}
function testUniqueIndex() {
if (!idbSupported) {
return;
}
asyncTestCase.waitForAsync('adding to unique index');
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'],
goog.db.Transaction.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'});
goog.events.listen(tx, goog.db.Transaction.EventTypes.ERROR, function(ev) {
// expected
assertTrue(
'Expected DATA_ERR, CONSTRAINT_ERR, was ',
// Chrome 21, 23+.
goog.db.Error.ErrorName.CONSTRAINT_ERR == ev.target.getName() ||
// Chrome 22.
goog.db.Error.ErrorName.DATE_ERR == ev.target.getName());
closeAndContinue(db);
});
}).addErrback(failOnError);
}
function testDeleteDatabase() {
if (!idbSupported) {
return;
}
asyncTestCase.waitForAsync('deleting database');
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).
addErrback(failOnError);
}
function testDeleteDatabaseIsBlocked() {
if (!idbSupported) {
return;
}
var wasBlocked = false;
asyncTestCase.waitForAsync('deleting database (blocked)');
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) {
return goog.db.deleteDatabase(dbName, function(ev) {
wasBlocked = true;
db.close();
});
}).addCallback(function() {
assertTrue(wasBlocked);
return openDatabase();
}).addCallback(assertStoreDoesntExist).addErrback(failOnError);
}
function testBlockedDeleteDatabaseWithVersionChangeEvent() {
if (!idbSupported) {
return;
}
var gotVersionChange = false;
asyncTestCase.waitForAsync('deleting database (blocked)');
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) {
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.
asyncTestCase.waitForAsync('check delete non-existent db');
globalDb.branch().addCallback(function(db) {
db.close();
return goog.db.deleteDatabase('non-existent-db');
}).addCallbacks(function() {
asyncTestCase.continueTesting();
}, failOnError);
}
function testObjectStoreCountAll() {
if (!idbSupported) {
return;
}
var values = ['1', '2', '3', '4'];
var keys = ['a', 'b', 'c', 'd'];
var addData = goog.partial(populateStore, values, keys);
var checkCountAll = function(db) {
var tx = db.createTransaction(['store']);
var store = tx.objectStore('store');
return store.count().addCallbacks(function(count) {
assertEquals(values.length, count);
tx.dispose();
closeAndContinue(db);
}, function(e) {
tx.dispose();
db.close();
failOnError(e);
});
};
asyncTestCase.waitForAsync('testObjectStoreCountAll');
globalDb.branch().addCallbacks(addStore, failOnError).
addCallback(addData).
addCallback(checkCountAll);
}
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 checkCountSome = function(db) {
var tx = db.createTransaction(['store']);
var store = tx.objectStore('store');
return store.count(goog.db.KeyRange.bound('b', 'c')).addCallbacks(
function(count) {
assertEquals(2, count);
tx.dispose();
closeAndContinue(db);
}, function(e) {
tx.dispose();
db.close();
failOnError(e);
});
};
asyncTestCase.waitForAsync('testObjectStoreCountSome');
globalDb.branch().addCallbacks(addStore, failOnError).
addCallback(addData).
addCallback(checkCountSome);
}
function testIndexCursorGet() {
if (!idbSupported) {
return;
}
var values = ['1', '2', '3', '4'];
var keys = ['a', 'b', 'c', 'd'];
var addData = goog.partial(populateStoreWithObjects, values, keys);
// Open the cursor over range ['b', 'c'], move in backwards direction.
var openCursorAndCheck = function(db) {
var cursorTx = db.createTransaction(['store']);
var store = cursorTx.objectStore('store');
var index = store.getIndex('index');
var values = [];
var keys = [];
var txDeferred = new goog.async.Deferred();
goog.events.listen(
cursorTx, goog.db.Transaction.EventTypes.ERROR, failOnError);
goog.events.listen(
cursorTx, goog.db.Transaction.EventTypes.COMPLETE,
function() {
txDeferred.callback();
});
var cursorDeferred = new goog.async.Deferred();
var cursor = index.openCursor(
goog.db.KeyRange.bound('2', '3'),
goog.db.Cursor.Direction.PREV);
var key = goog.events.listen(
cursor, goog.db.Cursor.EventType.NEW_DATA,
function() {
values.push(cursor.getValue()['value']);
keys.push(cursor.getValue()['key']);
cursor.next();
});
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) {
cursorDeferred.callback(db);
} else {
cursorDeferred.errback();
}
});
goog.async.DeferredList.gatherResults(
[cursorDeferred, txDeferred]).addCallback(
function(results) {
goog.events.unlistenByKey(key);
var db = results[0];
assertArrayEquals(['3', '2'], values);
assertArrayEquals(['c', 'b'], keys);
closeAndContinue(db);
});
};
asyncTestCase.waitForAsync('testIndexCursorGet');
globalDb.branch().addCallbacks(addStoreWithIndex, failOnError).
addCallback(addData).
addCallback(openCursorAndCheck);
}
function testIndexCursorReplace() {
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', '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 d = new goog.async.Deferred();
var cursorTx = db.createTransaction(
['store'],
goog.db.Transaction.TransactionMode.READ_WRITE);
var txDeferred = new goog.async.Deferred();
goog.events.listen(
cursorTx, goog.db.Transaction.EventTypes.ERROR, failOnError);
goog.events.listen(
cursorTx, goog.db.Transaction.EventTypes.COMPLETE,
function() {
txDeferred.callback();
});
var store = cursorTx.objectStore('store');
var index = store.getIndex('index');
var cursorDeferred = new goog.async.Deferred();
var cursor = index.openCursor(
goog.db.KeyRange.bound('3', '4', false, true));
var key = goog.events.listen(
cursor, goog.db.Cursor.EventType.NEW_DATA,
function() {
assertEquals('3', cursor.getValue()['value']);
cursor.update(
{
'key': cursor.getValue()['key'],
'value': '5'
}).addCallback(function() {
cursor.next();
}).addErrback(failOnError);
});
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) {
cursorDeferred.callback(db);
} else {
cursorDeferred.errback();
}
});
goog.async.DeferredList.gatherResults(
[cursorDeferred, txDeferred]).addCallbacks(
function(results) {
goog.events.unlistenByKey(key);
d.callback(results[0]);
}, failOnError);
return d;
};
// Setup and execute test case.
asyncTestCase.waitForAsync('replacing value by cursor');
globalDb.branch().addCallbacks(addStoreWithIndex, failOnError).
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 d = new goog.async.Deferred();
var cursorTx = db.createTransaction(
['store'],
goog.db.Transaction.TransactionMode.READ_WRITE);
var txDeferred = new goog.async.Deferred();
goog.events.listen(
cursorTx, goog.db.Transaction.EventTypes.ERROR, failOnError);
goog.events.listen(
cursorTx, goog.db.Transaction.EventTypes.COMPLETE,
function() {
txDeferred.callback();
});
var store = cursorTx.objectStore('store');
var index = store.getIndex('index');
var cursorDeferred = new goog.async.Deferred();
var cursor = index.openCursor(goog.db.KeyRange.lowerBound('2', true));
var key = goog.events.listen(
cursor, goog.db.Cursor.EventType.NEW_DATA,
function() {
cursor.remove('5').addCallback(function() {
cursor.next();
}).addErrback(failOnError);
});
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) {
cursorDeferred.callback(db);
} else {
cursorDeferred.errback();
}
});
goog.async.DeferredList.gatherResults(
[cursorDeferred, txDeferred]).addCallbacks(
function(results) {
goog.events.unlistenByKey(key);
d.callback(results[0]);
}, failOnError);
return d;
};
// Setup and execute test case.
asyncTestCase.waitForAsync('removing value by cursor');
globalDb.branch().addCallbacks(addStoreWithIndex, failOnError).
addCallback(addData).
addCallback(openCursorAndRemove).
addCallback(checkStore);
}
function testCanWaitForTransactionToComplete() {
if (!idbSupported) {
return;
}
asyncTestCase.waitForAsync('wait for transaction to complete');
globalDb.branch().addCallback(addStore).addCallback(function(db) {
var tx = db.createTransaction(['store'],
goog.db.Transaction.TransactionMode.READ_WRITE);
tx.objectStore('store').add({key: 'hi', value: 'something'}, 'stuff');
tx.wait().addCallbacks(closeAndContinue, failOnError);
});
}
function testWaitingOnTransactionThatHasAnError() {
if (!idbSupported) {
return;
}
asyncTestCase.waitForAsync('adding to unique index');
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'],
goog.db.Transaction.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'});
tx.wait().addCallbacks(
function() {
fail('expected transaction to fail');
}, function(err) {
assertTrue(
'Expected DATA_ERR, CONSTRAINT_ERR, was ' + err.getName(),
// Chrome 21, 23+.
goog.db.Error.ErrorName.CONSTRAINT_ERR == err.getName() ||
// Chrome 22.
goog.db.Error.ErrorName.DATA_ERR == err.getName());
closeAndContinue(db);
});
}).addErrback(failOnError);
}
function testWaitingOnAnAbortedTransaction() {
if (!idbSupported) {
return;
}
asyncTestCase.waitForAsync('aborting transaction');
globalDb.branch().addCallback(addStore).addCallback(function(db) {
var tx = db.createTransaction(['store'],
goog.db.Transaction.TransactionMode.READ_WRITE);
tx.wait().addCallbacks(
function() {
fail('Wait result should have failed');
},
function(e) {
assertEquals(goog.db.Error.ErrorName.ABORT_ERR, e.getName());
closeAndContinue(db);
});
tx.abort();
});
}