Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
457 lines
13 KiB
JavaScript
Executable file
457 lines
13 KiB
JavaScript
Executable file
/* global MockIndexedDB, MockNavigatorDatastore, mockLocalStorage, Datastore */
|
|
'use strict';
|
|
|
|
require('/shared/test/unit/mocks/mock_indexedDB.js');
|
|
require('/shared/test/unit/mocks/mock_navigator_datastore.js');
|
|
require('mocks/mock_localStorage.js');
|
|
require('/js/datastore.js');
|
|
|
|
suite('Datastore', () => {
|
|
var mockIndexedDB;
|
|
var realNavigatorDatastores;
|
|
var realLocalStorage;
|
|
var datastoreName = 'datastore_name_example';
|
|
var bookmarks;
|
|
|
|
setup(() => {
|
|
mockIndexedDB = new MockIndexedDB();
|
|
mockLocalStorage.mSetup();
|
|
Object.defineProperty(window, 'localStorage', {
|
|
configurable: true,
|
|
get: () => mockLocalStorage
|
|
});
|
|
realNavigatorDatastores = navigator.getDataStores;
|
|
navigator.getDataStores = MockNavigatorDatastore.getDataStores;
|
|
|
|
bookmarks = new Datastore(datastoreName);
|
|
});
|
|
|
|
teardown(() => {
|
|
mockIndexedDB.mTearDown();
|
|
Object.defineProperty(window, 'localStorage', {
|
|
configurable: true,
|
|
get: () => realLocalStorage
|
|
});
|
|
navigator.getDataStores = realNavigatorDatastores;
|
|
|
|
bookmarks = null;
|
|
});
|
|
|
|
suite('Datastore constructor', () => {
|
|
test('should store the name of the datastore', () => {
|
|
assert.equal(bookmarks.name, datastoreName);
|
|
assert.isTrue(bookmarks.revisionName.includes(datastoreName));
|
|
assert.isTrue(bookmarks.idbName.includes(datastoreName));
|
|
assert.isTrue(bookmarks.storeName.includes(datastoreName));
|
|
});
|
|
});
|
|
|
|
suite('Datastore#init()', () => {
|
|
test('should open indexedDB and Datastore instances', done => {
|
|
assert.isNull(bookmarks.db);
|
|
assert.isNull(bookmarks.datastore);
|
|
bookmarks.init().then(() => {
|
|
assert.isObject(bookmarks.db);
|
|
assert.isObject(bookmarks.datastore);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
suite('Datastore#upgradeSchema()', () => {
|
|
var names = [];
|
|
var onUpgradeNeededEvent = version => {
|
|
return {
|
|
target: {
|
|
result: {
|
|
createObjectStore: (name, options) => {
|
|
names.push(name);
|
|
return {
|
|
createIndex: (name, keyPath, options) => {}
|
|
};
|
|
}
|
|
}
|
|
},
|
|
oldVersion: version
|
|
};
|
|
};
|
|
|
|
setup(() => {
|
|
names = [];
|
|
});
|
|
|
|
test('should create an object store with older versions', () => {
|
|
bookmarks.upgradeSchema(onUpgradeNeededEvent(0));
|
|
assert.deepEqual(names, [bookmarks.storeName]);
|
|
});
|
|
|
|
test('should not create object stores with newer versions', () => {
|
|
bookmarks.upgradeSchema(onUpgradeNeededEvent(2));
|
|
assert.equal(names.length, 0);
|
|
});
|
|
});
|
|
|
|
suite('Datastore#synchronise()', () => {
|
|
var stub;
|
|
|
|
setup(done => {
|
|
// Bookmarks calls synchronise once the datastore is open and the
|
|
// event handler is registered.
|
|
stub = sinon.stub(bookmarks, 'synchronise', () => {
|
|
stub.restore();
|
|
done();
|
|
});
|
|
|
|
bookmarks.init();
|
|
});
|
|
|
|
teardown(() => {
|
|
stub.restore();
|
|
});
|
|
|
|
test('should call set() on update', done => {
|
|
stub = sinon.stub(bookmarks, 'set');
|
|
bookmarks.datastore._tasks[0] = {
|
|
operation: 'update',
|
|
id: 0,
|
|
data: {}
|
|
};
|
|
bookmarks.synchronise().then(() => {
|
|
assert.isTrue(stub.calledOnce);
|
|
done();
|
|
});
|
|
});
|
|
|
|
test('should call set() on add', done => {
|
|
stub = sinon.stub(bookmarks, 'set');
|
|
bookmarks.datastore._tasks[0] = {
|
|
operation: 'add',
|
|
id: 0,
|
|
data: {}
|
|
};
|
|
bookmarks.synchronise().then(() => {
|
|
assert.isTrue(stub.calledOnce);
|
|
done();
|
|
});
|
|
});
|
|
|
|
test('should call remove() on remove', done => {
|
|
stub = sinon.stub(bookmarks, 'remove');
|
|
bookmarks.datastore._tasks[0] = {
|
|
operation: 'remove',
|
|
id: 0,
|
|
data: {}
|
|
};
|
|
bookmarks.synchronise().then(() => {
|
|
assert.isTrue(stub.calledOnce);
|
|
done();
|
|
});
|
|
});
|
|
|
|
test('should call clear() on clear', done => {
|
|
stub = sinon.stub(bookmarks, 'clear');
|
|
bookmarks.datastore._tasks[0] = {
|
|
operation: 'clear',
|
|
id: 0,
|
|
data: {}
|
|
};
|
|
bookmarks.synchronise().then(() => {
|
|
assert.isTrue(stub.calledOnce);
|
|
done();
|
|
});
|
|
});
|
|
|
|
test('should call updateRevision() when done', done => {
|
|
stub = sinon.stub(bookmarks, 'updateRevision');
|
|
bookmarks.datastore._tasks = [{
|
|
operation: 'done',
|
|
id: 0,
|
|
data: null
|
|
}];
|
|
bookmarks.synchronise().then(() => {
|
|
assert.isTrue(stub.calledOnce);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
suite('indexedDB backed methods', () => {
|
|
var objectStores = {};
|
|
var deletedValues = [];
|
|
var attachDBTransaction = objectStoreName => {
|
|
objectStores = {};
|
|
deletedValues = [];
|
|
bookmarks.db.transaction = (objectStoreNames, IDBTransactionMode) => {
|
|
var oncomplete = null;
|
|
return {
|
|
objectStore: name => {
|
|
return {
|
|
put: value => {
|
|
if (name !== objectStoreName) {
|
|
return;
|
|
}
|
|
objectStores[name] = value;
|
|
if (oncomplete) {
|
|
oncomplete();
|
|
oncomplete = null;
|
|
}
|
|
},
|
|
delete: value => {
|
|
if (name !== objectStoreName) {
|
|
return;
|
|
}
|
|
deletedValues.push(value);
|
|
if (oncomplete) {
|
|
oncomplete();
|
|
oncomplete = null;
|
|
}
|
|
},
|
|
clear: () => {
|
|
if (name !== objectStoreName) {
|
|
return;
|
|
}
|
|
if (oncomplete) {
|
|
oncomplete();
|
|
oncomplete = null;
|
|
}
|
|
},
|
|
get: () => {
|
|
if (name !== objectStoreName) {
|
|
return;
|
|
}
|
|
return {
|
|
set onsuccess(cb) {
|
|
cb({
|
|
target: {
|
|
result: { id: 'abc/', data: {} }
|
|
}
|
|
});
|
|
}
|
|
};
|
|
},
|
|
openCursor: () => {
|
|
return {
|
|
set onsuccess(cb) {
|
|
if (objectStoreNames.indexOf(name) === -1) {
|
|
return;
|
|
}
|
|
cb({
|
|
target: {
|
|
result: {
|
|
value: { id: 'abc/', data: {} },
|
|
continue: () => {
|
|
cb({
|
|
target: {
|
|
result: {
|
|
value: { id: 'def/', data: {} },
|
|
continue: () => {}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
});
|
|
if (oncomplete) {
|
|
oncomplete();
|
|
oncomplete = null;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
};
|
|
},
|
|
set oncomplete(cb) {
|
|
oncomplete = cb;
|
|
},
|
|
onerror: () => {}
|
|
};
|
|
};
|
|
};
|
|
|
|
setup(() => {
|
|
bookmarks.db = {};
|
|
});
|
|
|
|
suite('Datastore#set()', () => {
|
|
var data = {
|
|
id: 'abc/',
|
|
misc: 'def'
|
|
};
|
|
|
|
setup(() => {
|
|
attachDBTransaction(bookmarks.storeName);
|
|
});
|
|
|
|
test('should store data', done => {
|
|
bookmarks.set(data)
|
|
.then(() => {
|
|
assert.equal(Object.keys(objectStores).length, 1);
|
|
assert.equal(objectStores[bookmarks.storeName].id, data.id);
|
|
assert.equal(objectStores[bookmarks.storeName].data, data);
|
|
done();
|
|
});
|
|
});
|
|
|
|
test('should emit an event', done => {
|
|
document.addEventListener(bookmarks.name + '-set', evt => {
|
|
assert.equal(evt.detail.id, data.id);
|
|
done();
|
|
});
|
|
bookmarks.set(data);
|
|
});
|
|
|
|
test('handles exception', done => {
|
|
var realTransaction = bookmarks.db.transaction;
|
|
bookmarks.db.transaction = (objectStoreNames, IDBTransactionMode) => {
|
|
var txn = realTransaction(objectStores, IDBTransactionMode);
|
|
var realObjectStore = txn.objectStore;
|
|
txn.objectStore = objectStoreName => {
|
|
var objectStore = realObjectStore(objectStoreName);
|
|
sinon.stub(objectStore, 'put', () => {
|
|
throw 'this exception should be caught';
|
|
});
|
|
return objectStore;
|
|
};
|
|
return txn;
|
|
};
|
|
bookmarks.set(data).then(done, done);
|
|
});
|
|
});
|
|
|
|
suite('Datastore#remove()', () => {
|
|
var id = 'abc/';
|
|
|
|
setup(() => {
|
|
attachDBTransaction(bookmarks.storeName);
|
|
});
|
|
|
|
test('should remove data from object store', done => {
|
|
bookmarks.remove(id)
|
|
.then(() => {
|
|
assert.deepEqual(deletedValues, [id]);
|
|
done();
|
|
});
|
|
});
|
|
|
|
test('should emit an event', done => {
|
|
document.addEventListener(bookmarks.name + '-removed', evt => {
|
|
assert.equal(evt.detail.id, id);
|
|
done();
|
|
});
|
|
bookmarks.remove(id);
|
|
});
|
|
|
|
test('handles exception', done => {
|
|
var realTransaction = bookmarks.db.transaction;
|
|
bookmarks.db.transaction = (objectStoreNames, IDBTransactionMode) => {
|
|
var txn = realTransaction(objectStores, IDBTransactionMode);
|
|
var realObjectStore = txn.objectStore;
|
|
txn.objectStore = objectStoreName => {
|
|
var objectStore = realObjectStore(objectStoreName);
|
|
sinon.stub(objectStore, 'delete', () => {
|
|
throw 'this exception should be caught';
|
|
});
|
|
return objectStore;
|
|
};
|
|
return txn;
|
|
};
|
|
bookmarks.remove(id).then(done, done);
|
|
});
|
|
});
|
|
|
|
suite('Datastore#clear()', () => {
|
|
test('should remove all data from object store', done => {
|
|
attachDBTransaction(bookmarks.storeName);
|
|
bookmarks.clear()
|
|
.then(() => {
|
|
done();
|
|
});
|
|
});
|
|
|
|
test('should emit an event', done => {
|
|
attachDBTransaction(bookmarks.storeName);
|
|
document.addEventListener(bookmarks.name + '-cleared', () => {
|
|
done();
|
|
});
|
|
bookmarks.clear();
|
|
});
|
|
});
|
|
|
|
suite('Datastore#get()', () => {
|
|
test('should retrieve data from object store', done => {
|
|
attachDBTransaction(bookmarks.storeName);
|
|
bookmarks.get('abc/')
|
|
.then(results => {
|
|
assert.deepEqual(results, { id: 'abc/', data: {} });
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
suite('Datastore#getAll()', () => {
|
|
test('should retrieve data from object store', done => {
|
|
attachDBTransaction(bookmarks.storeName);
|
|
bookmarks.getAll()
|
|
.then(results => {
|
|
assert.equal(results.length, 2);
|
|
assert.deepEqual(results[0], { id: 'abc/', data: {} });
|
|
assert.deepEqual(results[1], { id: 'def/', data: {} });
|
|
done();
|
|
});
|
|
});
|
|
|
|
test('should accept a filtering function as argument', done => {
|
|
attachDBTransaction(bookmarks.storeName);
|
|
bookmarks.getAll(entry => entry.id.includes('def/'))
|
|
.then(results => {
|
|
assert.equal(results.length, 1);
|
|
assert.deepEqual(results[0], { id: 'def/', data: {} });
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
suite('Datastore#updateRevision()', () => {
|
|
setup(done => {
|
|
bookmarks.init().then(done);
|
|
});
|
|
|
|
test('should set the datastore revision ID in lastRevision', () => {
|
|
assert.isNull(bookmarks.lastRevision);
|
|
bookmarks.updateRevision();
|
|
assert.equal(bookmarks.lastRevision, bookmarks.datastore.revisionId);
|
|
});
|
|
|
|
test('should update the datastore revision ID in lastRevision', done => {
|
|
var newRevisionId = 'xxx-zzz';
|
|
bookmarks.datastore._tasks = [{
|
|
operation: 'done',
|
|
id: 0,
|
|
data: null
|
|
}];
|
|
|
|
bookmarks.datastore.revisionId = newRevisionId;
|
|
|
|
bookmarks.synchronise().then(() => {
|
|
bookmarks.init().then(() => {
|
|
assert.equal(bookmarks.lastRevision, newRevisionId);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
suite('Datastore#onChange()', () => {
|
|
test('should call synchronise()', done => {
|
|
var synchroniseStub = sinon.stub(bookmarks, 'synchronise',
|
|
() => Promise.resolve());
|
|
bookmarks.onChange();
|
|
setTimeout(() => {
|
|
assert.isTrue(synchroniseStub.calledOnce);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|