VulpesOS/gaia/apps/homescreen/test/unit/datastore_test.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

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();
});
});
});
});