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.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
19
gaia/apps/homescreen/test/unit/.jshintrc
Executable file
19
gaia/apps/homescreen/test/unit/.jshintrc
Executable file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"extends": "../../../../.jshintrc",
|
||||
"predef": [
|
||||
"assert",
|
||||
"suite",
|
||||
"test",
|
||||
"setup",
|
||||
"teardown",
|
||||
"suiteSetup",
|
||||
"suiteTeardown",
|
||||
"suiteTemplate",
|
||||
"require",
|
||||
"requireApp",
|
||||
"requireElements",
|
||||
"sinon",
|
||||
"mocha",
|
||||
"loadBodyHTML"
|
||||
]
|
||||
}
|
||||
43
gaia/apps/homescreen/test/unit/_index.html
Executable file
43
gaia/apps/homescreen/test/unit/_index.html
Executable file
|
|
@ -0,0 +1,43 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<header id="page-indicator-header"><div id="page-indicator"><span></span><span></span></div></header>
|
||||
<div id="shadow"></div>
|
||||
<div id="panels">
|
||||
<div id="apps-panel">
|
||||
<div class="scrollable">
|
||||
<div id="apps"></div>
|
||||
</div>
|
||||
|
||||
<div id="cancel-download" class="dialog">
|
||||
<section>
|
||||
<p class="body">stop-download-body</p>
|
||||
</section>
|
||||
<button class="action">stop-download-action</button>
|
||||
<button>cancel-action</button>
|
||||
</div>
|
||||
|
||||
<div id="resume-download" class="dialog">
|
||||
<section>
|
||||
<p class="body">resume-download-body</p>
|
||||
</section>
|
||||
<button class="action">resume-download-action</button>
|
||||
<button>cancel-action</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="pages-panel">
|
||||
<div class="scrollable">
|
||||
<div id="pages"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="bottombar">
|
||||
<div id="remove"></div>
|
||||
<div id="rename"></div>
|
||||
<div id="done"></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
1263
gaia/apps/homescreen/test/unit/apps_test.js
Executable file
1263
gaia/apps/homescreen/test/unit/apps_test.js
Executable file
File diff suppressed because it is too large
Load diff
271
gaia/apps/homescreen/test/unit/appsmetadata_test.js
Executable file
271
gaia/apps/homescreen/test/unit/appsmetadata_test.js
Executable file
|
|
@ -0,0 +1,271 @@
|
|||
/* global MockIndexedDB, AppsMetadata */
|
||||
'use strict';
|
||||
|
||||
require('/shared/test/unit/mocks/mock_indexedDB.js');
|
||||
require('/js/appsmetadata.js');
|
||||
|
||||
suite('AppsMetadata', () => {
|
||||
var mockIndexedDB;
|
||||
var metadata;
|
||||
|
||||
setup(() => {
|
||||
mockIndexedDB = new MockIndexedDB();
|
||||
metadata = new AppsMetadata();
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
metadata = null;
|
||||
mockIndexedDB.mTearDown();
|
||||
});
|
||||
|
||||
suite('AppsMetadata#init()', () => {
|
||||
test('should open an indexedDB instance', done => {
|
||||
assert.equal(mockIndexedDB.dbs.length, 0);
|
||||
metadata.init().then(() => {
|
||||
assert.equal(mockIndexedDB.dbs.length, 1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('AppsMetadata#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 2 object stores with older versions', () => {
|
||||
metadata.upgradeSchema(onUpgradeNeededEvent(0));
|
||||
assert.equal(names.length, 2);
|
||||
assert.isTrue(names.indexOf('order') > -1);
|
||||
assert.isTrue(names.indexOf('icon') > -1);
|
||||
});
|
||||
|
||||
test('should not create object stores with newer versions', () => {
|
||||
metadata.upgradeSchema(onUpgradeNeededEvent(2));
|
||||
assert.equal(names.length, 0);
|
||||
});
|
||||
});
|
||||
|
||||
suite('indexedDB backed methods', () => {
|
||||
var objectStores = {};
|
||||
var deletedValues = [];
|
||||
var attachDBTransaction = objectStoreName => {
|
||||
metadata.db.transaction = (objectStoreNames, IDBTransactionMode) => {
|
||||
return {
|
||||
objectStore: name => {
|
||||
return {
|
||||
put: value => {
|
||||
if (objectStoreName && name !== objectStoreName) {
|
||||
return;
|
||||
}
|
||||
objectStores[name] = value;
|
||||
},
|
||||
get: id => {
|
||||
return {
|
||||
set onsuccess(cb) {
|
||||
if (name === 'icon') {
|
||||
switch(id) {
|
||||
case 'abc/':
|
||||
cb({ target: { result: { id: 'abc/', icon: 'abc' } } });
|
||||
return;
|
||||
|
||||
case 'def/':
|
||||
cb({ target: { result: { id: 'def/', icon: 'def' } } });
|
||||
return;
|
||||
}
|
||||
}
|
||||
cb({ target: {} });
|
||||
}
|
||||
};
|
||||
},
|
||||
delete: value => {
|
||||
if (name !== objectStoreName) {
|
||||
return;
|
||||
}
|
||||
deletedValues.push(value);
|
||||
},
|
||||
clear: () => {
|
||||
if (name !== objectStoreName) {
|
||||
return;
|
||||
}
|
||||
},
|
||||
openCursor: () => {
|
||||
return {
|
||||
set onsuccess(cb) {
|
||||
if (objectStoreNames.indexOf(name) === -1) {
|
||||
return;
|
||||
}
|
||||
var value = (name === 'order') ?
|
||||
{ id: 'abc/', order: 0 } :
|
||||
{ id: 'abc/', icon: 'abc' };
|
||||
cb({
|
||||
target: {
|
||||
result: {
|
||||
value: value,
|
||||
continue: () => {
|
||||
var value = (name === 'order') ?
|
||||
{ id: 'def/', order: 1 } :
|
||||
{ id: 'def/', icon: 'def' };
|
||||
cb({
|
||||
target: {
|
||||
result: {
|
||||
value: value,
|
||||
continue: () => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
},
|
||||
index: function() {
|
||||
return this;
|
||||
}
|
||||
};
|
||||
},
|
||||
set oncomplete(cb) {
|
||||
cb();
|
||||
},
|
||||
onerror: () => {
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
setup(() => {
|
||||
metadata.db = {};
|
||||
objectStores = {};
|
||||
deletedValues = [];
|
||||
});
|
||||
|
||||
suite('AppsMetadata#set()', () => {
|
||||
var setMetadata = (id, order, icon) => {
|
||||
metadata.set(
|
||||
[{
|
||||
id: id,
|
||||
order: order,
|
||||
icon: icon
|
||||
}]);
|
||||
};
|
||||
|
||||
test('should ignore apps without id', () => {
|
||||
attachDBTransaction('order');
|
||||
var order = 0, icon = 'abc';
|
||||
setMetadata(undefined, order, icon);
|
||||
assert.equal(Object.keys(objectStores).length, 0);
|
||||
});
|
||||
|
||||
test('should store apps with order', () => {
|
||||
attachDBTransaction('order');
|
||||
var id = 'abc/', order = 0;
|
||||
setMetadata(id, order, undefined);
|
||||
assert.equal(Object.keys(objectStores).length, 1);
|
||||
assert.equal(objectStores.order.id, id);
|
||||
assert.equal(objectStores.order.order, order);
|
||||
});
|
||||
|
||||
test('should store apps with icon', () => {
|
||||
attachDBTransaction('icon');
|
||||
var id = 'abc/', icon = 'abc';
|
||||
setMetadata(id, undefined, icon);
|
||||
assert.equal(Object.keys(objectStores).length, 1);
|
||||
assert.equal(objectStores.icon.id, id);
|
||||
assert.equal(objectStores.icon.icon, icon);
|
||||
});
|
||||
|
||||
test('updating the order should not change the icon', () => {
|
||||
attachDBTransaction();
|
||||
var id = 'abc/', order = 0, icon = 'abc';
|
||||
setMetadata(id, order, icon);
|
||||
assert.equal(objectStores.order.id, id);
|
||||
assert.equal(objectStores.order.order, order);
|
||||
assert.equal(objectStores.icon.id, id);
|
||||
assert.equal(objectStores.icon.icon, icon);
|
||||
|
||||
order = 2;
|
||||
setMetadata(id, order, undefined);
|
||||
assert.equal(Object.keys(objectStores).length, 2);
|
||||
assert.equal(objectStores.order.id, id);
|
||||
assert.equal(objectStores.order.order, order);
|
||||
assert.equal(objectStores.icon.id, id);
|
||||
assert.equal(objectStores.icon.icon, icon);
|
||||
});
|
||||
|
||||
test('updating the icon should not change the order', () => {
|
||||
attachDBTransaction();
|
||||
var id = 'abc/', order = 0, icon = 'abc';
|
||||
setMetadata(id, order, icon);
|
||||
assert.equal(objectStores.order.id, id);
|
||||
assert.equal(objectStores.order.order, order);
|
||||
assert.equal(objectStores.icon.id, id);
|
||||
assert.equal(objectStores.icon.icon, icon);
|
||||
|
||||
icon = 'def';
|
||||
setMetadata(id, undefined, icon);
|
||||
assert.equal(Object.keys(objectStores).length, 2);
|
||||
assert.equal(objectStores.order.id, id);
|
||||
assert.equal(objectStores.order.order, order);
|
||||
assert.equal(objectStores.icon.id, id);
|
||||
assert.equal(objectStores.icon.icon, icon);
|
||||
});
|
||||
});
|
||||
|
||||
suite('AppsMetadata#remove()', () => {
|
||||
var id = 'abc/';
|
||||
|
||||
test('should remove data from order object store', done => {
|
||||
attachDBTransaction('order');
|
||||
metadata.remove(id)
|
||||
.then(() => {
|
||||
done(() => {
|
||||
assert.deepEqual(deletedValues, [id]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('should remove data from icon object store', done => {
|
||||
attachDBTransaction('icon');
|
||||
metadata.remove(id)
|
||||
.then(() => {
|
||||
done(() => {
|
||||
assert.deepEqual(deletedValues, [id]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('AppsMetadata#getAll()', () => {
|
||||
test('should combine data from both object stores', done => {
|
||||
attachDBTransaction('order');
|
||||
metadata.getAll()
|
||||
.then(results => {
|
||||
done(() => {
|
||||
assert.equal(results.length, 2);
|
||||
assert.deepEqual(results[0],
|
||||
{ id: 'abc/', order: 0, icon: 'abc' });
|
||||
assert.deepEqual(results[1],
|
||||
{ id: 'def/', order: 1, icon: 'def' });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
457
gaia/apps/homescreen/test/unit/datastore_test.js
Executable file
457
gaia/apps/homescreen/test/unit/datastore_test.js
Executable file
|
|
@ -0,0 +1,457 @@
|
|||
/* 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
272
gaia/apps/homescreen/test/unit/firstrun_test.js
Executable file
272
gaia/apps/homescreen/test/unit/firstrun_test.js
Executable file
|
|
@ -0,0 +1,272 @@
|
|||
/* global FirstRun */
|
||||
'use strict';
|
||||
|
||||
require('/js/firstrun.js');
|
||||
|
||||
suite('FirstRun', () => {
|
||||
const COLS_PREF = 'grid.cols';
|
||||
|
||||
function testExpectedResults(results, expected) {
|
||||
var string = '';
|
||||
var order = 0;
|
||||
results.order.forEach(entry => {
|
||||
string += entry.id;
|
||||
assert.equal(entry.order, order++);
|
||||
});
|
||||
assert.equal(string, expected);
|
||||
}
|
||||
|
||||
suite('default settings', () => {
|
||||
var json;
|
||||
|
||||
setup(() => {
|
||||
window.LazyLoader = {
|
||||
getJSON: (file) => {
|
||||
assert.equal(file, 'js/init.json');
|
||||
return Promise.resolve(json);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
test('handles null JSON', (done) => {
|
||||
json = null;
|
||||
|
||||
FirstRun().then(results => {
|
||||
done(() => {
|
||||
assert.isTrue(Array.isArray(results.order));
|
||||
assert.equal(results.order.length, 0);
|
||||
assert.equal(results.small, false);
|
||||
});
|
||||
}, done);
|
||||
});
|
||||
|
||||
test('handles empty JSON', (done) => {
|
||||
json = {};
|
||||
|
||||
FirstRun().then(results => {
|
||||
done(() => {
|
||||
assert.isTrue(Array.isArray(results.order));
|
||||
assert.equal(results.order.length, 0);
|
||||
assert.equal(results.small, false);
|
||||
});
|
||||
}, done);
|
||||
});
|
||||
|
||||
test('handle null values in the JSON', (done) => {
|
||||
json = { grid:
|
||||
[[ null,
|
||||
{ manifestURL: '1' },
|
||||
null,
|
||||
null,
|
||||
{ manifestURL: '2', entry_point: '3' } ],
|
||||
[ { manifestURL: '4' } ]
|
||||
]};
|
||||
|
||||
FirstRun().then(results => {
|
||||
done(() => {
|
||||
testExpectedResults(results, '1/2/34/');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('treats columns <= 3 as default columns setting', (done) => {
|
||||
json = { preferences: {} };
|
||||
|
||||
json.preferences[COLS_PREF] = 2;
|
||||
FirstRun().then(results1 => {
|
||||
json.preferences[COLS_PREF] = 3;
|
||||
FirstRun().then(results2 => {
|
||||
done(() => {
|
||||
assert.equal(results1.small, false);
|
||||
assert.equal(results2.small, false);
|
||||
});
|
||||
}, done);
|
||||
}, done);
|
||||
});
|
||||
|
||||
test('treats columns > 3 as small columns setting', (done) => {
|
||||
json = { preferences: {} };
|
||||
json.preferences[COLS_PREF] = 4;
|
||||
|
||||
FirstRun().then(results => {
|
||||
done(() => {
|
||||
assert.equal(results.small, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('transforms grid into compatible format', (done) => {
|
||||
json = { grid:
|
||||
[[ { manifestURL: '1' },
|
||||
{ manifestURL: '2', entry_point: '3' } ],
|
||||
[ { manifestURL: '4' } ]
|
||||
]};
|
||||
|
||||
FirstRun().then(results => {
|
||||
done(() => {
|
||||
testExpectedResults(results, '1/2/34/');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('omits objects with no manifestURL', (done) => {
|
||||
json = { grid:
|
||||
[[ { manifestURL: '1' },
|
||||
{ manifestURL: '2', entry_point: '3' } ],
|
||||
[ { invalidObject: true } ],
|
||||
[ { manifestURL: '4' } ]
|
||||
]};
|
||||
|
||||
FirstRun().then(results => {
|
||||
done(() => {
|
||||
testExpectedResults(results, '1/2/34/');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('verticalhome import', () => {
|
||||
const VERTICALHOME_ITEMS = 'verticalhome_items_migration';
|
||||
|
||||
const VERTICALHOME_PREFS = 'vertical_preferences_store';
|
||||
|
||||
const HOMESCREEN_PREFS = 'homescreen_settings';
|
||||
|
||||
var realNavigatorDatastores, realConsoleWarn;
|
||||
var itemsDatastore, verticalPrefsDatastore, homescreenPrefsDatastore,
|
||||
noDatastore;
|
||||
|
||||
setup(() => {
|
||||
realNavigatorDatastores = navigator.getDataStores;
|
||||
|
||||
noDatastore = false;
|
||||
navigator.getDataStores = name => {
|
||||
assert.isTrue((name === VERTICALHOME_ITEMS) ||
|
||||
(name === VERTICALHOME_PREFS) ||
|
||||
(name === HOMESCREEN_PREFS));
|
||||
|
||||
if (noDatastore) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
switch(name) {
|
||||
case VERTICALHOME_ITEMS:
|
||||
return Promise.resolve([itemsDatastore]);
|
||||
case VERTICALHOME_PREFS:
|
||||
return Promise.resolve([verticalPrefsDatastore]);
|
||||
case HOMESCREEN_PREFS:
|
||||
return Promise.resolve([homescreenPrefsDatastore]);
|
||||
}
|
||||
};
|
||||
|
||||
// Initialise the three datastores as fake empty datastores
|
||||
itemsDatastore = verticalPrefsDatastore = homescreenPrefsDatastore = {
|
||||
getLength: () => { return Promise.resolve(0); },
|
||||
get: () => { return Promise.reject(); }
|
||||
};
|
||||
|
||||
// Overload console.warn to disable FirstRun's built-in error fallback
|
||||
realConsoleWarn = console.warn;
|
||||
console.warn = (message, error) => {
|
||||
throw(error);
|
||||
};
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
navigator.getDataStores = realNavigatorDatastores;
|
||||
console.warn = realConsoleWarn;
|
||||
});
|
||||
|
||||
test('handles inaccessible datastore', done => {
|
||||
noDatastore = true;
|
||||
FirstRun().then(() => {
|
||||
done('should not succeed with inaccessible datastore');
|
||||
}, (e) => {
|
||||
done(() => {
|
||||
assert.equal(e, 'verticalhome datastores inaccessible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('handles empty datastore', done => {
|
||||
FirstRun().then(() => {
|
||||
done('should not succeed with empty datastore');
|
||||
}, (e) => {
|
||||
done(() => {
|
||||
assert.equal(e, 'No verticalhome app order to import');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function populateItemStore() {
|
||||
var items = {
|
||||
'2': { index: '2', type: 'app', manifestURL: '2', entryPoint: '3' },
|
||||
'1': { index: '1', type: 'app', manifestURL: '1' },
|
||||
'4': { index: '4', type: 'divider' },
|
||||
'6': { index: '6', type: 'bookmark', id: '4' }
|
||||
};
|
||||
var keys = Object.getOwnPropertyNames(items);
|
||||
var length = keys.length;
|
||||
|
||||
itemsDatastore = {
|
||||
getLength: () => { return Promise.resolve(length); },
|
||||
sync: () => {
|
||||
var i = 0;
|
||||
return {
|
||||
next: () => {
|
||||
return Promise.resolve((i++ < length) ? {
|
||||
operation: 'put',
|
||||
id: keys[i - 1],
|
||||
data: items[keys[i - 1]]
|
||||
} : {
|
||||
operation: 'done'
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test('converts verticalhome format to homescreen format', done => {
|
||||
populateItemStore();
|
||||
|
||||
FirstRun().then(results => {
|
||||
done(() => {
|
||||
testExpectedResults(results, '1/2/34');
|
||||
});
|
||||
}, done);
|
||||
});
|
||||
|
||||
test('reads column preferences', done => {
|
||||
populateItemStore();
|
||||
verticalPrefsDatastore.get = pref => {
|
||||
return (pref === COLS_PREF) ?
|
||||
Promise.resolve(4) : Promise.reject();
|
||||
};
|
||||
|
||||
FirstRun().then(results => {
|
||||
done(() => {
|
||||
testExpectedResults(results, '1/2/34');
|
||||
assert.isTrue(results.small);
|
||||
});
|
||||
}, done);
|
||||
});
|
||||
|
||||
test('stores column preference if cols > 3', done => {
|
||||
populateItemStore();
|
||||
verticalPrefsDatastore.get = pref => {
|
||||
return (pref === COLS_PREF) ?
|
||||
Promise.resolve(4) : Promise.reject();
|
||||
};
|
||||
homescreenPrefsDatastore.put = (pref, id) => {
|
||||
done(() => {
|
||||
assert.equal(id, COLS_PREF);
|
||||
assert.equal(pref, 4);
|
||||
});
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
FirstRun().catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
19
gaia/apps/homescreen/test/unit/mocks/mock_apps.js
Executable file
19
gaia/apps/homescreen/test/unit/mocks/mock_apps.js
Executable file
|
|
@ -0,0 +1,19 @@
|
|||
/* exported Apps */
|
||||
'use strict';
|
||||
|
||||
(function(exports) {
|
||||
|
||||
function Apps() {
|
||||
this.panel = document.getElementById('apps-panel');
|
||||
this.scrollable = document.querySelector('#apps-panel .scrollable');
|
||||
this.editMode = false;
|
||||
this.dialogs = [];
|
||||
}
|
||||
|
||||
Apps.prototype = {
|
||||
exitEditMode: function() {}
|
||||
};
|
||||
|
||||
exports.Apps = Apps;
|
||||
|
||||
})(window);
|
||||
42
gaia/apps/homescreen/test/unit/mocks/mock_appsmetadata.js
Executable file
42
gaia/apps/homescreen/test/unit/mocks/mock_appsmetadata.js
Executable file
|
|
@ -0,0 +1,42 @@
|
|||
/* exported AppsMetadata */
|
||||
'use strict';
|
||||
|
||||
(function(exports) {
|
||||
|
||||
let _data = [];
|
||||
|
||||
function AppsMetadata() {
|
||||
}
|
||||
|
||||
AppsMetadata.prototype = {
|
||||
db: null,
|
||||
|
||||
init: () => {
|
||||
return Promise.resolve();
|
||||
},
|
||||
upgradeSchema: () => {},
|
||||
set: data=> {
|
||||
_data = data;
|
||||
return Promise.resolve();
|
||||
},
|
||||
remove: () => {
|
||||
return Promise.resolve();
|
||||
},
|
||||
getAll: () => {
|
||||
return Promise.resolve(_data);
|
||||
},
|
||||
|
||||
get _data() {
|
||||
return _data;
|
||||
},
|
||||
set _data(data) {
|
||||
_data = data;
|
||||
},
|
||||
mSetup: () => {
|
||||
_data = [];
|
||||
}
|
||||
};
|
||||
|
||||
exports.AppsMetadata = AppsMetadata;
|
||||
|
||||
})(window);
|
||||
58
gaia/apps/homescreen/test/unit/mocks/mock_datastore.js
Executable file
58
gaia/apps/homescreen/test/unit/mocks/mock_datastore.js
Executable file
|
|
@ -0,0 +1,58 @@
|
|||
/* exported Datastore */
|
||||
'use strict';
|
||||
|
||||
(function(exports) {
|
||||
|
||||
let _name = '';
|
||||
|
||||
function Datastore(name) {
|
||||
_name = name;
|
||||
}
|
||||
|
||||
Datastore.prototype = {
|
||||
init: () => {
|
||||
return Promise.resolve();
|
||||
},
|
||||
upgradeSchema: () => {
|
||||
return Promise.resolve();
|
||||
},
|
||||
synchronise: () => {
|
||||
return Promise.resolve();
|
||||
},
|
||||
set: (data) => {
|
||||
return Promise.resolve();
|
||||
},
|
||||
remove: (id) => {
|
||||
return Promise.resolve();
|
||||
},
|
||||
clear: () => {
|
||||
return Promise.resolve();
|
||||
},
|
||||
updateRevision: () => {
|
||||
return Promise.resolve();
|
||||
},
|
||||
onChange: () => {},
|
||||
get: (id) => {
|
||||
return Promise.resolve();
|
||||
},
|
||||
getAll: () => {
|
||||
return Promise.resolve([]);
|
||||
},
|
||||
datastore: {
|
||||
put: (data, id) => Promise.resolve()
|
||||
},
|
||||
|
||||
get _name() {
|
||||
return _name;
|
||||
},
|
||||
set _name(name) {
|
||||
_name = name;
|
||||
},
|
||||
mSetup: () => {
|
||||
_name = [];
|
||||
}
|
||||
};
|
||||
|
||||
exports.Datastore = Datastore;
|
||||
|
||||
})(window);
|
||||
26
gaia/apps/homescreen/test/unit/mocks/mock_localStorage.js
Executable file
26
gaia/apps/homescreen/test/unit/mocks/mock_localStorage.js
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
/* exported mockLocalStorage */
|
||||
'use strict';
|
||||
|
||||
var _rawContent = {};
|
||||
|
||||
var mockLocalStorage = {
|
||||
setItem: (key, val) => {
|
||||
_rawContent[key] = val;
|
||||
},
|
||||
getItem: key => {
|
||||
return _rawContent[key];
|
||||
},
|
||||
removeItem: key => {
|
||||
delete _rawContent[key];
|
||||
},
|
||||
|
||||
set mRawContent(val) {
|
||||
_rawContent = val;
|
||||
},
|
||||
get mRawContent() {
|
||||
return _rawContent;
|
||||
},
|
||||
mSetup: () => {
|
||||
_rawContent = {};
|
||||
}
|
||||
};
|
||||
19
gaia/apps/homescreen/test/unit/mocks/mock_pages.js
Executable file
19
gaia/apps/homescreen/test/unit/mocks/mock_pages.js
Executable file
|
|
@ -0,0 +1,19 @@
|
|||
/* exported Pages */
|
||||
'use strict';
|
||||
|
||||
(function(exports) {
|
||||
|
||||
function Pages() {
|
||||
this.panel = document.getElementById('pages-panel');
|
||||
this.scrollable = document.querySelector('#pages-panel .scrollable');
|
||||
this.editMode = false;
|
||||
this.dialogs = [];
|
||||
}
|
||||
|
||||
Pages.prototype = {
|
||||
exitEditMode: function() {}
|
||||
};
|
||||
|
||||
exports.Pages = Pages;
|
||||
|
||||
})(window);
|
||||
17
gaia/apps/homescreen/test/unit/mocks/mock_settings.js
Executable file
17
gaia/apps/homescreen/test/unit/mocks/mock_settings.js
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
/* exported Settings */
|
||||
'use strict';
|
||||
|
||||
(function(exports) {
|
||||
|
||||
function Settings() {
|
||||
this.small = false;
|
||||
this.scrollSnapping = false;
|
||||
this.firstRun = false;
|
||||
}
|
||||
|
||||
Settings.prototype = {
|
||||
};
|
||||
|
||||
exports.Settings = Settings;
|
||||
|
||||
})(window);
|
||||
414
gaia/apps/homescreen/test/unit/pages_test.js
Executable file
414
gaia/apps/homescreen/test/unit/pages_test.js
Executable file
|
|
@ -0,0 +1,414 @@
|
|||
/* jshint nonew: false */
|
||||
/* global Pages, PagesStore */
|
||||
'use strict';
|
||||
|
||||
require('/shared/test/unit/load_body_html_helper.js');
|
||||
require('mocks/mock_datastore.js');
|
||||
require('/js/pages.js');
|
||||
|
||||
suite('Pages', () => {
|
||||
var pages;
|
||||
|
||||
setup(() => {
|
||||
window.PagesStore = window.Datastore;
|
||||
loadBodyHTML('_index.html');
|
||||
pages = new Pages();
|
||||
});
|
||||
|
||||
suite('Pages constructor', () => {
|
||||
var stub;
|
||||
|
||||
teardown(() => {
|
||||
stub.restore();
|
||||
});
|
||||
|
||||
test('should initialise the pages store', done => {
|
||||
stub = sinon.stub(PagesStore.prototype, 'init', () => {
|
||||
done();
|
||||
return Promise.reject();
|
||||
});
|
||||
new Pages();
|
||||
});
|
||||
|
||||
test('should get the list of pinned pages', done => {
|
||||
stub = sinon.stub(PagesStore.prototype, 'getAll', () => {
|
||||
done();
|
||||
return Promise.reject();
|
||||
});
|
||||
new Pages();
|
||||
});
|
||||
|
||||
test('should mark the pages panel empty if no pages', done => {
|
||||
stub = sinon.stub(PagesStore.prototype, 'getAll', () => {
|
||||
return { then: callback => {
|
||||
callback([]);
|
||||
done(() => {
|
||||
assert.isTrue(document.getElementById('pages-panel').classList.
|
||||
contains('empty'));
|
||||
});
|
||||
}};
|
||||
});
|
||||
new Pages();
|
||||
});
|
||||
});
|
||||
|
||||
suite('Pages#addPinnedPage()', () => {
|
||||
var pinCard;
|
||||
var createElementStub;
|
||||
var updatePinnedPageStub;
|
||||
var appendChildStub;
|
||||
|
||||
setup(() => {
|
||||
pinCard = document.createElement('div');
|
||||
createElementStub = sinon.stub(document, 'createElement', () => pinCard);
|
||||
updatePinnedPageStub = sinon.stub(pages, 'updatePinnedPage');
|
||||
appendChildStub = sinon.stub(pages.pages, 'appendChild');
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
createElementStub.restore();
|
||||
updatePinnedPageStub.restore();
|
||||
appendChildStub.restore();
|
||||
});
|
||||
|
||||
test('should remove .empty from the pages panel', () => {
|
||||
var pagesPanel = document.getElementById('pages-panel');
|
||||
assert.isTrue(pages.empty);
|
||||
assert.isTrue(pagesPanel.classList.contains('empty'));
|
||||
pages.addPinnedPage();
|
||||
assert.isFalse(pages.empty);
|
||||
assert.isFalse(pagesPanel.classList.contains('empty'));
|
||||
});
|
||||
|
||||
test('should add a card to the pages list', () => {
|
||||
pages.addPinnedPage();
|
||||
assert.isTrue(createElementStub.calledWith('gaia-pin-card'));
|
||||
assert.isTrue(updatePinnedPageStub.calledOnce);
|
||||
assert.isTrue(appendChildStub.calledOnce);
|
||||
});
|
||||
|
||||
test('should make card accessible', () => {
|
||||
pages.addPinnedPage();
|
||||
assert.equal(pinCard.tabIndex, 0);
|
||||
assert.equal(pinCard.getAttribute('role'), 'link');
|
||||
});
|
||||
});
|
||||
|
||||
suite('Pages#updatePinnedPage()', () => {
|
||||
var clock;
|
||||
var createObjectURLStub;
|
||||
|
||||
var mockPinnedPage = {
|
||||
title: 'Mock pinned page',
|
||||
url: 'Mock URL',
|
||||
pinTime: 4242,
|
||||
screenshot: 'Mock screenshot',
|
||||
meta: {
|
||||
'theme-color': 'Mock color'
|
||||
}
|
||||
};
|
||||
|
||||
var mockCard = {
|
||||
title: null,
|
||||
dataset: { id: null },
|
||||
style: { order: 0 },
|
||||
background: null,
|
||||
meta: null,
|
||||
icon: null
|
||||
};
|
||||
|
||||
var mockIconBlob = 'Mock icon blob';
|
||||
|
||||
setup(() => {
|
||||
clock = sinon.useFakeTimers();
|
||||
|
||||
createObjectURLStub = sinon.stub(URL, 'createObjectURL', blob => blob);
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
clock.restore();
|
||||
createObjectURLStub.restore();
|
||||
});
|
||||
|
||||
test('should set title, url and order', () => {
|
||||
pages.updatePinnedPage(mockCard, mockPinnedPage);
|
||||
assert.equal(mockCard.title, mockPinnedPage.title);
|
||||
assert.equal(mockCard.dataset.id, mockPinnedPage.url);
|
||||
var pinTime = -Math.round(mockPinnedPage.pinTime / 1000);
|
||||
assert.equal(mockCard.style.order, pinTime);
|
||||
});
|
||||
|
||||
test('should set screenshot, themeColor and icon after timeout', done => {
|
||||
window.IconsHelper = {
|
||||
getIconBlob: (url, size, page) => {
|
||||
return {
|
||||
then: (callback) => {
|
||||
callback({ blob: mockIconBlob });
|
||||
assert.equal(mockCard.icon, 'url(' + mockIconBlob + ')');
|
||||
done();
|
||||
return { catch: () => {} };
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pages.updatePinnedPage(mockCard, mockPinnedPage);
|
||||
clock.tick();
|
||||
var meta = mockCard.meta;
|
||||
assert.equal(meta['theme-color'], mockPinnedPage.meta['theme-color']);
|
||||
assert.equal(meta.screenshot, mockPinnedPage.screenshot);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Pages#enterEditMode()', () => {
|
||||
var mockCard;
|
||||
setup(() => {
|
||||
mockCard = document.createElement('div');
|
||||
pages.enterEditMode(mockCard);
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
pages.exitEditMode();
|
||||
});
|
||||
|
||||
test('card is marked as selected', () => {
|
||||
assert.isTrue(mockCard.classList.contains('selected'));
|
||||
});
|
||||
|
||||
test('document is marked as in edit-mode', () => {
|
||||
assert.isTrue(document.body.classList.contains('edit-mode'));
|
||||
});
|
||||
|
||||
test('delete button is activated', () => {
|
||||
assert.isTrue(pages.remove.classList.contains('active'));
|
||||
});
|
||||
|
||||
test('previous card is unselected', () => {
|
||||
var mockCard2 = document.createElement('div');
|
||||
pages.enterEditMode(mockCard2);
|
||||
assert.isFalse(mockCard.classList.contains('selected'));
|
||||
assert.isTrue(mockCard2.classList.contains('selected'));
|
||||
});
|
||||
});
|
||||
|
||||
suite('Pages#exitEditMode()', () => {
|
||||
var mockCard;
|
||||
setup(() => {
|
||||
mockCard = document.createElement('div');
|
||||
pages.enterEditMode(mockCard);
|
||||
pages.exitEditMode();
|
||||
});
|
||||
|
||||
test('card is not marked as selected', () => {
|
||||
assert.isFalse(mockCard.classList.contains('selected'));
|
||||
assert.equal(pages.selectedCard, null);
|
||||
});
|
||||
|
||||
test('document is not marked as in edit-mode', () => {
|
||||
assert.isFalse(document.body.classList.contains('edit-mode'));
|
||||
});
|
||||
|
||||
test('delete button is not activated', () => {
|
||||
assert.isFalse(pages.remove.classList.contains('active'));
|
||||
});
|
||||
});
|
||||
|
||||
suite('Pages#removeCard()', () => {
|
||||
var mockCard;
|
||||
setup(() => {
|
||||
mockCard = document.createElement('div');
|
||||
});
|
||||
|
||||
test('removes child from container', () => {
|
||||
var removeStub = sinon.stub(pages.pages, 'removeChild');
|
||||
pages.removeCard(mockCard);
|
||||
assert.isTrue(removeStub.calledWith(mockCard));
|
||||
});
|
||||
|
||||
test('deselects card if card is selected', () => {
|
||||
pages.selectedCard = mockCard;
|
||||
var removeStub = sinon.stub(pages.pages, 'removeChild');
|
||||
pages.removeCard(mockCard);
|
||||
assert.equal(pages.selectedCard, null);
|
||||
removeStub.restore();
|
||||
});
|
||||
|
||||
test('shows the empty pinned pages panel when removing last card', done => {
|
||||
pages.empty = false;
|
||||
Object.defineProperty(pages.pages, 'children', {
|
||||
value: { length: 0 },
|
||||
configurable: true
|
||||
});
|
||||
var removeChildStub = sinon.stub(pages.pages, 'removeChild',
|
||||
(card, callback) => {
|
||||
callback();
|
||||
done(() => {
|
||||
delete pages.pages.children;
|
||||
removeChildStub.restore();
|
||||
assert.isTrue(pages.empty);
|
||||
assert.isTrue(pages.panel.classList.contains('empty'));
|
||||
});
|
||||
});
|
||||
pages.removeCard(mockCard);
|
||||
});
|
||||
|
||||
test('exits edit mode when removing last card', done => {
|
||||
pages.empty = false;
|
||||
pages.editMode = true;
|
||||
Object.defineProperty(pages.pages, 'children', {
|
||||
value: { length: 0 },
|
||||
configurable: true
|
||||
});
|
||||
var removeChildStub = sinon.stub(pages.pages, 'removeChild',
|
||||
(card, callback) => {
|
||||
callback();
|
||||
done(() => {
|
||||
delete pages.pages.children;
|
||||
removeChildStub.restore();
|
||||
assert.isFalse(pages.editMode);
|
||||
});
|
||||
});
|
||||
pages.removeCard(mockCard);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Pages#unpinSelectedCard()', () => {
|
||||
var removeCardStub, storeGetStub, storePutStub, mockEntry, callback;
|
||||
var mockCard = {
|
||||
dataset: {
|
||||
id: 'abc'
|
||||
}
|
||||
};
|
||||
|
||||
setup(() => {
|
||||
mockEntry = { data: { pinned: true } };
|
||||
removeCardStub = sinon.stub(pages, 'removeCard');
|
||||
storeGetStub =
|
||||
sinon.stub(pages.pagesStore, 'get', () => {
|
||||
return Promise.resolve(mockEntry);
|
||||
});
|
||||
storePutStub =
|
||||
sinon.stub(pages.pagesStore.datastore, 'put', (data, id) => {
|
||||
if (callback) {
|
||||
callback(data, id);
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
pages.selectedCard = mockCard;
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
removeCardStub.restore();
|
||||
storeGetStub.restore();
|
||||
storePutStub.restore();
|
||||
pages.selectedCard = null;
|
||||
});
|
||||
|
||||
test('removes card from container', () => {
|
||||
pages.unpinSelectedCard();
|
||||
assert.isTrue(removeCardStub.calledWith(mockCard));
|
||||
});
|
||||
|
||||
test('unpins card in datastore', done => {
|
||||
callback = (data, id) => {
|
||||
done(() => {
|
||||
assert.equal(id, mockCard.dataset.id);
|
||||
assert.isFalse(mockEntry.data.pinned);
|
||||
});
|
||||
};
|
||||
pages.unpinSelectedCard();
|
||||
});
|
||||
});
|
||||
|
||||
suite('Pages#handleEvent()', () => {
|
||||
var mockCard = {
|
||||
nodeName: 'GAIA-PIN-CARD',
|
||||
title: 'Mock title',
|
||||
icon: 'Mock icon',
|
||||
dataset: {
|
||||
id: 'Mock URL'
|
||||
}
|
||||
};
|
||||
|
||||
var mockNotCard = {
|
||||
nodeName: 'MOCK-NOT-CARD'
|
||||
};
|
||||
|
||||
suite('click', () => {
|
||||
test('clicking a card should launch a remote window', () => {
|
||||
var windowOpenStub = sinon.stub(window, 'open',
|
||||
(url, target, features) => {
|
||||
assert.equal(url, mockCard.dataset.id);
|
||||
assert.equal(target, '_blank');
|
||||
|
||||
var expectedFeatures = {
|
||||
name: mockCard.title,
|
||||
icon: mockCard.icon,
|
||||
remote: true
|
||||
};
|
||||
expectedFeatures = Object.keys(expectedFeatures).
|
||||
map(function eachFeature(key) {
|
||||
return encodeURIComponent(key) + '=' +
|
||||
encodeURIComponent(expectedFeatures[key]);
|
||||
}).join(',');
|
||||
|
||||
assert.equal(features, expectedFeatures);
|
||||
});
|
||||
pages.handleEvent({ type: 'click', target: mockCard });
|
||||
windowOpenStub.restore();
|
||||
});
|
||||
|
||||
test('clicking outside a card should do nothing', () => {
|
||||
var windowOpenStub = sinon.stub(window, 'open');
|
||||
pages.handleEvent({ type: 'click', target: mockNotCard });
|
||||
assert.isFalse(windowOpenStub.called);
|
||||
windowOpenStub.restore();
|
||||
});
|
||||
|
||||
test('pressing a card in edit mode selects it', () => {
|
||||
var editModeStub = sinon.stub(pages, 'enterEditMode');
|
||||
pages.editMode = true;
|
||||
pages.handleEvent({ type: 'click', target: mockCard });
|
||||
assert.isTrue(editModeStub.calledWith(mockCard));
|
||||
editModeStub.restore();
|
||||
});
|
||||
|
||||
test('pressing delete in edit mode unpins a card', () => {
|
||||
var unpinStub = sinon.stub(pages, 'unpinSelectedCard');
|
||||
pages.selectedCard = mockCard;
|
||||
pages.remove.dispatchEvent(new CustomEvent('click'));
|
||||
assert.isTrue(unpinStub.called);
|
||||
});
|
||||
});
|
||||
|
||||
suite('contextmenu', () => {
|
||||
var editModeStub;
|
||||
setup(() => {
|
||||
editModeStub = sinon.stub(pages, 'enterEditMode');
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
editModeStub.restore();
|
||||
});
|
||||
|
||||
test('long-pressing a card initiates edit mode', () => {
|
||||
pages.handleEvent({ type: 'contextmenu', target: mockCard });
|
||||
assert.isTrue(editModeStub.calledWith(mockCard));
|
||||
});
|
||||
|
||||
test('long-pressing not on a card does nothing', () => {
|
||||
pages.handleEvent({ type: 'contextmenu', target: mockNotCard });
|
||||
assert.isFalse(editModeStub.called);
|
||||
});
|
||||
});
|
||||
|
||||
suite('resize', () => {
|
||||
test('should call pages.synchronise()', () => {
|
||||
var synchroniseCalled = false;
|
||||
pages.pages.synchronise = () => { synchroniseCalled = true; };
|
||||
pages.handleEvent(new CustomEvent('resize'));
|
||||
assert.isTrue(synchroniseCalled);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
197
gaia/apps/homescreen/test/unit/pagesstore_test.js
Executable file
197
gaia/apps/homescreen/test/unit/pagesstore_test.js
Executable file
|
|
@ -0,0 +1,197 @@
|
|||
/* global MockIndexedDB, MockNavigatorDatastore, mockLocalStorage, PagesStore */
|
||||
'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');
|
||||
require('/js/pagesstore.js');
|
||||
|
||||
suite('PagesStore', () => {
|
||||
var mockIndexedDB;
|
||||
var realNavigatorDatastores;
|
||||
var realLocalStorage;
|
||||
var datastoreName = 'pagesstore_name_example';
|
||||
var pagesstore;
|
||||
|
||||
setup(() => {
|
||||
mockIndexedDB = new MockIndexedDB();
|
||||
mockLocalStorage.mSetup();
|
||||
Object.defineProperty(window, 'localStorage', {
|
||||
configurable: true,
|
||||
get: () => mockLocalStorage
|
||||
});
|
||||
realNavigatorDatastores = navigator.getDataStores;
|
||||
navigator.getDataStores = MockNavigatorDatastore.getDataStores;
|
||||
|
||||
pagesstore = new PagesStore(datastoreName);
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
mockIndexedDB.mTearDown();
|
||||
Object.defineProperty(window, 'localStorage', {
|
||||
configurable: true,
|
||||
get: () => realLocalStorage
|
||||
});
|
||||
navigator.getDataStores = realNavigatorDatastores;
|
||||
|
||||
pagesstore = null;
|
||||
});
|
||||
|
||||
suite('PagesStore constructor', () => {
|
||||
test('should store the name of the datastore', () => {
|
||||
assert.equal(pagesstore.name, datastoreName);
|
||||
assert.isTrue(pagesstore.revisionName.includes(datastoreName));
|
||||
assert.isTrue(pagesstore.idbName.includes(datastoreName));
|
||||
assert.isTrue(pagesstore.storeName.includes(datastoreName));
|
||||
});
|
||||
});
|
||||
|
||||
suite('indexedDB backed methods', () => {
|
||||
var data1 = {
|
||||
url: 'http://www.theguardian.com/uk/culture',
|
||||
title: 'Culture | The Guardian',
|
||||
icons: {},
|
||||
frecency: 4,
|
||||
visits: [1440494813199],
|
||||
screenshot: null,
|
||||
themeColor: null,
|
||||
visited: 1440494813199,
|
||||
pinned: true,
|
||||
pinTime: 1440426834317,
|
||||
id: 'http://www.theguardian.com/uk/culture'
|
||||
};
|
||||
var data2 = {
|
||||
url: 'http://www.cnet.com/',
|
||||
title: 'Product reviews and prices, software downloads, and tech news',
|
||||
icons: {},
|
||||
frecency: 1,
|
||||
visits: [1440427988417],
|
||||
screenshot: null,
|
||||
themeColor: null,
|
||||
visited: 1440427988417,
|
||||
id: 'http://www.cnet.com/'
|
||||
};
|
||||
var objectStores = {};
|
||||
var deletedValues = [];
|
||||
var attachDBTransaction = objectStoreName => {
|
||||
objectStores = {};
|
||||
deletedValues = [];
|
||||
pagesstore.db.transaction = (objectStoreNames, IDBTransactionMode) => {
|
||||
return {
|
||||
objectStore: name => {
|
||||
return {
|
||||
put: value => {
|
||||
if (name !== objectStoreName) {
|
||||
return;
|
||||
}
|
||||
objectStores[name] = value;
|
||||
},
|
||||
delete: value => {
|
||||
if (name !== objectStoreName) {
|
||||
return;
|
||||
}
|
||||
deletedValues.push(value);
|
||||
},
|
||||
clear: () => {
|
||||
if (name !== objectStoreName) {
|
||||
return;
|
||||
}
|
||||
},
|
||||
get: () => {
|
||||
if (name !== objectStoreName) {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
set onsuccess(cb) {
|
||||
cb({
|
||||
target: {
|
||||
result: data1
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
},
|
||||
openCursor: () => {
|
||||
return {
|
||||
set onsuccess(cb) {
|
||||
if (objectStoreNames.indexOf(name) === -1) {
|
||||
return;
|
||||
}
|
||||
cb({
|
||||
target: {
|
||||
result: {
|
||||
value: { id: data1.url, data: data1 },
|
||||
continue: () => {
|
||||
cb({
|
||||
target: {
|
||||
result: {
|
||||
value: { id: data2.url, data: data2 },
|
||||
continue: () => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
},
|
||||
set oncomplete(cb) {
|
||||
cb();
|
||||
},
|
||||
onerror: () => {
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
setup(() => {
|
||||
pagesstore.db = {};
|
||||
});
|
||||
|
||||
suite('PagesStore#set()', () => {
|
||||
setup(() => {
|
||||
attachDBTransaction(pagesstore.storeName);
|
||||
});
|
||||
|
||||
test('should use the url as the id', done => {
|
||||
pagesstore.set(data1)
|
||||
.then(() => {
|
||||
assert.equal(Object.keys(objectStores).length, 1);
|
||||
assert.equal(objectStores[pagesstore.storeName].id, data1.url);
|
||||
assert.equal(objectStores[pagesstore.storeName].data, data1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('should emit an event', done => {
|
||||
document.addEventListener(pagesstore.name + '-set', evt => {
|
||||
assert.equal(evt.detail.id, data1.id);
|
||||
done();
|
||||
});
|
||||
pagesstore.set(data1);
|
||||
});
|
||||
});
|
||||
|
||||
suite.only('PagesStore#getAll()', () => {
|
||||
test('should not accept any argument', () => {
|
||||
assert.equal(pagesstore.getAll.length, 0);
|
||||
});
|
||||
|
||||
test('should retrieve data from object store', done => {
|
||||
attachDBTransaction(pagesstore.storeName);
|
||||
pagesstore.getAll()
|
||||
.then(results => {
|
||||
assert.equal(results.length, 1);
|
||||
assert.deepEqual(results[0].id, data1.url);
|
||||
assert.deepEqual(results[0].data, data1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
238
gaia/apps/homescreen/test/unit/settings_test.js
Executable file
238
gaia/apps/homescreen/test/unit/settings_test.js
Executable file
|
|
@ -0,0 +1,238 @@
|
|||
/* global mockLocalStorage, MockNavigatorDatastore, MockDatastore, Settings */
|
||||
/* jshint nonew: false */
|
||||
'use strict';
|
||||
|
||||
require('mocks/mock_localStorage.js');
|
||||
require('/shared/test/unit/mocks/mock_navigator_datastore.js');
|
||||
require('/js/settings.js');
|
||||
|
||||
suite('Settings', () => {
|
||||
const DEFAULT_SETTINGS = '{"version":0,"small":false,"scrollSnapping":false}';
|
||||
|
||||
var realLocalStorage;
|
||||
var realNavigatorDatastores;
|
||||
|
||||
setup(() => {
|
||||
realNavigatorDatastores = navigator.getDataStores;
|
||||
navigator.getDataStores = MockNavigatorDatastore.getDataStores;
|
||||
|
||||
realLocalStorage = Object.getOwnPropertyDescriptor(window, 'localStorage');
|
||||
Object.defineProperty(window, 'localStorage', {
|
||||
configurable: true,
|
||||
get: () => mockLocalStorage
|
||||
});
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
navigator.getDataStores = realNavigatorDatastores;
|
||||
Object.defineProperty(window, 'localStorage', realLocalStorage);
|
||||
});
|
||||
|
||||
suite('Settings constructor', () => {
|
||||
test('attempts to retrieve existing settings', () => {
|
||||
var getItemStub = sinon.stub(window.localStorage, 'getItem');
|
||||
new Settings();
|
||||
assert.isTrue(getItemStub.calledWith('settings'));
|
||||
getItemStub.restore();
|
||||
});
|
||||
|
||||
test('sets firstRun to true if no settings', () => {
|
||||
mockLocalStorage.removeItem('settings');
|
||||
var settings = new Settings();
|
||||
assert.isTrue(settings.firstRun);
|
||||
});
|
||||
|
||||
suite('settings exist', () => {
|
||||
setup(() => {
|
||||
mockLocalStorage.setItem('settings', DEFAULT_SETTINGS);
|
||||
});
|
||||
|
||||
test('firstRun is false', () => {
|
||||
var settings = new Settings();
|
||||
assert.isFalse(settings.firstRun);
|
||||
});
|
||||
|
||||
test('restores settings', () => {
|
||||
var settings = new Settings();
|
||||
assert.isFalse(settings.small);
|
||||
assert.isFalse(settings.scrollSnapping);
|
||||
|
||||
mockLocalStorage.setItem('settings',
|
||||
'{"version":0,"small":true,"scrollSnapping":true}');
|
||||
settings = new Settings();
|
||||
assert.isTrue(settings.small);
|
||||
assert.isTrue(settings.scrollSnapping);
|
||||
});
|
||||
|
||||
test('should not restore data if version number differs', () => {
|
||||
mockLocalStorage.setItem('settings', '{"version":500,"small":true}');
|
||||
var settings = new Settings();
|
||||
assert.isFalse(settings.small);
|
||||
});
|
||||
|
||||
test('opens settings datastore', () => {
|
||||
var getDataStoresStub = sinon.stub(navigator, 'getDataStores',
|
||||
() => Promise.reject());
|
||||
new Settings();
|
||||
assert.isTrue(getDataStoresStub.calledWith('homescreen_settings'));
|
||||
getDataStoresStub.restore();
|
||||
});
|
||||
|
||||
suite('datastore settings', () => {
|
||||
var target;
|
||||
var cols;
|
||||
var page;
|
||||
var afterGetCall;
|
||||
var datastoreGetStub;
|
||||
|
||||
setup(() => {
|
||||
cols = 3;
|
||||
page = false;
|
||||
|
||||
datastoreGetStub = sinon.stub(MockDatastore, 'get', id => {
|
||||
var returnData;
|
||||
var returnObject = {
|
||||
then: callback => {
|
||||
callback(returnData);
|
||||
if (id === target) {
|
||||
afterGetCall();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
switch (id) {
|
||||
case 'grid.cols':
|
||||
returnData = cols;
|
||||
break;
|
||||
|
||||
case 'grid.paging':
|
||||
returnData = page;
|
||||
break;
|
||||
}
|
||||
return returnObject;
|
||||
});
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
datastoreGetStub.restore();
|
||||
});
|
||||
|
||||
test('restores columns setting', done => {
|
||||
var settings;
|
||||
|
||||
target = 'grid.cols';
|
||||
afterGetCall = () => {
|
||||
try {
|
||||
assert.isFalse(settings.small);
|
||||
} catch(e) {
|
||||
done(e);
|
||||
}
|
||||
|
||||
afterGetCall = () => {
|
||||
done(() => {
|
||||
assert.isTrue(settings.small);
|
||||
});
|
||||
};
|
||||
|
||||
cols = 4;
|
||||
settings = new Settings();
|
||||
};
|
||||
|
||||
settings = new Settings();
|
||||
});
|
||||
|
||||
test('responds to columns setting change', done => {
|
||||
var settings;
|
||||
|
||||
target = 'grid.cols';
|
||||
afterGetCall = () => {
|
||||
try {
|
||||
assert.isFalse(settings.small);
|
||||
} catch(e) {
|
||||
done(e);
|
||||
}
|
||||
|
||||
var dispatchEventStub = sinon.stub(window, 'dispatchEvent');
|
||||
|
||||
afterGetCall = () => {
|
||||
done(() => {
|
||||
assert.isTrue(dispatchEventStub.called);
|
||||
assert.isTrue(settings.small);
|
||||
dispatchEventStub.restore();
|
||||
});
|
||||
};
|
||||
|
||||
cols = 4;
|
||||
MockDatastore._cb({ id: 'grid.cols' });
|
||||
};
|
||||
|
||||
settings = new Settings();
|
||||
});
|
||||
|
||||
test('restores paging setting', done => {
|
||||
var settings;
|
||||
|
||||
target = 'grid.paging';
|
||||
afterGetCall = () => {
|
||||
try {
|
||||
assert.isFalse(settings.scrollSnapping);
|
||||
} catch(e) {
|
||||
done(e);
|
||||
}
|
||||
|
||||
afterGetCall = () => {
|
||||
done(() => {
|
||||
assert.isTrue(settings.scrollSnapping);
|
||||
});
|
||||
};
|
||||
|
||||
page = true;
|
||||
settings = new Settings();
|
||||
};
|
||||
|
||||
settings = new Settings();
|
||||
});
|
||||
|
||||
test('responds to paging setting change', done => {
|
||||
var settings;
|
||||
|
||||
target = 'grid.paging';
|
||||
afterGetCall = () => {
|
||||
try {
|
||||
assert.isFalse(settings.scrollSnapping);
|
||||
} catch(e) {
|
||||
done(e);
|
||||
}
|
||||
|
||||
var dispatchEventStub = sinon.stub(window, 'dispatchEvent');
|
||||
|
||||
afterGetCall = () => {
|
||||
done(() => {
|
||||
assert.isTrue(dispatchEventStub.called);
|
||||
assert.isTrue(settings.scrollSnapping);
|
||||
dispatchEventStub.restore();
|
||||
});
|
||||
};
|
||||
|
||||
page = true;
|
||||
MockDatastore._cb({ id: 'grid.paging' });
|
||||
};
|
||||
|
||||
settings = new Settings();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('Settings#save()', () => {
|
||||
test('saves columns setting', () => {
|
||||
mockLocalStorage.setItem('settings', DEFAULT_SETTINGS);
|
||||
var settings = new Settings();
|
||||
settings.small = true;
|
||||
settings.save();
|
||||
|
||||
var settingsObj = JSON.parse(mockLocalStorage.getItem('settings'));
|
||||
assert.isTrue(settingsObj.small);
|
||||
});
|
||||
});
|
||||
});
|
||||
328
gaia/apps/homescreen/test/unit/window_test.js
Executable file
328
gaia/apps/homescreen/test/unit/window_test.js
Executable file
|
|
@ -0,0 +1,328 @@
|
|||
/* jshint nonew: false */
|
||||
/* global AppWindow */
|
||||
'use strict';
|
||||
|
||||
require('/shared/test/unit/load_body_html_helper.js');
|
||||
require('mocks/mock_pages.js');
|
||||
require('mocks/mock_apps.js');
|
||||
require('/js/window.js');
|
||||
|
||||
suite('AppWindow', () => {
|
||||
var appWindow;
|
||||
|
||||
setup(() => {
|
||||
loadBodyHTML('_index.html');
|
||||
appWindow = new AppWindow();
|
||||
});
|
||||
|
||||
suite('AppWindow#updatePanelIndicator()', () => {
|
||||
var indicatorToggleStubs;
|
||||
var realPanels;
|
||||
var mockPanels;
|
||||
|
||||
setup(() => {
|
||||
mockPanels = {
|
||||
scrollLeft: 0,
|
||||
scrollWidth: 200
|
||||
};
|
||||
indicatorToggleStubs = [
|
||||
sinon.stub(appWindow.indicator.children[0].classList, 'toggle'),
|
||||
sinon.stub(appWindow.indicator.children[1].classList, 'toggle')];
|
||||
realPanels = appWindow.panels;
|
||||
appWindow.panels = mockPanels;
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
indicatorToggleStubs.forEach((stub) => { stub.restore(); });
|
||||
appWindow.panels = realPanels;
|
||||
});
|
||||
|
||||
test('should update indicator when apps visible', () => {
|
||||
appWindow.appsVisible = false;
|
||||
appWindow.updatePanelIndicator();
|
||||
assert.isTrue(indicatorToggleStubs[0].calledWith('active', true));
|
||||
assert.isTrue(indicatorToggleStubs[1].calledWith('active', false));
|
||||
assert.equal(appWindow.header.getAttribute('data-l10n-id'), 'apps-panel');
|
||||
});
|
||||
|
||||
test('should update indicator when apps visible in RTL', () => {
|
||||
appWindow.appsVisible = true;
|
||||
mockPanels.scrollLeft = -100;
|
||||
appWindow.updatePanelIndicator();
|
||||
assert.isTrue(indicatorToggleStubs[0].calledWith('active', false));
|
||||
assert.isTrue(indicatorToggleStubs[1].calledWith('active', true));
|
||||
assert.equal(appWindow.header.getAttribute('data-l10n-id'),
|
||||
'pages-panel');
|
||||
});
|
||||
|
||||
test('should update aria-hidden on both panels', () => {
|
||||
var appPanelSetAttributeStub =
|
||||
sinon.stub(appWindow.apps.panel, 'setAttribute');
|
||||
var pagesPanelSetAttributeStub =
|
||||
sinon.stub(appWindow.pages.panel, 'setAttribute');
|
||||
|
||||
appWindow.appsVisible = false;
|
||||
appWindow.updatePanelIndicator();
|
||||
assert.isTrue(appPanelSetAttributeStub.calledWith('aria-hidden', false));
|
||||
assert.isTrue(pagesPanelSetAttributeStub.calledWith('aria-hidden', true));
|
||||
|
||||
appPanelSetAttributeStub.restore();
|
||||
pagesPanelSetAttributeStub.restore();
|
||||
});
|
||||
|
||||
test('should update indicator when pages visible', () => {
|
||||
var setAttributeSpy = sinon.spy(appWindow.indicator, 'setAttribute');
|
||||
var spy1 = setAttributeSpy.withArgs('aria-valuenow', 2);
|
||||
var spy2 = setAttributeSpy.withArgs('data-l10n-args', JSON.stringify({
|
||||
currentPage: 2,
|
||||
totalPages: 2
|
||||
}));
|
||||
|
||||
mockPanels.scrollLeft = mockPanels.scrollLeftMax;
|
||||
appWindow.updatePanelIndicator();
|
||||
|
||||
assert.isTrue(indicatorToggleStubs[0].calledWith('active', false));
|
||||
assert.isTrue(indicatorToggleStubs[1].calledWith('active', true));
|
||||
assert.isTrue(spy1.called);
|
||||
assert.isTrue(spy2.called);
|
||||
assert.equal(appWindow.header.getAttribute('data-l10n-id'),
|
||||
'pages-panel');
|
||||
});
|
||||
|
||||
test('should do nothing when visibility is unchanged', () => {
|
||||
var setAttributeSpy = sinon.spy(appWindow.indicator, 'setAttribute');
|
||||
appWindow.appsVisible = true;
|
||||
mockPanels.scrollLeft = 0;
|
||||
appWindow.updatePanelIndicator();
|
||||
assert.isFalse(indicatorToggleStubs[0].called);
|
||||
assert.isFalse(indicatorToggleStubs[1].called);
|
||||
assert.isFalse(setAttributeSpy.called);
|
||||
setAttributeSpy.restore();
|
||||
});
|
||||
});
|
||||
|
||||
suite('AppWindow#handleEvent()', () => {
|
||||
suite('keypress', () => {
|
||||
var scrollObject, realPanels;
|
||||
var event = {
|
||||
type: 'keypress',
|
||||
ctrlKey: true,
|
||||
DOM_VK_RIGHT: 'right',
|
||||
DOM_VK_LEFT: 'left'
|
||||
};
|
||||
var mockPanels = {
|
||||
scrollLeftMax: 100,
|
||||
scrollTo: obj => { scrollObject = obj; }
|
||||
};
|
||||
|
||||
setup(() => {
|
||||
realPanels = appWindow.panels;
|
||||
appWindow.panels = mockPanels;
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
appWindow.panels = realPanels;
|
||||
});
|
||||
|
||||
test('right should display pinned pages', () => {
|
||||
event.keyCode = 'right';
|
||||
appWindow.handleEvent(event);
|
||||
assert.equal(scrollObject.left, appWindow.panels.scrollLeftMax);
|
||||
assert.equal(scrollObject.top, 0);
|
||||
assert.equal(scrollObject.behavior, 'smooth');
|
||||
});
|
||||
|
||||
test('left should display apps', () => {
|
||||
event.keyCode = 'left';
|
||||
appWindow.handleEvent(event);
|
||||
assert.equal(scrollObject.left, 0);
|
||||
assert.equal(scrollObject.top, 0);
|
||||
assert.equal(scrollObject.behavior, 'smooth');
|
||||
});
|
||||
});
|
||||
|
||||
suite('scroll', () => {
|
||||
test('should show and hide the drop shadow accordingly', () => {
|
||||
appWindow.appsVisible = true;
|
||||
appWindow.apps.scrollable = {
|
||||
scrollTop: 50
|
||||
};
|
||||
appWindow.handleEvent(new CustomEvent('scroll'));
|
||||
assert.isTrue(appWindow.shadow.classList.contains('visible'));
|
||||
|
||||
appWindow.apps.scrollable.scrollTop = 0;
|
||||
appWindow.handleEvent(new CustomEvent('scroll'));
|
||||
assert.isFalse(appWindow.shadow.classList.contains('visible'));
|
||||
|
||||
appWindow.appsVisible = false;
|
||||
appWindow.pages.scrollable = {
|
||||
scrollTop: 50
|
||||
};
|
||||
appWindow.handleEvent(new CustomEvent('scroll'));
|
||||
assert.isTrue(appWindow.shadow.classList.contains('visible'));
|
||||
|
||||
appWindow.pages.scrollable.scrollTop = 0;
|
||||
appWindow.handleEvent(new CustomEvent('scroll'));
|
||||
assert.isFalse(appWindow.shadow.classList.contains('visible'));
|
||||
});
|
||||
|
||||
test('should update the panel indicator', () => {
|
||||
var updatePanelStub = sinon.stub(appWindow, 'updatePanelIndicator');
|
||||
|
||||
appWindow.panels.dispatchEvent(new CustomEvent('scroll'));
|
||||
assert.isTrue(updatePanelStub.called);
|
||||
|
||||
updatePanelStub.restore();
|
||||
});
|
||||
});
|
||||
|
||||
suite('hashchange', () => {
|
||||
var realDocumentHidden;
|
||||
setup(() => {
|
||||
realDocumentHidden =
|
||||
Object.getOwnPropertyDescriptor(document, 'hidden');
|
||||
Object.defineProperty(document, 'hidden', {
|
||||
value: false,
|
||||
configurable: true
|
||||
});
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
if (realDocumentHidden) {
|
||||
Object.defineProperty(document, 'hidden', realDocumentHidden);
|
||||
} else {
|
||||
delete document.hidden;
|
||||
}
|
||||
});
|
||||
|
||||
test('should scroll to the top of the apps page when visible', done => {
|
||||
var realScrollable = appWindow.apps.scrollable;
|
||||
appWindow.apps.scrollable = {
|
||||
scrollTo: (obj) => {
|
||||
done(() => {
|
||||
assert.equal(obj.top, 0);
|
||||
assert.equal(obj.left, 0);
|
||||
});
|
||||
},
|
||||
scrollLeft: 0,
|
||||
parentNode: {
|
||||
offsetLeft: 0
|
||||
}
|
||||
};
|
||||
|
||||
appWindow.appsVisible = true;
|
||||
appWindow.handleEvent(new CustomEvent('hashchange'));
|
||||
appWindow.apps.scrollable = realScrollable;
|
||||
});
|
||||
|
||||
test('should scroll to the top of the pages page when visible', done => {
|
||||
var realScrollable = appWindow.pages.scrollable;
|
||||
appWindow.pages.scrollable = {
|
||||
scrollTop: 0,
|
||||
scrollLeft: 0,
|
||||
parentNode: {
|
||||
offsetLeft: 0
|
||||
}
|
||||
};
|
||||
|
||||
var realPanels = appWindow.panels;
|
||||
appWindow.panels = {
|
||||
scrollTo: (obj) => {
|
||||
done(() => {
|
||||
assert.equal(obj.top, 0);
|
||||
assert.equal(obj.left, 0);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
appWindow.appsVisible = false;
|
||||
appWindow.handleEvent(new CustomEvent('hashchange'));
|
||||
appWindow.pages.scrollable = realScrollable;
|
||||
appWindow.panels = realPanels;
|
||||
});
|
||||
|
||||
test('should scroll to apps panel when at top of pages panel', done => {
|
||||
var realScrollable = appWindow.pages.scrollable;
|
||||
appWindow.pages.scrollable = {
|
||||
scrollTo: (obj) => {
|
||||
done(() => {
|
||||
assert.equal(obj.top, 0);
|
||||
assert.equal(obj.left, 0);
|
||||
});
|
||||
},
|
||||
scrollLeft: 0,
|
||||
parentNode: {
|
||||
offsetLeft: 0
|
||||
}
|
||||
};
|
||||
|
||||
appWindow.appsVisible = false;
|
||||
appWindow.handleEvent(new CustomEvent('hashchange'));
|
||||
appWindow.pages.scrollable = realScrollable;
|
||||
});
|
||||
|
||||
test('should cancel apps-panel dialogs', done => {
|
||||
var realDialogs = appWindow.apps.dialogs;
|
||||
appWindow.apps.dialogs = [{
|
||||
close: () => { done(); },
|
||||
opened: () => { return true; }
|
||||
}];
|
||||
appWindow.handleEvent(new CustomEvent('hashchange'));
|
||||
appWindow.apps.dialogs = realDialogs;
|
||||
});
|
||||
|
||||
test('should cancel pages-panel dialogs', done => {
|
||||
var realDialogs = appWindow.pages.dialogs;
|
||||
appWindow.pages.dialogs = [{
|
||||
close: () => { done(); },
|
||||
opened: () => { return true; }
|
||||
}];
|
||||
appWindow.handleEvent(new CustomEvent('hashchange'));
|
||||
appWindow.pages.dialogs = realDialogs;
|
||||
});
|
||||
|
||||
test('should exit edit mode', () => {
|
||||
var exitEditModeStub;
|
||||
|
||||
appWindow.apps.editMode = true;
|
||||
exitEditModeStub = sinon.stub(appWindow.apps, 'exitEditMode');
|
||||
appWindow.handleEvent(new CustomEvent('hashchange'));
|
||||
assert.isTrue(exitEditModeStub.called);
|
||||
exitEditModeStub.restore();
|
||||
|
||||
appWindow.ignoreHashChangeTimeout = null;
|
||||
appWindow.apps.editMode = false;
|
||||
|
||||
appWindow.pages.editMode = true;
|
||||
exitEditModeStub = sinon.stub(appWindow.pages, 'exitEditMode');
|
||||
appWindow.handleEvent(new CustomEvent('hashchange'));
|
||||
assert.isTrue(exitEditModeStub.called);
|
||||
exitEditModeStub.restore();
|
||||
});
|
||||
});
|
||||
|
||||
suite('visibilitychange', () => {
|
||||
var appsExitEditModeStub, pagesExitEditModeStub;
|
||||
setup(() => {
|
||||
appsExitEditModeStub = sinon.stub(appWindow.apps, 'exitEditMode');
|
||||
pagesExitEditModeStub = sinon.stub(appWindow.pages, 'exitEditMode');
|
||||
Object.defineProperty(document, 'hidden', {
|
||||
value: true,
|
||||
configurable: true
|
||||
});
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
appsExitEditModeStub.restore();
|
||||
pagesExitEditModeStub.restore();
|
||||
delete document.hidden;
|
||||
});
|
||||
|
||||
test('should exit edit mode', () => {
|
||||
appWindow.handleEvent(new CustomEvent('visibilitychange'));
|
||||
assert.isTrue(appsExitEditModeStub.called);
|
||||
assert.isTrue(pagesExitEditModeStub.called);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue