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
27
gaia/apps/homescreen/test/build/integration/homescreen_test.js
Executable file
27
gaia/apps/homescreen/test/build/integration/homescreen_test.js
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
'use strict';
|
||||
|
||||
/* jshint node: true, mocha: true */
|
||||
/* global suiteSetup */
|
||||
|
||||
var helper = require('helper');
|
||||
|
||||
suite('Homescreen tests', function() {
|
||||
suiteSetup(helper.cleanupWorkspace);
|
||||
teardown(helper.cleanupWorkspace);
|
||||
|
||||
test('APP=homescreen make', function(done) {
|
||||
helper.exec('APP=homescreen make', function(error, stdout, stderr) {
|
||||
helper.checkError(error, stdout, stderr);
|
||||
|
||||
var zipPath = process.cwd() +
|
||||
'/profile/webapps/homescreen.gaiamobile.org/application.zip';
|
||||
var checkList = ['manifest.webapp'];
|
||||
|
||||
checkList.forEach(function(filePath) {
|
||||
helper.checkFilePathInZip(zipPath, filePath);
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
19
gaia/apps/homescreen/test/marionette/.jshintrc
Executable file
19
gaia/apps/homescreen/test/marionette/.jshintrc
Executable file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"extends": "../../../../.jshintrc",
|
||||
"predef": [
|
||||
"__dirname",
|
||||
"module",
|
||||
"suite",
|
||||
"test",
|
||||
"setup",
|
||||
"teardown",
|
||||
"suiteSetup",
|
||||
"suiteTeardown",
|
||||
"marionette",
|
||||
"marionetteScriptFinished",
|
||||
"require",
|
||||
"define",
|
||||
"requireApp",
|
||||
"sinon"
|
||||
]
|
||||
}
|
||||
65
gaia/apps/homescreen/test/marionette/app_appcache_pending_test.js
Executable file
65
gaia/apps/homescreen/test/marionette/app_appcache_pending_test.js
Executable file
|
|
@ -0,0 +1,65 @@
|
|||
/* global __dirname */
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var AppInstall =
|
||||
require('../../../../apps/system/test/marionette/lib/app_install');
|
||||
var createAppServer = require('./server/parent');
|
||||
|
||||
marionette('Homescreen - Appcache installed download', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
var server;
|
||||
setup(function(done) {
|
||||
var app = __dirname + '/fixtures/appcache';
|
||||
createAppServer(app, client, function(err, _server) {
|
||||
server = _server;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
var home, system, appInstall;
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
appInstall = new AppInstall(client);
|
||||
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
teardown(function(done) {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
test('shows spinner while downloading', function() {
|
||||
// correctly install the app...
|
||||
client.switchToFrame();
|
||||
|
||||
// ensure appcache path is delayed
|
||||
server.cork(server.manifest.appcache_path);
|
||||
appInstall.install(server.manifestURL);
|
||||
|
||||
// switch back to the homescreen
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
|
||||
var icon = home.getIcon(server.manifestURL);
|
||||
|
||||
// wait until we see the nice spinner thing
|
||||
client.waitFor(function() {
|
||||
return home.iconIsLoading(icon);
|
||||
});
|
||||
|
||||
// let the rest of the app come through
|
||||
server.uncork(server.manifest.appcache_path);
|
||||
// wait until it is no longer loading
|
||||
client.waitFor(function() {
|
||||
return !home.iconIsLoading(icon);
|
||||
});
|
||||
|
||||
// ensure the app launches!
|
||||
home.launchIcon(icon);
|
||||
assert.equal(client.title(), 'iwrotethis');
|
||||
});
|
||||
});
|
||||
38
gaia/apps/homescreen/test/marionette/app_blacklist_test.js
Executable file
38
gaia/apps/homescreen/test/marionette/app_blacklist_test.js
Executable file
|
|
@ -0,0 +1,38 @@
|
|||
'use strict';
|
||||
var assert = require('assert');
|
||||
|
||||
marionette('Homescreen - App Blacklist', function() {
|
||||
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
var home, system;
|
||||
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
system.waitForFullyLoaded();
|
||||
|
||||
client.apps.launch(home.URL);
|
||||
home.waitForLaunch();
|
||||
|
||||
// Fail finding elements quickly.
|
||||
client.setSearchTimeout(20);
|
||||
});
|
||||
|
||||
function lookForIcon(manifestURL) {
|
||||
try {
|
||||
home.getIcon(manifestURL);
|
||||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
test('app does not appear on home screen if in blacklist', function() {
|
||||
// Ensure that a normal icon is there.
|
||||
var calendarApp = lookForIcon('app://calendar.gaiamobile.org');
|
||||
assert.ok(calendarApp);
|
||||
});
|
||||
|
||||
});
|
||||
54
gaia/apps/homescreen/test/marionette/app_default_order_test.js
Executable file
54
gaia/apps/homescreen/test/marionette/app_default_order_test.js
Executable file
|
|
@ -0,0 +1,54 @@
|
|||
'use strict';
|
||||
/* global __dirname */
|
||||
|
||||
var assert = require('assert');
|
||||
|
||||
marionette('Homescreen - Default app order', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
var home, system;
|
||||
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
test('Default app order is respected', function() {
|
||||
var expectedOrder = [
|
||||
'Phone',
|
||||
'Messages',
|
||||
'Contacts',
|
||||
'E-Mail',
|
||||
'Browser',
|
||||
'Camera',
|
||||
'Gallery',
|
||||
'Music',
|
||||
'Video',
|
||||
'Marketplace',
|
||||
'Calendar',
|
||||
'Clock',
|
||||
'Settings',
|
||||
'FM Radio',
|
||||
'BuddyUp',
|
||||
'Bugzilla Lite',
|
||||
'Facebook',
|
||||
'Twitter',
|
||||
'Notes',
|
||||
'Calculator',
|
||||
'Usage'
|
||||
];
|
||||
var length = expectedOrder.length;
|
||||
|
||||
var iconNames = home.visibleIcons.map(function(icon) {
|
||||
return home.getIconText(icon);
|
||||
}).slice(0, length);
|
||||
|
||||
for (var i = 0; i < length; i++) {
|
||||
assert.equal(iconNames[i], expectedOrder[i]);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
60
gaia/apps/homescreen/test/marionette/app_hosted_failed_icon_fetch_test.js
Executable file
60
gaia/apps/homescreen/test/marionette/app_hosted_failed_icon_fetch_test.js
Executable file
|
|
@ -0,0 +1,60 @@
|
|||
/* global __dirname */
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var AppInstall =
|
||||
require('../../../../apps/system/test/marionette/lib/app_install');
|
||||
var createAppServer = require('./server/parent');
|
||||
|
||||
marionette('Homescreen - Hosted app failed icon fetch', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
var server;
|
||||
setup(function(done) {
|
||||
var app = __dirname + '/fixtures/template_app';
|
||||
createAppServer(app, client, function(err, _server) {
|
||||
server = _server;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
var home, system, appInstall;
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
appInstall = new AppInstall(client);
|
||||
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
teardown(function(done) {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
test('fallback to default icon', function() {
|
||||
var iconURL = server.manifest.icons['128'];
|
||||
|
||||
// correctly install the app...
|
||||
client.switchToFrame();
|
||||
|
||||
// ensure the icon fails to download!
|
||||
server.fail(iconURL);
|
||||
appInstall.install(server.manifestURL);
|
||||
|
||||
// switch back to the homescreen
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
|
||||
var icon = home.getIcon(server.manifestURL);
|
||||
|
||||
// ensure the default icon is shown
|
||||
home.waitForIconImageUrl(icon, 'default');
|
||||
|
||||
// ensure the icon can be launched!
|
||||
home.launchIcon(icon);
|
||||
assert.equal(client.title(), 'iwrotethis');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
44
gaia/apps/homescreen/test/marionette/app_hosted_install_test.js
Executable file
44
gaia/apps/homescreen/test/marionette/app_hosted_install_test.js
Executable file
|
|
@ -0,0 +1,44 @@
|
|||
/* global __dirname */
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var AppInstall =
|
||||
require('../../../../apps/system/test/marionette/lib/app_install');
|
||||
var createAppServer = require('./server/parent');
|
||||
|
||||
marionette('Homescreen - Hosted App Install', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
var server;
|
||||
setup(function(done) {
|
||||
var app = __dirname + '/fixtures/template_app';
|
||||
createAppServer(app, client, function(err, _server) {
|
||||
server = _server;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
var home, system, appInstall;
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
appInstall = new AppInstall(client);
|
||||
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
teardown(function(done) {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
test('install app', function() {
|
||||
client.switchToFrame();
|
||||
appInstall.install(server.manifestURL);
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
home.launchIcon(home.getIcon(server.manifestURL));
|
||||
assert.equal(client.title(), 'iwrotethis');
|
||||
});
|
||||
});
|
||||
|
||||
113
gaia/apps/homescreen/test/marionette/app_hosted_retry_icon_fetch_test.js
Executable file
113
gaia/apps/homescreen/test/marionette/app_hosted_retry_icon_fetch_test.js
Executable file
|
|
@ -0,0 +1,113 @@
|
|||
/* global __dirname */
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var AppInstall =
|
||||
require('../../../../apps/system/test/marionette/lib/app_install');
|
||||
var createAppServer = require('./server/parent');
|
||||
|
||||
marionette('Homescreen - Hosted app failed icon fetch', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
var server;
|
||||
setup(function(done) {
|
||||
var app = __dirname + '/fixtures/template_app';
|
||||
createAppServer(app, client, function(err, _server) {
|
||||
server = _server;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
var actions, home, system, appInstall;
|
||||
setup(function() {
|
||||
actions = client.loader.getActions();
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
appInstall = new AppInstall(client);
|
||||
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
teardown(function(done) {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
function tapAndWaitFor(icon, element) {
|
||||
client.waitFor(function() {
|
||||
icon.tap();
|
||||
actions.wait(0.5).perform();
|
||||
return element.scriptWith(function(el) {
|
||||
return getComputedStyle(el).display !== 'none';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
test('shows icon after a restart', function() {
|
||||
// go to the system app
|
||||
client.switchToFrame();
|
||||
|
||||
// don't let the server send the zip archive
|
||||
server.cork(server.applicationZipUri);
|
||||
appInstall.installPackage(server.packageManifestURL);
|
||||
|
||||
// switch back to the homescreen
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
|
||||
var icon = home.getIcon(server.packageManifestURL);
|
||||
home.scrollIconToCenter(icon);
|
||||
client.waitFor(function() {
|
||||
return home.iconIsLoading(icon);
|
||||
});
|
||||
|
||||
// stop the download
|
||||
tapAndWaitFor(icon, home.cancelDownloadDialog);
|
||||
home.actionDialog(home.cancelDownloadDialog, 'stop-download-action');
|
||||
home.waitForIconImageUrl(icon, 'app_install_canceled');
|
||||
|
||||
// Restart the download
|
||||
server.uncork(server.applicationZipUri);
|
||||
|
||||
// resume the download from the ui
|
||||
tapAndWaitFor(icon, home.resumeDownloadDialog);
|
||||
home.actionDialog(home.resumeDownloadDialog, 'resume-download-action');
|
||||
|
||||
// wait until we are showing our desired icon
|
||||
home.waitForIconImageUrl(icon, 'app-icon');
|
||||
});
|
||||
|
||||
test('fallback to default icon when icon fails', function() {
|
||||
var iconURL = server.manifest.icons['128'];
|
||||
// correctly install the app...
|
||||
client.switchToFrame();
|
||||
|
||||
// ensure the icon fails to download!
|
||||
server.fail(iconURL);
|
||||
appInstall.install(server.manifestURL);
|
||||
|
||||
// switch back to the homescreen
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
|
||||
var icon = home.getIcon(server.manifestURL);
|
||||
|
||||
// ensure the default icon is shown
|
||||
home.waitForIconImageUrl(icon, 'default');
|
||||
|
||||
server.unfail(iconURL);
|
||||
|
||||
// XXX: We don't have real network access but we can sorta emulate it by
|
||||
// failing then switching back to an online state.
|
||||
client.executeScript(function() {
|
||||
window.dispatchEvent(new CustomEvent('online'));
|
||||
});
|
||||
|
||||
// wait until we are showing our desired icon
|
||||
home.waitForIconImageUrl(icon, 'app-icon');
|
||||
|
||||
// ensure the icon can be launched!
|
||||
home.launchIcon(icon);
|
||||
assert.equal(client.title(), 'iwrotethis');
|
||||
});
|
||||
});
|
||||
|
||||
86
gaia/apps/homescreen/test/marionette/app_hosted_use_cached_icon_test.js
Executable file
86
gaia/apps/homescreen/test/marionette/app_hosted_use_cached_icon_test.js
Executable file
|
|
@ -0,0 +1,86 @@
|
|||
/* global __dirname */
|
||||
'use strict';
|
||||
|
||||
var AppInstall =
|
||||
require('../../../../apps/system/test/marionette/lib/app_install');
|
||||
var createAppServer = require('./server/parent');
|
||||
|
||||
marionette('Homescreen - Hosted app cached icon fetch', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
var server;
|
||||
setup(function(done) {
|
||||
var app = __dirname + '/fixtures/template_app';
|
||||
createAppServer(app, client, function(err, _server) {
|
||||
server = _server;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
var home, system, appInstall;
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
appInstall = new AppInstall(client);
|
||||
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
teardown(function(done) {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
test('fallback to default icon', function() {
|
||||
var iconURL = server.manifest.icons['128'];
|
||||
|
||||
// correctly install the app...
|
||||
client.switchToFrame();
|
||||
appInstall.install(server.manifestURL);
|
||||
|
||||
// switch back to the homescreen
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
var icon = home.getIcon(server.manifestURL);
|
||||
|
||||
// ensure it is cached
|
||||
home.waitForIconImageUrl(icon, 'app-icon');
|
||||
var id = home.getIconId(icon);
|
||||
client.waitFor(function() {
|
||||
var metadata = client.executeAsyncScript(function() {
|
||||
window.wrappedJSObject.appWindow.apps.metadata.getAll().then(
|
||||
marionetteScriptFinished);
|
||||
});
|
||||
|
||||
for (var i = 0, iLen = metadata.length; i < iLen; i++) {
|
||||
if (metadata[i].id.startsWith(id) &&
|
||||
typeof metadata[i].icon !== 'undefined') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// ensure http fails so we use the cached icon
|
||||
server.fail(iconURL);
|
||||
|
||||
// Reload the homescreen
|
||||
home.restart();
|
||||
icon = home.getIcon(server.manifestURL);
|
||||
|
||||
// check for the cached icon...
|
||||
home.waitForIconImageUrl(icon, 'user-set');
|
||||
|
||||
// allow the request to succeed
|
||||
server.unfail(iconURL);
|
||||
|
||||
// trigger the download of a new icon
|
||||
client.executeScript(function() {
|
||||
window.wrappedJSObject.dispatchEvent(new CustomEvent('online'));
|
||||
});
|
||||
|
||||
// now shows the freshly redownloaded icon
|
||||
home.waitForIconImageUrl(icon, 'app-icon');
|
||||
});
|
||||
|
||||
});
|
||||
66
gaia/apps/homescreen/test/marionette/app_order_test.js
Executable file
66
gaia/apps/homescreen/test/marionette/app_order_test.js
Executable file
|
|
@ -0,0 +1,66 @@
|
|||
'use strict';
|
||||
/* global __dirname */
|
||||
|
||||
var assert = require('assert');
|
||||
|
||||
marionette('Homescreen - App order', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
var actions, home, system;
|
||||
|
||||
setup(function() {
|
||||
actions = client.loader.getActions();
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
test('App order is retained after restart', function() {
|
||||
// Drag icon to a different place
|
||||
var icons = home.visibleIcons;
|
||||
var location1 = icons[0].location();
|
||||
var location2 = icons[1].location();
|
||||
actions.wait(0.5).press(icons[0]).wait(0.5).
|
||||
move(icons[1]).release().wait(0.5).perform();
|
||||
|
||||
client.waitFor(function() {
|
||||
return icons[0].location().x === location2.x &&
|
||||
icons[1].location().x === location1.x;
|
||||
});
|
||||
|
||||
// Wait for the icon order to be saved
|
||||
var ids = home.getIconIdentifiers();
|
||||
var numIcons = ids.length;
|
||||
client.waitFor(function() {
|
||||
var order = client.executeAsyncScript(function() {
|
||||
window.wrappedJSObject.appWindow.apps.metadata.getAll().then(
|
||||
marionetteScriptFinished);
|
||||
});
|
||||
|
||||
// The order array is stored in order, but also contains non-visible
|
||||
// icons, so just skip unknown entries.
|
||||
var correctlyPlacedIcons = 0;
|
||||
for (var i = 0, iLen = order.length; i < iLen; i++) {
|
||||
if (order[i].id.startsWith(ids[correctlyPlacedIcons])) {
|
||||
++ correctlyPlacedIcons;
|
||||
}
|
||||
}
|
||||
return correctlyPlacedIcons === numIcons;
|
||||
});
|
||||
|
||||
// Test that icon ordering is retained after a restart
|
||||
home.restart();
|
||||
|
||||
client.waitFor(function() {
|
||||
return home.visibleIcons.length === numIcons;
|
||||
});
|
||||
|
||||
var newIds = home.getIconIdentifiers();
|
||||
for (var i = 0, iLen = ids.length; i < iLen; i++) {
|
||||
assert.equal(ids[i], newIds[i]);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
59
gaia/apps/homescreen/test/marionette/app_packaged_fail_test.js
Executable file
59
gaia/apps/homescreen/test/marionette/app_packaged_fail_test.js
Executable file
|
|
@ -0,0 +1,59 @@
|
|||
/* global __dirname */
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var AppInstall =
|
||||
require('../../../../apps/system/test/marionette/lib/app_install');
|
||||
var createAppServer = require('./server/parent');
|
||||
|
||||
marionette('Homescreen - Packaged App Failed Download', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js'),
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
var server;
|
||||
setup(function(done) {
|
||||
var app = __dirname + '/fixtures/template_app';
|
||||
createAppServer(app, client, function(err, _server) {
|
||||
server = _server;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
var home, system, appInstall;
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
appInstall = new AppInstall(client);
|
||||
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
teardown(function(done) {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
test('failed state then retry and launch', function() {
|
||||
client.switchToFrame();
|
||||
|
||||
server.fail(server.applicationZipUri);
|
||||
appInstall.installPackage(server.packageManifestURL);
|
||||
appInstall.dismissToast();
|
||||
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
|
||||
var icon = home.getIcon(server.packageManifestURL);
|
||||
home.waitForIconImageUrl(icon, 'failed');
|
||||
|
||||
server.unfail(server.applicationZipUri);
|
||||
|
||||
icon.tap();
|
||||
home.actionDialog(home.resumeDownloadDialog, 'resume-download-action');
|
||||
home.waitForIconImageUrl(icon, 'app-icon');
|
||||
|
||||
home.launchIcon(icon);
|
||||
assert.equal(client.title(), 'iwrotethis');
|
||||
});
|
||||
});
|
||||
|
||||
45
gaia/apps/homescreen/test/marionette/app_packaged_install_test.js
Executable file
45
gaia/apps/homescreen/test/marionette/app_packaged_install_test.js
Executable file
|
|
@ -0,0 +1,45 @@
|
|||
/* global __dirname */
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var AppInstall =
|
||||
require('../../../../apps/system/test/marionette/lib/app_install');
|
||||
var createAppServer = require('./server/parent');
|
||||
|
||||
marionette('Homescreen - Packaged App Install', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
var server;
|
||||
setup(function(done) {
|
||||
var app = __dirname + '/fixtures/template_app';
|
||||
createAppServer(app, client, function(err, _server) {
|
||||
server = _server;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
var home, system, appInstall;
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
appInstall = new AppInstall(client);
|
||||
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
teardown(function(done) {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
test('install app', function() {
|
||||
client.switchToFrame();
|
||||
appInstall.installPackage(server.packageManifestURL);
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
var icon = home.getIcon(server.packageManifestURL);
|
||||
home.waitForIconImageUrl(icon, 'app-icon');
|
||||
home.launchIcon(icon);
|
||||
assert.equal(client.title(), 'iwrotethis');
|
||||
});
|
||||
});
|
||||
61
gaia/apps/homescreen/test/marionette/app_packaged_pending_test.js
Executable file
61
gaia/apps/homescreen/test/marionette/app_packaged_pending_test.js
Executable file
|
|
@ -0,0 +1,61 @@
|
|||
/* global __dirname */
|
||||
'use strict';
|
||||
|
||||
var AppInstall =
|
||||
require('../../../../apps/system/test/marionette/lib/app_install');
|
||||
var createAppServer = require('./server/parent');
|
||||
|
||||
marionette('Homescreen - Packaged App Pending', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
|
||||
var server;
|
||||
setup(function(done) {
|
||||
var app = __dirname + '/fixtures/template_app';
|
||||
createAppServer(app, client, function(err, _server) {
|
||||
server = _server;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
var home, system, appInstall;
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
appInstall = new AppInstall(client);
|
||||
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
teardown(function(done) {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
test('app loading spinner', function() {
|
||||
// go to the system app
|
||||
client.switchToFrame();
|
||||
|
||||
// don't let the server send the zip archive
|
||||
server.cork(server.applicationZipUri);
|
||||
appInstall.installPackage(server.packageManifestURL);
|
||||
|
||||
// switch back to the homescreen
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
|
||||
var icon = home.getIcon(server.packageManifestURL);
|
||||
// wait until the icon is spinning!
|
||||
client.waitFor(function() {
|
||||
return home.iconIsLoading(icon);
|
||||
});
|
||||
|
||||
// let the rest of the app come through
|
||||
server.uncork(server.applicationZipUri);
|
||||
|
||||
// wait until it is no longer loading
|
||||
client.waitFor(function() {
|
||||
return !home.iconIsLoading(icon);
|
||||
});
|
||||
});
|
||||
});
|
||||
63
gaia/apps/homescreen/test/marionette/app_packaged_pending_update_test.js
Executable file
63
gaia/apps/homescreen/test/marionette/app_packaged_pending_update_test.js
Executable file
|
|
@ -0,0 +1,63 @@
|
|||
/* global __dirname */
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var AppInstall =
|
||||
require('../../../../apps/system/test/marionette/lib/app_install');
|
||||
var createAppServer = require('./server/parent');
|
||||
|
||||
marionette('Homescreen - Packaged App Pending Update', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
|
||||
var server;
|
||||
setup(function(done) {
|
||||
var app = __dirname + '/fixtures/template_app';
|
||||
createAppServer(app, client, function(err, _server) {
|
||||
server = _server;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
var home, system, appInstall;
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
appInstall = new AppInstall(client);
|
||||
|
||||
// wait for the system app to be running
|
||||
system.waitForFullyLoaded();
|
||||
client.apps.launch(home.URL);
|
||||
home.waitForLaunch();
|
||||
|
||||
// install the app
|
||||
client.switchToFrame();
|
||||
appInstall.installPackage(server.packageManifestURL);
|
||||
|
||||
// switch to the homescreen
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
});
|
||||
|
||||
teardown(function(done) {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
test('update an installed app', function() {
|
||||
var icon = home.getIcon(server.packageManifestURL);
|
||||
|
||||
// ensure the app is installed before updating it
|
||||
home.waitForIconImageUrl(icon, 'app-icon');
|
||||
|
||||
// Update the manifests.
|
||||
server.setRoot(__dirname + '/fixtures/template_app_updated');
|
||||
|
||||
// Stage the update
|
||||
appInstall.stageUpdate(server.packageManifestURL);
|
||||
|
||||
// ensure we can still launch an app in the older version
|
||||
home.launchIcon(icon);
|
||||
assert.equal(client.title(), 'iwrotethis');
|
||||
});
|
||||
});
|
||||
|
||||
93
gaia/apps/homescreen/test/marionette/app_packaged_resume_test.js
Executable file
93
gaia/apps/homescreen/test/marionette/app_packaged_resume_test.js
Executable file
|
|
@ -0,0 +1,93 @@
|
|||
/* global __dirname */
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var AppInstall =
|
||||
require('../../../../apps/system/test/marionette/lib/app_install');
|
||||
var createAppServer = require('./server/parent');
|
||||
|
||||
marionette('Homescreen - Packaged App Resuming Downloads', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
|
||||
var server, actions, home, system, appInstall;
|
||||
setup(function(done) {
|
||||
var app = __dirname + '/fixtures/template_app';
|
||||
createAppServer(app, client, function(err, _server) {
|
||||
server = _server;
|
||||
|
||||
actions = client.loader.getActions();
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
appInstall = new AppInstall(client);
|
||||
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
teardown(function(done) {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
function tapAndWaitFor(icon, element) {
|
||||
client.waitFor(function() {
|
||||
icon.tap();
|
||||
actions.wait(0.5).perform();
|
||||
return element.scriptWith(function(el) {
|
||||
return getComputedStyle(el).display !== 'none';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
test('failed state then retry and launch', function() {
|
||||
client.switchToFrame();
|
||||
|
||||
server.cork(server.applicationZipUri);
|
||||
appInstall.installPackage(server.packageManifestURL);
|
||||
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
|
||||
// Get the icon
|
||||
var icon = home.getIcon(server.packageManifestURL);
|
||||
|
||||
// Scroll so the icon is in the middle of the screen and won't be
|
||||
// obscured by the system toast
|
||||
home.scrollIconToCenter(icon);
|
||||
|
||||
// pause the download
|
||||
tapAndWaitFor(icon, home.cancelDownloadDialog);
|
||||
|
||||
home.actionDialog(home.cancelDownloadDialog, 'stop-download-action');
|
||||
home.waitForIconImageUrl(icon, 'app_install_canceled');
|
||||
|
||||
// resume the download
|
||||
tapAndWaitFor(icon, home.resumeDownloadDialog);
|
||||
home.actionDialog(home.resumeDownloadDialog, 'resume-download-action');
|
||||
client.waitFor(function() {
|
||||
return home.iconIsLoading(icon);
|
||||
});
|
||||
|
||||
// pause it again!
|
||||
tapAndWaitFor(icon, home.cancelDownloadDialog);
|
||||
home.actionDialog(home.cancelDownloadDialog, 'stop-download-action');
|
||||
home.waitForIconImageUrl(icon, 'app_install_canceled');
|
||||
|
||||
// uncork so next resume works...
|
||||
server.uncork(server.applicationZipUri);
|
||||
|
||||
// finally download the entire app
|
||||
tapAndWaitFor(icon, home.resumeDownloadDialog);
|
||||
home.actionDialog(home.resumeDownloadDialog, 'resume-download-action');
|
||||
home.waitForIconImageUrl(icon, 'app-icon');
|
||||
|
||||
// now it should work!
|
||||
home.launchIcon(icon);
|
||||
assert.equal(client.title(), 'iwrotethis');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
101
gaia/apps/homescreen/test/marionette/app_packaged_resume_update_test.js
Executable file
101
gaia/apps/homescreen/test/marionette/app_packaged_resume_update_test.js
Executable file
|
|
@ -0,0 +1,101 @@
|
|||
/* global __dirname */
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var AppInstall =
|
||||
require('../../../../apps/system/test/marionette/lib/app_install');
|
||||
var createAppServer = require('./server/parent');
|
||||
|
||||
marionette('Homescreen - Packaged App Resume Update', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
|
||||
var server;
|
||||
setup(function(done) {
|
||||
var app = __dirname + '/fixtures/template_app';
|
||||
createAppServer(app, client, function(err, _server) {
|
||||
server = _server;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
var actions, home, system, appInstall;
|
||||
setup(function() {
|
||||
actions = client.loader.getActions();
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
appInstall = new AppInstall(client);
|
||||
|
||||
// wait for the system app to be running
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
|
||||
// install the app
|
||||
client.switchToFrame();
|
||||
appInstall.installPackage(server.packageManifestURL);
|
||||
|
||||
// switch to the homescreen
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
});
|
||||
|
||||
teardown(function(done) {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
function tapAndWaitFor(icon, element) {
|
||||
client.waitFor(function() {
|
||||
icon.tap();
|
||||
actions.wait(0.5).perform();
|
||||
return element.scriptWith(function(el) {
|
||||
return getComputedStyle(el).display !== 'none';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
test('resume update', function() {
|
||||
var icon = home.getIcon(server.packageManifestURL);
|
||||
|
||||
// Scroll so the icon is in the middle of the screen and won't be
|
||||
// obscured by the system toast
|
||||
home.scrollIconToCenter(icon);
|
||||
|
||||
// ensure the app is installed before updating it
|
||||
home.waitForIconImageUrl(icon, 'app-icon');
|
||||
|
||||
// Cork the zip so we can see the loading spinner.
|
||||
server.cork('/app.zip');
|
||||
|
||||
// Update the manifests.
|
||||
server.setRoot(__dirname + '/fixtures/template_app_updated');
|
||||
|
||||
// Ensure we have an update.
|
||||
appInstall.update(server.packageManifestURL);
|
||||
|
||||
// Ensure we see the loading signs...
|
||||
client.waitFor(function() {
|
||||
return home.iconIsLoading(icon);
|
||||
});
|
||||
|
||||
// pause the update
|
||||
tapAndWaitFor(icon, home.cancelDownloadDialog);
|
||||
home.actionDialog(home.cancelDownloadDialog, 'stop-download-action');
|
||||
home.waitForIconImageUrl(icon, 'app_install_canceled');
|
||||
|
||||
// resume the update
|
||||
tapAndWaitFor(icon, home.resumeDownloadDialog);
|
||||
home.actionDialog(home.resumeDownloadDialog, 'resume-download-action');
|
||||
client.waitFor(function() {
|
||||
return home.iconIsLoading(icon);
|
||||
});
|
||||
|
||||
// Wait for the download to be complete.
|
||||
server.uncork('/app.zip');
|
||||
home.waitForIconImageUrl(icon, 'app-icon');
|
||||
|
||||
// verify the app was really updated
|
||||
home.launchIcon(icon);
|
||||
assert.equal(client.title(), 'updatedwow');
|
||||
});
|
||||
});
|
||||
|
||||
59
gaia/apps/homescreen/test/marionette/app_packaged_role_hidden_test.js
Executable file
59
gaia/apps/homescreen/test/marionette/app_packaged_role_hidden_test.js
Executable file
|
|
@ -0,0 +1,59 @@
|
|||
/* global __dirname */
|
||||
'use strict';
|
||||
|
||||
var AppInstall =
|
||||
require('../../../../apps/system/test/marionette/lib/app_install');
|
||||
var createAppServer = require('./server/parent');
|
||||
|
||||
marionette('Homescreen - Packaged App Pending', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
|
||||
var server;
|
||||
setup(function(done) {
|
||||
var app = __dirname + '/fixtures/role_homescreen';
|
||||
createAppServer(app, client, function(err, _server) {
|
||||
server = _server;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
var home, system, appInstall;
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
appInstall = new AppInstall(client);
|
||||
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
teardown(function(done) {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
test('app is displayed until determined role=homescreen', function() {
|
||||
// go to the system app
|
||||
client.switchToFrame();
|
||||
|
||||
// don't let the server send the zip archive
|
||||
server.cork(server.applicationZipUri);
|
||||
appInstall.installPackage(server.packageManifestURL);
|
||||
|
||||
// switch back to the homescreen
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
|
||||
var icon = home.getIcon(server.packageManifestURL);
|
||||
// wait until the icon is spinning!
|
||||
client.waitFor(function() {
|
||||
return home.iconIsLoading(icon);
|
||||
});
|
||||
|
||||
// let the rest of the app come through
|
||||
server.uncork(server.applicationZipUri);
|
||||
|
||||
// wait until the icon is hidden
|
||||
client.helper.waitForElementToDisappear(icon);
|
||||
});
|
||||
});
|
||||
93
gaia/apps/homescreen/test/marionette/app_packaged_update_test.js
Executable file
93
gaia/apps/homescreen/test/marionette/app_packaged_update_test.js
Executable file
|
|
@ -0,0 +1,93 @@
|
|||
/* global __dirname */
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var AppInstall =
|
||||
require('../../../../apps/system/test/marionette/lib/app_install');
|
||||
var createAppServer = require('./server/parent');
|
||||
|
||||
marionette('Homescreen - Packaged App Update', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
|
||||
var server;
|
||||
setup(function(done) {
|
||||
var app = __dirname + '/fixtures/template_app';
|
||||
createAppServer(app, client, function(err, _server) {
|
||||
server = _server;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
var home, system, appInstall;
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
appInstall = new AppInstall(client);
|
||||
|
||||
// wait for the system app to be running
|
||||
system.waitForFullyLoaded();
|
||||
|
||||
// Launch the homescreen first, then go to the system app.
|
||||
// Make sure we do this before installing an application.
|
||||
home.waitForLaunch();
|
||||
client.switchToFrame();
|
||||
|
||||
// install the app with a broken icon
|
||||
appInstall.installPackage(server.packageManifestURL);
|
||||
|
||||
// switch to the homescreen
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
});
|
||||
|
||||
teardown(function(done) {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
test('update an installed app', function() {
|
||||
var icon = home.getIcon(server.packageManifestURL);
|
||||
|
||||
// ensure the app is installed before updating it
|
||||
home.waitForIconImageUrl(icon, 'app-icon');
|
||||
|
||||
// Prepare to screenshot
|
||||
var iconImage = home.getIconImage(icon);
|
||||
var iconTitle = home.getIconText(icon);
|
||||
|
||||
// Cork the zip so we can see the loading spinner.
|
||||
server.cork('/app.zip');
|
||||
|
||||
// Update the manifests.
|
||||
server.setRoot(__dirname + '/fixtures/template_app_updated');
|
||||
|
||||
// Ensure we have an update.
|
||||
appInstall.update(server.packageManifestURL);
|
||||
|
||||
// Ensure we see the loading signs...
|
||||
client.waitFor(function() {
|
||||
return home.iconIsLoading(icon);
|
||||
});
|
||||
|
||||
// Wait for the download to be complete.
|
||||
server.uncork('/app.zip');
|
||||
home.waitForIconImageUrl(icon, 'app-icon');
|
||||
|
||||
// See bug 826555 for rationale for the title not changing.
|
||||
assert.equal(
|
||||
home.getIconText(icon),
|
||||
iconTitle,
|
||||
'app name should not be updated'
|
||||
);
|
||||
|
||||
// Make sure the icon changed
|
||||
assert.notEqual(
|
||||
home.getIconImage(icon),
|
||||
iconImage,
|
||||
'app icon should have updated');
|
||||
|
||||
// verify the app was really updated
|
||||
home.launchIcon(icon);
|
||||
assert.equal(client.title(), 'updatedwow');
|
||||
});
|
||||
});
|
||||
45
gaia/apps/homescreen/test/marionette/app_reorder_hint_test.js
Executable file
45
gaia/apps/homescreen/test/marionette/app_reorder_hint_test.js
Executable file
|
|
@ -0,0 +1,45 @@
|
|||
'use strict';
|
||||
/* global __dirname */
|
||||
|
||||
marionette('Homescreen - App reordering', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
var actions, home, system;
|
||||
|
||||
setup(function() {
|
||||
actions = client.loader.getActions();
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
test('App reordering has a visual hint', function() {
|
||||
var icons = home.visibleIcons;
|
||||
var location1 = icons[0].location();
|
||||
var location3 = icons[2].location();
|
||||
|
||||
// Drag second icon over third icon
|
||||
actions.wait(0.5).press(icons[1]).wait(0.5).
|
||||
move(icons[2]).perform();
|
||||
|
||||
// Test that the third icon moves out of the way in the right direction
|
||||
client.waitFor(function() {
|
||||
return icons[2].location().x < location3.x;
|
||||
});
|
||||
|
||||
// Drag second icon over first icon
|
||||
actions.move(icons[0]).perform();
|
||||
|
||||
// Test that the third icon returns to its original position
|
||||
client.waitFor(function() {
|
||||
return icons[2].location().x === location3.x;
|
||||
});
|
||||
|
||||
// Test that the first icon moves out of the way in the right direction
|
||||
client.waitFor(function() {
|
||||
return icons[0].location().x > location1.x;
|
||||
});
|
||||
});
|
||||
});
|
||||
30
gaia/apps/homescreen/test/marionette/app_short_name_test.js
Executable file
30
gaia/apps/homescreen/test/marionette/app_short_name_test.js
Executable file
|
|
@ -0,0 +1,30 @@
|
|||
/* global __dirname */
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
|
||||
marionette('Homescreen - App manifest', function() {
|
||||
var manifestName = 'shortnameapp.gaiamobile.org';
|
||||
var manifestUrl = 'app://' + manifestName + '/manifest.webapp';
|
||||
|
||||
var clientOptions = require(__dirname + '/client_options.js');
|
||||
clientOptions.apps = {};
|
||||
clientOptions.apps[manifestName] = __dirname + '/fixtures/short_name_app';
|
||||
var client = marionette.client({ profile: clientOptions });
|
||||
|
||||
var home, system;
|
||||
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
system.waitForFullyLoaded();
|
||||
|
||||
client.apps.launch(home.URL);
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
test('short_name', function() {
|
||||
assert.equal(home.getIconText(home.getIcon(manifestUrl)), 'Short name');
|
||||
});
|
||||
});
|
||||
|
||||
78
gaia/apps/homescreen/test/marionette/app_uninstall_pending_test.js
Executable file
78
gaia/apps/homescreen/test/marionette/app_uninstall_pending_test.js
Executable file
|
|
@ -0,0 +1,78 @@
|
|||
/* global __dirname */
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
|
||||
var AppInstall =
|
||||
require('../../../../apps/system/test/marionette/lib/app_install');
|
||||
var createAppServer = require('./server/parent');
|
||||
|
||||
marionette('Homescreen - App uninstall while pending', function() {
|
||||
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js'),
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
var actions, home, system, appInstall;
|
||||
|
||||
var server;
|
||||
setup(function(done) {
|
||||
var app = __dirname + '/fixtures/template_app';
|
||||
createAppServer(app, client, function(err, _server) {
|
||||
server = _server;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
appInstall = new AppInstall(client);
|
||||
|
||||
actions = client.loader.getActions();
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
|
||||
// ensure that the zip file does not get sent
|
||||
server.cork(server.applicationZipUri);
|
||||
|
||||
// wait for the system app to be running
|
||||
system.waitForFullyLoaded();
|
||||
client.apps.launch(home.URL);
|
||||
home.waitForLaunch();
|
||||
|
||||
// install the app
|
||||
client.switchToFrame();
|
||||
appInstall.installPackage(server.packageManifestURL);
|
||||
|
||||
// switch to the homescreen
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
});
|
||||
|
||||
test('uninstall the app', function() {
|
||||
var icon = home.getIcon(server.packageManifestURL);
|
||||
home.scrollIconToCenter(icon);
|
||||
|
||||
// remove the icon
|
||||
actions.longPress(icon, 1).perform();
|
||||
home.waitForEditBar();
|
||||
home.removeButton.tap();
|
||||
|
||||
// confirm the dialog to ensure it was removed.
|
||||
client.switchToFrame();
|
||||
home.confirmDialog('remove');
|
||||
|
||||
// ensure the icon disappears
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
client.helper.waitForElementToDisappear(icon);
|
||||
|
||||
// make sure app has gone after restart
|
||||
home.restart();
|
||||
client.setSearchTimeout(20);
|
||||
icon = null;
|
||||
try {
|
||||
icon = home.getIcon(server.packageManifestURL);
|
||||
} catch(e) { }
|
||||
assert.ok(!icon, 'app was not removed');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
85
gaia/apps/homescreen/test/marionette/app_uninstall_test.js
Executable file
85
gaia/apps/homescreen/test/marionette/app_uninstall_test.js
Executable file
|
|
@ -0,0 +1,85 @@
|
|||
/* global __dirname */
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
|
||||
var AppInstall =
|
||||
require('../../../../apps/system/test/marionette/lib/app_install');
|
||||
var createAppServer = require('./server/parent');
|
||||
|
||||
marionette('Homescreen - App Uninstall', function() {
|
||||
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js'),
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
var actions, home, system, appInstall;
|
||||
|
||||
var server;
|
||||
setup(function(done) {
|
||||
var app = __dirname + '/fixtures/template_app';
|
||||
createAppServer(app, client, function(err, _server) {
|
||||
server = _server;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
appInstall = new AppInstall(client);
|
||||
|
||||
actions = client.loader.getActions();
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
system.waitForFullyLoaded();
|
||||
|
||||
client.apps.launch(home.URL);
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
function test_app_uninstall(manifestURL) {
|
||||
// Install the app
|
||||
client.switchToFrame();
|
||||
appInstall.install(manifestURL);
|
||||
appInstall.dismissToast();
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
|
||||
var icon = home.getIcon(manifestURL);
|
||||
|
||||
// XXX: work around issues where the icon is hidden by other
|
||||
// status messages on the system app.
|
||||
icon.scriptWith(function(el) {
|
||||
// effectively scroll to the bottom of the screen.
|
||||
el.scrollIntoView(false);
|
||||
});
|
||||
|
||||
// remove the icon
|
||||
actions.longPress(icon, 1).perform();
|
||||
home.removeButton.tap();
|
||||
|
||||
// confirm the dialog to ensure it was removed.
|
||||
client.switchToFrame();
|
||||
home.confirmDialog('remove');
|
||||
|
||||
// ensure the icon disappears
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
client.helper.waitForElementToDisappear(icon);
|
||||
|
||||
// make sure app has gone after restart
|
||||
home.restart();
|
||||
client.setSearchTimeout(20);
|
||||
icon = null;
|
||||
try {
|
||||
icon = home.getIcon(server.packageManifestURL);
|
||||
} catch(e) { }
|
||||
assert.ok(!icon, 'app was not removed');
|
||||
}
|
||||
|
||||
test('uninstall hosted app', function() {
|
||||
test_app_uninstall(server.manifestURL);
|
||||
});
|
||||
|
||||
test('uninstall packaged app', function() {
|
||||
test_app_uninstall(server.packageManifestURL);
|
||||
});
|
||||
});
|
||||
|
||||
69
gaia/apps/homescreen/test/marionette/app_unrecoverable_error_test.js
Executable file
69
gaia/apps/homescreen/test/marionette/app_unrecoverable_error_test.js
Executable file
|
|
@ -0,0 +1,69 @@
|
|||
/* global __dirname */
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var AppInstall =
|
||||
require('../../../../apps/system/test/marionette/lib/app_install');
|
||||
var createAppServer = require('./server/parent');
|
||||
|
||||
marionette('Homescreen - App unrecoverable error', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js'),
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
var server;
|
||||
setup(function(done) {
|
||||
var app = __dirname + '/fixtures/template_app';
|
||||
createAppServer(app, client, function(err, _server) {
|
||||
server = _server;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
var home, system, appInstall;
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
appInstall = new AppInstall(client);
|
||||
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
teardown(function(done) {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
test('remove an app that was in an unrecoverable state', function() {
|
||||
client.switchToFrame();
|
||||
|
||||
server.serverError(server.applicationZipUri);
|
||||
appInstall.installPackage(server.packageManifestURL);
|
||||
appInstall.dismissToast();
|
||||
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
|
||||
// if the server returns a 500 error the app is assumed to be in an
|
||||
// unrecoverable state.
|
||||
var icon = home.getIcon(server.packageManifestURL);
|
||||
home.waitForIconImageUrl(icon, 'unrecoverable');
|
||||
|
||||
// agree to uninstall the app
|
||||
icon.tap();
|
||||
client.switchToFrame();
|
||||
home.confirmDialog('unrecoverable');
|
||||
|
||||
// ensure the icon disappears
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
client.helper.waitForElementToDisappear(icon);
|
||||
|
||||
// make sure app has gone after restart
|
||||
home.restart();
|
||||
client.setSearchTimeout(20);
|
||||
icon = null;
|
||||
try {
|
||||
icon = home.getIcon(server.packageManifestURL);
|
||||
} catch(e) { }
|
||||
assert.ok(!icon, 'app was not removed');
|
||||
});
|
||||
});
|
||||
10
gaia/apps/homescreen/test/marionette/client_options.js
Executable file
10
gaia/apps/homescreen/test/marionette/client_options.js
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
module.exports = {
|
||||
prefs: {
|
||||
'dom.webcomponents.enabled': true
|
||||
},
|
||||
settings: {
|
||||
'devtools.pseudolocalization.enabled': false,
|
||||
'language.current': 'en-US',
|
||||
'homescreen.manifestURL': 'app://homescreen.gaiamobile.org/manifest.webapp'
|
||||
}
|
||||
};
|
||||
12
gaia/apps/homescreen/test/marionette/client_options_bookmarks.js
Executable file
12
gaia/apps/homescreen/test/marionette/client_options_bookmarks.js
Executable file
|
|
@ -0,0 +1,12 @@
|
|||
module.exports = {
|
||||
prefs: {
|
||||
'dom.webcomponents.enabled': true,
|
||||
'dom.mozApps.homescreenURL':
|
||||
'app://homescreen.gaiamobile.org/manifest.webapp'
|
||||
},
|
||||
settings: {
|
||||
'devtools.pseudolocalization.enabled': false,
|
||||
'language.current': 'en-US',
|
||||
'homescreen.manifestURL': 'app://homescreen.gaiamobile.org/manifest.webapp'
|
||||
}
|
||||
};
|
||||
13
gaia/apps/homescreen/test/marionette/client_options_verticalhome.js
Executable file
13
gaia/apps/homescreen/test/marionette/client_options_verticalhome.js
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
module.exports = {
|
||||
prefs: {
|
||||
'dom.webcomponents.enabled': true,
|
||||
'dom.mozApps.homescreenURL':
|
||||
'app://homescreen.gaiamobile.org/manifest.webapp'
|
||||
},
|
||||
settings: {
|
||||
'devtools.pseudolocalization.enabled': false,
|
||||
'language.current': 'en-US',
|
||||
'homescreen.manifestURL':
|
||||
'app://verticalhome.gaiamobile.org/manifest.webapp'
|
||||
}
|
||||
};
|
||||
0
gaia/apps/homescreen/test/marionette/fixtures/appcache/Blank.png
Executable file
0
gaia/apps/homescreen/test/marionette/fixtures/appcache/Blank.png
Executable file
4
gaia/apps/homescreen/test/marionette/fixtures/appcache/cache.appcache
Executable file
4
gaia/apps/homescreen/test/marionette/fixtures/appcache/cache.appcache
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
CACHE MANIFEST
|
||||
# v1 2014-06-10
|
||||
index.html
|
||||
style/icons/Blank.png
|
||||
10
gaia/apps/homescreen/test/marionette/fixtures/appcache/index.html
Executable file
10
gaia/apps/homescreen/test/marionette/fixtures/appcache/index.html
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<title>iwrotethis</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="test-app"></div>
|
||||
</body>
|
||||
</html>
|
||||
61
gaia/apps/homescreen/test/marionette/fixtures/appcache/manifest.webapp
Executable file
61
gaia/apps/homescreen/test/marionette/fixtures/appcache/manifest.webapp
Executable file
|
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"name": "Template",
|
||||
"description": "Web app template",
|
||||
"launch_path": "/index.html",
|
||||
"developer": {
|
||||
"name": "The Gaia Team",
|
||||
"url": "https://github.com/mozilla-b2g/gaia"
|
||||
},
|
||||
"locales": {
|
||||
"ar": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"de": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"el": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"en-US": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"es": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"fr": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"it": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"pt-BR": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"ru": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"tr": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"zh-TW": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
}
|
||||
},
|
||||
"default_locale": "en-US",
|
||||
"appcache_path": "/cache.appcache",
|
||||
"icons": {
|
||||
"128": "/style/icons/Blank.png"
|
||||
}
|
||||
}
|
||||
|
||||
BIN
gaia/apps/homescreen/test/marionette/fixtures/appcache/style/icons/Blank.png
Executable file
BIN
gaia/apps/homescreen/test/marionette/fixtures/appcache/style/icons/Blank.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
|
|
@ -0,0 +1 @@
|
|||
This is an invalid favicon for testing purposes.
|
||||
11
gaia/apps/homescreen/test/marionette/fixtures/invalid_favicon/sample.html
Executable file
11
gaia/apps/homescreen/test/marionette/fixtures/invalid_favicon/sample.html
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Sample page</title>
|
||||
</head>
|
||||
<body>
|
||||
<header><h1>Mozilla</h1></header>
|
||||
<section>We are global community with a common purpose</section>
|
||||
</body>
|
||||
</html>
|
||||
0
gaia/apps/homescreen/test/marionette/fixtures/role_homescreen/Blank.png
Executable file
0
gaia/apps/homescreen/test/marionette/fixtures/role_homescreen/Blank.png
Executable file
10
gaia/apps/homescreen/test/marionette/fixtures/role_homescreen/index.html
Executable file
10
gaia/apps/homescreen/test/marionette/fixtures/role_homescreen/index.html
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<title>iwrotethis</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="test-app"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"name": "Template",
|
||||
"description": "Web app template",
|
||||
"launch_path": "/index.html",
|
||||
"developer": {
|
||||
"name": "The Gaia Team",
|
||||
"url": "https://github.com/mozilla-b2g/gaia"
|
||||
},
|
||||
"locales": {
|
||||
"ar": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"de": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"el": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"en-US": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"es": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"fr": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"it": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"pt-BR": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"ru": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"tr": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"zh-TW": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
}
|
||||
},
|
||||
"role": "homescreen",
|
||||
"default_locale": "en-US",
|
||||
"icons": {
|
||||
"128": "/style/icons/Blank.png"
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
11
gaia/apps/homescreen/test/marionette/fixtures/sample.html
Executable file
11
gaia/apps/homescreen/test/marionette/fixtures/sample.html
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Sample page</title>
|
||||
</head>
|
||||
<body>
|
||||
<header><h1>Mozilla</h1></header>
|
||||
<section>We are global community with a common purpose</section>
|
||||
</body>
|
||||
</html>
|
||||
9
gaia/apps/homescreen/test/marionette/fixtures/short_name_app/index.html
Executable file
9
gaia/apps/homescreen/test/marionette/fixtures/short_name_app/index.html
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<title>App with a short name</title>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
14
gaia/apps/homescreen/test/marionette/fixtures/short_name_app/manifest.webapp
Executable file
14
gaia/apps/homescreen/test/marionette/fixtures/short_name_app/manifest.webapp
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "Loooooooooooooooooooooooooooooong name",
|
||||
"short_name": "Short name",
|
||||
"description": "Web app with a short name in the manifest",
|
||||
"launch_path": "/index.html",
|
||||
"developer": {
|
||||
"name": "The Gaia Team",
|
||||
"url": "https://github.com/mozilla-b2g/gaia"
|
||||
},
|
||||
"icons": {
|
||||
"128": "/style/icons/Blank.png"
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
0
gaia/apps/homescreen/test/marionette/fixtures/template_app/Blank.png
Executable file
0
gaia/apps/homescreen/test/marionette/fixtures/template_app/Blank.png
Executable file
10
gaia/apps/homescreen/test/marionette/fixtures/template_app/index.html
Executable file
10
gaia/apps/homescreen/test/marionette/fixtures/template_app/index.html
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<title>iwrotethis</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="test-app"></div>
|
||||
</body>
|
||||
</html>
|
||||
60
gaia/apps/homescreen/test/marionette/fixtures/template_app/manifest.webapp
Executable file
60
gaia/apps/homescreen/test/marionette/fixtures/template_app/manifest.webapp
Executable file
|
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
"name": "Template",
|
||||
"description": "Web app template",
|
||||
"launch_path": "/index.html",
|
||||
"developer": {
|
||||
"name": "The Gaia Team",
|
||||
"url": "https://github.com/mozilla-b2g/gaia"
|
||||
},
|
||||
"locales": {
|
||||
"ar": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"de": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"el": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"en-US": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"es": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"fr": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"it": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"pt-BR": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"ru": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"tr": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"zh-TW": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
}
|
||||
},
|
||||
"default_locale": "en-US",
|
||||
"icons": {
|
||||
"128": "/style/icons/Blank.png"
|
||||
}
|
||||
}
|
||||
|
||||
BIN
gaia/apps/homescreen/test/marionette/fixtures/template_app/style/icons/Blank.png
Executable file
BIN
gaia/apps/homescreen/test/marionette/fixtures/template_app/style/icons/Blank.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
|
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<title>updatedwow</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="test-app"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"name": "Template",
|
||||
"description": "Web app template",
|
||||
"launch_path": "/index.html",
|
||||
"developer": {
|
||||
"name": "The Gaia Team",
|
||||
"url": "https://github.com/mozilla-b2g/gaia"
|
||||
},
|
||||
"locales": {
|
||||
"ar": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"de": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"el": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"en-US": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"es": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"fr": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"it": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"pt-BR": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"ru": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"tr": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
},
|
||||
"zh-TW": {
|
||||
"name": "Template",
|
||||
"description": "Web app template"
|
||||
}
|
||||
},
|
||||
"default_locale": "en-US",
|
||||
"icons": {
|
||||
"128": "/style/icons/MagicIcon.png"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
72
gaia/apps/homescreen/test/marionette/home_button_test.js
Executable file
72
gaia/apps/homescreen/test/marionette/home_button_test.js
Executable file
|
|
@ -0,0 +1,72 @@
|
|||
'use strict';
|
||||
/* global __dirname */
|
||||
|
||||
var Pinning = require(
|
||||
'../../../../apps/system/test/marionette/lib/pinning_the_web');
|
||||
var Bookmark = require('../../../../apps/system/test/marionette/lib/bookmark');
|
||||
var Server = require('../../../../shared/test/integration/server');
|
||||
var assert = require('assert');
|
||||
|
||||
marionette('Homescreen - Home button', function() {
|
||||
var client = marionette.client({
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
var actions, bookmark, pinning, home, server, system;
|
||||
|
||||
suiteSetup(function(done) {
|
||||
Server.create(__dirname + '/fixtures/', function(err, _server) {
|
||||
server = _server;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
server.stop();
|
||||
});
|
||||
|
||||
var url;
|
||||
setup(function() {
|
||||
actions = client.loader.getActions();
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
pinning = new Pinning(client);
|
||||
bookmark = new Bookmark(client, server);
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
test('scroll to the top of the screen', function() {
|
||||
client.executeScript(function(scrollable) {
|
||||
scrollable.scrollTop = 100;
|
||||
}, [home.appsScrollable]);
|
||||
|
||||
system.tapHome();
|
||||
home.waitForLaunch();
|
||||
|
||||
assert.equal(client.executeScript(function(scrollable) {
|
||||
return scrollable.scrollTop;
|
||||
}, [home.appsScrollable]), 0);
|
||||
});
|
||||
|
||||
test('exit edit mode', function() {
|
||||
url = server.url('sample.html');
|
||||
client.switchToFrame();
|
||||
pinning.openAndPinSiteFromBrowser(url);
|
||||
system.dismissBanner();
|
||||
|
||||
system.tapHome();
|
||||
home.waitForLaunch();
|
||||
|
||||
var icon = home.getIcon(url);
|
||||
|
||||
home.scrollIconToCenter(icon);
|
||||
actions.longPress(icon, 1).perform();
|
||||
|
||||
system.tapHome();
|
||||
home.waitForLaunch();
|
||||
|
||||
assert.equal(client.executeScript(function() {
|
||||
return document.body.classList.contains('edit-mode');
|
||||
}), false);
|
||||
});
|
||||
});
|
||||
112
gaia/apps/homescreen/test/marionette/layout_test.js
Executable file
112
gaia/apps/homescreen/test/marionette/layout_test.js
Executable file
|
|
@ -0,0 +1,112 @@
|
|||
'use strict';
|
||||
var assert = require('assert');
|
||||
|
||||
marionette('Homescreen - Layout', function() {
|
||||
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
var actions, home, system;
|
||||
|
||||
setup(function() {
|
||||
actions = client.loader.getActions();
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
test('apps grid is LTR in LTR language', function() {
|
||||
var icons = home.visibleIcons;
|
||||
var app1Location = icons[0].location();
|
||||
var app2Location = icons[1].location();
|
||||
var app3Location = icons[2].location();
|
||||
var gridWidth = home.container.size().width;
|
||||
|
||||
// check that they are all on the same line
|
||||
assert.equal(app1Location.y, app2Location.y,
|
||||
'1st and 2nd apps should be on the same line.');
|
||||
assert.equal(app2Location.y, app3Location.y,
|
||||
'2nd and 3rd apps should be on the same line.');
|
||||
|
||||
// check left-alignement
|
||||
assert.ok(app1Location.x < gridWidth / 3, 'First app is left-aligned');
|
||||
// check the ordering
|
||||
assert.ok(app1Location.x < app2Location.x,
|
||||
'App1 icon should be on the left of app2 icon');
|
||||
assert.ok(app2Location.x < app3Location.x,
|
||||
'App2 icon should be on the left of app3 icon');
|
||||
});
|
||||
|
||||
test('apps grid is RTL in RTL language', function() {
|
||||
client.switchToFrame();
|
||||
client.settings.set('language.current', 'ar-x-psbidi');
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
|
||||
// Localization can be async, wait for the content to update
|
||||
var localizedPhoneName =
|
||||
home.localizedAppName('communications', 'dialer', 'ar-x-psbidi');
|
||||
client.waitFor(function() {
|
||||
return !!home.getIconByName(localizedPhoneName);
|
||||
});
|
||||
|
||||
// Relayout on localisation change is animated, so wait for it to finish
|
||||
actions.wait(1).perform();
|
||||
|
||||
var icons = home.visibleIcons;
|
||||
var app1Location = icons[0].location();
|
||||
var app2Location = icons[1].location();
|
||||
var app3Location = icons[2].location();
|
||||
var gridWidth = home.container.size().width;
|
||||
|
||||
// check that they are all on the same line
|
||||
assert.equal(app1Location.y, app2Location.y,
|
||||
'1st and 2nd apps should be on the same line.');
|
||||
assert.equal(app2Location.y, app3Location.y,
|
||||
'2st and 3nd apps should be on the same line.');
|
||||
|
||||
// check right-alignement
|
||||
assert.ok(app1Location.x > gridWidth / 2, 'First app is right-aligned');
|
||||
// check the ordering
|
||||
assert.ok(app1Location.x > app2Location.x,
|
||||
'App1 icon should be on the right of app2 icon');
|
||||
assert.ok(app2Location.x > app3Location.x,
|
||||
'App2 icon should be on the right of app3 icon');
|
||||
});
|
||||
|
||||
test('switching to 4-column layout works', function() {
|
||||
// check that we're in 3-column layout
|
||||
client.waitFor(function() {
|
||||
var icons = home.visibleIcons;
|
||||
var app1Location = icons[0].location();
|
||||
var app2Location = icons[1].location();
|
||||
var app3Location = icons[2].location();
|
||||
var app4Location = icons[3].location();
|
||||
|
||||
return ((app1Location.y === app2Location.y) &&
|
||||
(app2Location.y === app3Location.y) &&
|
||||
(app4Location.y > app3Location.y));
|
||||
});
|
||||
|
||||
// Switch the setting for 4-column layout
|
||||
client.executeScript(function () {
|
||||
navigator.getDataStores('homescreen_settings').then(
|
||||
function(stores) {
|
||||
stores[0].put(4, 'grid.cols');
|
||||
});
|
||||
});
|
||||
|
||||
// check that we're in 4-column layout
|
||||
client.waitFor(function() {
|
||||
var icons = home.visibleIcons;
|
||||
var app1Location = icons[0].location();
|
||||
var app2Location = icons[1].location();
|
||||
var app3Location = icons[2].location();
|
||||
var app4Location = icons[3].location();
|
||||
|
||||
return ((app1Location.y === app2Location.y) &&
|
||||
(app2Location.y === app3Location.y) &&
|
||||
(app3Location.y === app4Location.y));
|
||||
});
|
||||
});
|
||||
});
|
||||
455
gaia/apps/homescreen/test/marionette/lib/homescreen.js
Executable file
455
gaia/apps/homescreen/test/marionette/lib/homescreen.js
Executable file
|
|
@ -0,0 +1,455 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Abstraction around homescreen.
|
||||
* @constructor
|
||||
*/
|
||||
function Homescreen(client) {
|
||||
this.client = client;
|
||||
this.system = client.loader.getAppClass('system');
|
||||
}
|
||||
|
||||
Homescreen.URL = 'app://homescreen.gaiamobile.org';
|
||||
|
||||
Homescreen.Selectors = {
|
||||
appsScrollable: '#apps-panel .scrollable',
|
||||
apps: '#apps',
|
||||
pages: '#pages',
|
||||
icon: '#apps gaia-app-icon',
|
||||
card: '#pages gaia-pin-card',
|
||||
bottomBar: '#bottombar',
|
||||
remove: '#remove',
|
||||
rename: '#rename',
|
||||
done: '#done',
|
||||
cancelDownload: '#cancel-download',
|
||||
resumeDownload: '#resume-download'
|
||||
};
|
||||
|
||||
Homescreen.prototype = {
|
||||
|
||||
URL: Homescreen.URL,
|
||||
Selectors: Homescreen.Selectors,
|
||||
|
||||
get appsScrollable() {
|
||||
return this.client.findElement(Homescreen.Selectors.appsScrollable);
|
||||
},
|
||||
|
||||
get container() {
|
||||
return this.client.findElement(Homescreen.Selectors.apps);
|
||||
},
|
||||
|
||||
get icons() {
|
||||
return this.client.findElements(Homescreen.Selectors.icon);
|
||||
},
|
||||
|
||||
get cards() {
|
||||
return this.client.findElements(Homescreen.Selectors.card);
|
||||
},
|
||||
|
||||
get visibleIcons() {
|
||||
return this.icons.filter(function(el) {
|
||||
return el.scriptWith(function(el) {
|
||||
return el.parentNode.style.display !== 'none';
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
get visibleCards() {
|
||||
return this.client.executeScript(function(selector) {
|
||||
var cards = document.body.querySelectorAll(selector);
|
||||
var visibles = [];
|
||||
for (var i = cards.length - 1; i >= 0; i--) {
|
||||
if (cards[i].parentNode.style.display !== 'none') {
|
||||
visibles.push(cards[i]);
|
||||
}
|
||||
}
|
||||
return visibles;
|
||||
}, [Homescreen.Selectors.card]);
|
||||
},
|
||||
|
||||
get removeButton() {
|
||||
return this.client.helper.waitForElement(Homescreen.Selectors.remove);
|
||||
},
|
||||
|
||||
get renameButton() {
|
||||
return this.client.helper.waitForElement(Homescreen.Selectors.rename);
|
||||
},
|
||||
|
||||
get doneButton() {
|
||||
return this.client.helper.waitForElement(Homescreen.Selectors.done);
|
||||
},
|
||||
|
||||
get cancelDownloadDialog() {
|
||||
return this.client.findElement(Homescreen.Selectors.cancelDownload);
|
||||
},
|
||||
|
||||
get resumeDownloadDialog() {
|
||||
return this.client.findElement(Homescreen.Selectors.resumeDownload);
|
||||
},
|
||||
|
||||
/**
|
||||
* Waits for the homescreen to launch and switches to the frame.
|
||||
*/
|
||||
waitForLaunch: function() {
|
||||
var client = this.client;
|
||||
client.helper.waitForElement('body');
|
||||
client.apps.switchToApp(Homescreen.URL);
|
||||
|
||||
// Wait for startup to complete
|
||||
client.waitFor(function() {
|
||||
return client.executeScript(function() {
|
||||
return window.wrappedJSObject.appWindow.apps.startupMetadata === null;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Waits for the edit bar to fully appear.
|
||||
*/
|
||||
waitForEditBar: function() {
|
||||
var body = this.client.findElement('body');
|
||||
var bar = this.client.helper.waitForElement(Homescreen.Selectors.bottomBar);
|
||||
this.client.waitFor(function() {
|
||||
var barRect = bar.rect();
|
||||
return barRect.y === (body.rect().height - barRect.height);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Find and return every id for all the items on the grid... Each element
|
||||
* can be used with `.getIcon` to find the element for a given id.
|
||||
*
|
||||
* @return {Array[String]}
|
||||
*/
|
||||
getIconIdentifiers: function() {
|
||||
return this.visibleIcons.map(function(el) {
|
||||
return this.getIconId(el);
|
||||
}, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch an icon element on the homescreen by its identifier.
|
||||
* For apps, the identifier is the manifestURL, or its manifestURL,
|
||||
* followed by a '/' followed by its entry point. For bookmarks, the
|
||||
* identifier is the bookmarked URL.
|
||||
*
|
||||
* @param {String} identifier The identifier of the icon.
|
||||
* @return {Marionette.Element}
|
||||
*/
|
||||
getIcon: function(identifier) {
|
||||
return this.client.findElement(
|
||||
Homescreen.Selectors.apps + ' [data-identifier*="' + identifier + '"]');
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch a card element on the homescreen by its identifier.
|
||||
* For apps, the identifier is the manifestURL, or its manifestURL,
|
||||
* followed by a '/' followed by its entry point. For pin, the
|
||||
* identifier is the pinned URL.
|
||||
*/
|
||||
getCard: function(identifier) {
|
||||
return this.client.findElement(
|
||||
Homescreen.Selectors.pages + ' [data-id="' + identifier + '"]');
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch an icon element on the homescreen by its name.
|
||||
*
|
||||
* @param {String} name The name of the icon.
|
||||
* @return {Marionette.Element}
|
||||
*/
|
||||
getIconByName: function(name) {
|
||||
function getName(icon) {
|
||||
return icon.shadowRoot.querySelector('#subtitle').textContent;
|
||||
}
|
||||
|
||||
var icons = this.icons;
|
||||
for (var i = 0, iLen = icons.length; i < iLen; i++) {
|
||||
if (icons[i].scriptWith(getName) === name) {
|
||||
return icons[i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a homescreen icon element's identifier.
|
||||
*
|
||||
* @param {Marionette.Element} icon A homescreen icon element reference.
|
||||
*/
|
||||
getIconId: function(icon) {
|
||||
return icon.scriptWith(function(el) {
|
||||
return el.dataset.identifier;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a homescreen icon element's text.
|
||||
*
|
||||
* @param {Marionette.Element} icon A homescreen icon element reference.
|
||||
*/
|
||||
getIconText: function(icon) {
|
||||
return icon.scriptWith(function(el) {
|
||||
return el.shadowRoot.querySelector('#subtitle').textContent;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a data-url of the image element in a homescreen icon, if it exists.
|
||||
*
|
||||
* @param {Marionette.Element} icon A homescreen icon element reference.
|
||||
*/
|
||||
getIconImage: function(icon) {
|
||||
return icon.scriptWith(function(el) {
|
||||
var image = el.shadowRoot.querySelector('#image-container img');
|
||||
if (!image) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = image.width;
|
||||
canvas.height = image.height;
|
||||
|
||||
var ctx = canvas.getContext('2d', { willReadFrequently: true });
|
||||
ctx.drawImage(image, 0, 0);
|
||||
|
||||
return canvas.toDataURL();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a homescreen icon element's image URL.
|
||||
*
|
||||
* @param {Marionette.Element} icon A homescreen icon element reference.
|
||||
*/
|
||||
getIconImageUrl: function(icon) {
|
||||
return icon.scriptWith(function(el) {
|
||||
return el.dataset.testIconUrl;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a homescreen card element's image URL.
|
||||
*
|
||||
* @param {Marionette.Element} card A homescreen card element reference.
|
||||
*/
|
||||
getCardImageUrl: function(card) {
|
||||
return card.scriptWith(function(el) {
|
||||
var icon = el.shadowRoot.querySelector('.icon-container i');
|
||||
return icon.style.backgroundImage;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Waits for an icon's image URL to contain a certain value.
|
||||
*
|
||||
* @param {Marionette.Element} icon A homescreen icon element reference.
|
||||
* @0aram {String} string A string to search for in the image URL.
|
||||
*/
|
||||
waitForIconImageUrl: function(icon, string) {
|
||||
this.client.waitFor(function() {
|
||||
var src = this.getIconImageUrl(icon);
|
||||
return src && src.indexOf(string) !== -1;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if a homescreen icon is in a loading state, false otherwise.
|
||||
*
|
||||
* @param {Marionette.Element} icon A homescreen icon element reference.
|
||||
*/
|
||||
iconIsLoading: function(icon) {
|
||||
return icon.scriptWith(function(el) {
|
||||
return el.shadowRoot.querySelector('#image-container').
|
||||
classList.contains('downloading');
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Launches an icon and switches to the corresponding application.
|
||||
*
|
||||
* @param {Marionette.Element} icon A homescreen icon element reference.
|
||||
*/
|
||||
launchIcon: function(icon) {
|
||||
var identifier =
|
||||
icon.scriptWith(function(el) { return el.dataset.identifier; });
|
||||
icon.tap();
|
||||
|
||||
var identifierWithoutEntryPoint = identifier.replace(/\/[^\/]*$/, '');
|
||||
var client = this.client.scope({ searchTimeout: 100 });
|
||||
var frame;
|
||||
|
||||
client.waitFor(function() {
|
||||
// wait for the app to show up
|
||||
client.switchToFrame();
|
||||
try {
|
||||
frame = client.findElement('iframe[mozapp="' + identifier + '"]');
|
||||
} catch (e1) {
|
||||
try {
|
||||
frame = client.findElement(
|
||||
'iframe[mozapp="' + identifierWithoutEntryPoint + '"]');
|
||||
} catch(e2) {
|
||||
try {
|
||||
frame = client.findElement(
|
||||
'iframe[data-frame-origin*="' + identifier + '"]');
|
||||
} catch (e3) {
|
||||
// try again...
|
||||
client.switchToFrame(this.system.getHomescreenIframe());
|
||||
icon.tap();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
client.switchToFrame(frame);
|
||||
return true;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Scrolls the homescreen so that the given icon is located roughly in
|
||||
* the center of the screen, vertically.
|
||||
*
|
||||
* @param {Marionette.Element} icon A homescreen icon element reference.
|
||||
*/
|
||||
scrollIconToCenter: function(icon) {
|
||||
this.client.executeScript(function(icon, scrollable, position) {
|
||||
var midPoint = scrollable.clientHeight / 2;
|
||||
scrollable.scrollTop = scrollable.scrollTop + (position - midPoint);
|
||||
}, [icon, this.appsScrollable, icon.location().y]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Restart the homescreen then refocus on it.
|
||||
*/
|
||||
restart: function() {
|
||||
this.client.executeScript(function() {
|
||||
window.close();
|
||||
});
|
||||
|
||||
// initialize our frames again since we killed the iframe
|
||||
this.client.switchToFrame();
|
||||
this.client.switchToFrame(this.system.getHomescreenIframe());
|
||||
},
|
||||
|
||||
/**
|
||||
* Perform an action in an action dialog.
|
||||
*
|
||||
* @param {Marionette.Element} dialog An action dialog element reference.
|
||||
* @param {String} action The data-l10n-id of the action to perform.
|
||||
*/
|
||||
actionDialog: function(dialog, action) {
|
||||
var button = this.client.helper.waitForElement(
|
||||
'button[data-l10n-id="' + action + '"]');
|
||||
button.tap();
|
||||
this.client.helper.waitForElementToDisappear(button);
|
||||
},
|
||||
|
||||
/**
|
||||
* Click confirm on a particular type of confirmation dialog.
|
||||
*
|
||||
* @param {String} type of dialog.
|
||||
* @param {String} selector of the button. Defaults to .confirm.
|
||||
*/
|
||||
confirmDialog: function(type, button) {
|
||||
var selector = 'gaia-confirm[data-type="' + type + '"]';
|
||||
var dialog = this.client.helper.waitForElement(selector);
|
||||
|
||||
var confirm;
|
||||
this.client.waitFor(function() {
|
||||
confirm = dialog.findElement(button || '.confirm');
|
||||
return confirm && confirm.displayed();
|
||||
});
|
||||
|
||||
// XXX: Hack to use faster polling
|
||||
var quickly = this.client.scope({ searchTimeout: 50 });
|
||||
confirm.client = quickly;
|
||||
|
||||
// tricky logic to ensure the dialog has been removed and clicked
|
||||
this.client.waitFor(function() {
|
||||
try {
|
||||
// click the dialog to dismiss it
|
||||
confirm.click();
|
||||
// ensure it is either hidden or hits the stale element ref
|
||||
return !confirm.displayed();
|
||||
} catch (e) {
|
||||
if (e.type === 'StaleElementReference') {
|
||||
// element was successfully removed
|
||||
return true;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Emulates pressing of the hardware home button.
|
||||
*/
|
||||
pressHomeButton: function() {
|
||||
this.client.executeScript(function() {
|
||||
var home = new CustomEvent('home');
|
||||
window.dispatchEvent(home);
|
||||
});
|
||||
this.client.apps.switchToApp(Homescreen.URL);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a localized application name from a manifest.
|
||||
* @param {String} app to open
|
||||
* @param {String} entryPoint to open
|
||||
* @param {String} locale
|
||||
*/
|
||||
localizedAppName: function(app, entryPoint, locale) {
|
||||
if (!locale) {
|
||||
locale = entryPoint;
|
||||
entryPoint = null;
|
||||
}
|
||||
|
||||
var file = 'app://' + app + '.gaiamobile.org/manifest.webapp';
|
||||
// use a chrome-scoped Marionette client for the cross-domain XHR
|
||||
var chromeClient = this.client.scope({context: 'chrome'});
|
||||
var manifest = chromeClient.executeAsyncScript(function(file) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', file, true);
|
||||
xhr.onload = function(o) {
|
||||
var data = JSON.parse(xhr.response);
|
||||
marionetteScriptFinished(data);
|
||||
};
|
||||
xhr.send(null);
|
||||
}, [file]);
|
||||
|
||||
var locales;
|
||||
if (entryPoint) {
|
||||
locales = manifest.entry_points[entryPoint].locales;
|
||||
} else {
|
||||
locales = manifest.locales;
|
||||
}
|
||||
|
||||
if (!locales) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (locale.indexOf('-x-ps') > -1) {
|
||||
return this.client.executeScript(function(locale, name) {
|
||||
var mozL10n = window.wrappedJSObject.navigator.mozL10n;
|
||||
return mozL10n.qps[locale].translate(name);
|
||||
}, [locale, locales['en-US'].name]);
|
||||
}
|
||||
|
||||
return locales[locale].name;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a localized string from a properties file.
|
||||
* @param {String} key of the string to lookup.
|
||||
*/
|
||||
l10n: function(key) {
|
||||
var string = this.client.executeScript(function(key) {
|
||||
return window.wrappedJSObject.navigator.mozL10n.get(key);
|
||||
}, [key]);
|
||||
|
||||
return string;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Homescreen;
|
||||
39
gaia/apps/homescreen/test/marionette/localization_test.js
Executable file
39
gaia/apps/homescreen/test/marionette/localization_test.js
Executable file
|
|
@ -0,0 +1,39 @@
|
|||
'use strict';
|
||||
var assert = require('assert');
|
||||
|
||||
marionette('Homescreen - Localization', function() {
|
||||
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options.js')
|
||||
});
|
||||
var actions, home, system;
|
||||
|
||||
setup(function() {
|
||||
actions = client.loader.getActions();
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
test('Localization updates icon names', function() {
|
||||
var settingsIcon = home.getIconByName('Settings');
|
||||
assert.equal(
|
||||
home.getIconText(settingsIcon),
|
||||
home.localizedAppName('settings', 'en-US'));
|
||||
|
||||
client.switchToFrame();
|
||||
client.executeScript(function() {
|
||||
navigator.mozSettings.createLock().set({
|
||||
'language.current': 'fr-x-psaccent'
|
||||
});
|
||||
});
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
|
||||
// Localization can be async, wait for the content to update
|
||||
var newIconName = home.localizedAppName('settings', 'fr-x-psaccent');
|
||||
client.waitFor(function() {
|
||||
return !!home.getIconByName(newIconName);
|
||||
});
|
||||
});
|
||||
});
|
||||
61
gaia/apps/homescreen/test/marionette/pinning_edit_test.js
Executable file
61
gaia/apps/homescreen/test/marionette/pinning_edit_test.js
Executable file
|
|
@ -0,0 +1,61 @@
|
|||
'use strict';
|
||||
/* global __dirname */
|
||||
|
||||
var Pinning = require(
|
||||
'../../../../apps/system/test/marionette/lib/pinning_the_web');
|
||||
var Bookmark = require('../../../../apps/system/test/marionette/lib/bookmark');
|
||||
var Server = require('../../../../shared/test/integration/server');
|
||||
|
||||
marionette('Homescreen - Pinned Site Edit', function() {
|
||||
var client = marionette.client({
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
var actions, bookmark, pinning, home, server, system;
|
||||
|
||||
suiteSetup(function(done) {
|
||||
Server.create(__dirname + '/fixtures/', function(err, _server) {
|
||||
server = _server;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
server.stop();
|
||||
});
|
||||
|
||||
var url;
|
||||
setup(function() {
|
||||
actions = client.loader.getActions();
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
pinning = new Pinning(client);
|
||||
bookmark = new Bookmark(client, server);
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
|
||||
url = server.url('sample.html');
|
||||
client.switchToFrame();
|
||||
pinning.openAndPinSiteFromBrowser(url);
|
||||
system.dismissBanner();
|
||||
|
||||
system.tapHome();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
test('pressing enter after renaming the pinned site', function() {
|
||||
var icon = home.getIcon(url);
|
||||
|
||||
home.scrollIconToCenter(icon);
|
||||
actions.longPress(icon, 1).perform();
|
||||
home.waitForEditBar();
|
||||
home.renameButton.tap();
|
||||
|
||||
client.switchToFrame();
|
||||
bookmark.renameAndPressEnter('renamed');
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
|
||||
client.waitFor(function() {
|
||||
return home.getIconText(icon) === 'renamed';
|
||||
});
|
||||
});
|
||||
});
|
||||
50
gaia/apps/homescreen/test/marionette/pinning_invalid_favicon_test.js
Executable file
50
gaia/apps/homescreen/test/marionette/pinning_invalid_favicon_test.js
Executable file
|
|
@ -0,0 +1,50 @@
|
|||
'use strict';
|
||||
/* global __dirname */
|
||||
|
||||
var Pinning = require(
|
||||
'../../../../apps/system/test/marionette/lib/pinning_the_web');
|
||||
var Server = require('../../../../shared/test/integration/server');
|
||||
|
||||
var assert = require('assert');
|
||||
|
||||
marionette('Homescreen - Pinning Favicon Failure', function() {
|
||||
var client = marionette.client({
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
|
||||
var pinning, home, server, system;
|
||||
|
||||
suiteSetup(function(done) {
|
||||
Server.create(__dirname + '/fixtures/invalid_favicon/',
|
||||
function(err, _server) {
|
||||
server = _server;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
server.stop();
|
||||
});
|
||||
|
||||
var url;
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
pinning = new Pinning(client, server);
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
|
||||
url = server.url('sample.html');
|
||||
client.switchToFrame();
|
||||
pinning.openAndPinPage(url);
|
||||
|
||||
system.tapHome();
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
});
|
||||
|
||||
test('Invalid icons get default icon assigned', function() {
|
||||
var card = home.getCard(url);
|
||||
assert.equal('', home.getCardImageUrl(card), 'Card has no bg image');
|
||||
});
|
||||
|
||||
});
|
||||
90
gaia/apps/homescreen/test/marionette/pinning_order_test.js
Executable file
90
gaia/apps/homescreen/test/marionette/pinning_order_test.js
Executable file
|
|
@ -0,0 +1,90 @@
|
|||
'use strict';
|
||||
/* global __dirname */
|
||||
|
||||
var assert = require('assert');
|
||||
var Pinning = require(
|
||||
'../../../../apps/system/test/marionette/lib/pinning_the_web');
|
||||
var Server = require('../../../../shared/test/integration/server');
|
||||
|
||||
marionette('Homescreen - Pin order', function() {
|
||||
var client = marionette.client({
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
var actions, pinning, home, server, system;
|
||||
|
||||
suiteSetup(function(done) {
|
||||
Server.create(__dirname + '/fixtures/', function(err, _server) {
|
||||
server = _server;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
server.stop();
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
actions = client.loader.getActions();
|
||||
pinning = new Pinning(client);
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
test('Pin order is retained after restart', function() {
|
||||
// This test is very similar to app_order_test, but as bookmarks are
|
||||
// sourced from a different database and initialised separately to apps,
|
||||
// we need to test that their metadata is also retained across homescreen
|
||||
// restarts.
|
||||
|
||||
// Add a bookmark
|
||||
var numIcons = home.visibleIcons.length;
|
||||
var url = server.url('sample.html');
|
||||
|
||||
client.switchToFrame();
|
||||
pinning.openAndPinSiteFromBrowser(url);
|
||||
|
||||
system.tapHome();
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
|
||||
client.waitFor(function() {
|
||||
return numIcons + 1 === home.visibleIcons.length;
|
||||
});
|
||||
|
||||
// Drag pin to a different place
|
||||
var icons = home.visibleIcons;
|
||||
|
||||
icons[numIcons].scriptWith(function(el) {
|
||||
el.scrollIntoView();
|
||||
});
|
||||
|
||||
var location1 = icons[numIcons].location();
|
||||
var location2 = icons[numIcons - 1].location();
|
||||
actions.wait(0.5).press(icons[numIcons]).wait(0.5).
|
||||
move(icons[numIcons - 1]).release().wait(0.5).perform();
|
||||
|
||||
assert.equal(icons[numIcons].location().x, location2.x);
|
||||
assert.equal(icons[numIcons - 1].location().x, location1.x);
|
||||
|
||||
// Test that icon order is retained after restart
|
||||
icons = home.visibleIcons.map(function(icon) {
|
||||
return home.getIconText(icon);
|
||||
});
|
||||
numIcons = icons.length;
|
||||
|
||||
home.restart();
|
||||
|
||||
client.waitFor(function() {
|
||||
return home.visibleIcons.length === numIcons;
|
||||
});
|
||||
|
||||
var newIcons = home.visibleIcons.map(function(icon) {
|
||||
return home.getIconText(icon);
|
||||
});
|
||||
for (var i = 0, iLen = icons.length; i < iLen; i++) {
|
||||
assert.equal(icons[i], newIcons[i]);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
49
gaia/apps/homescreen/test/marionette/pinning_test.js
Executable file
49
gaia/apps/homescreen/test/marionette/pinning_test.js
Executable file
|
|
@ -0,0 +1,49 @@
|
|||
'use strict';
|
||||
/* global __dirname */
|
||||
|
||||
var Pinning = require(
|
||||
'../../../../apps/system/test/marionette/lib/pinning_the_web');
|
||||
var Server = require('../../../../shared/test/integration/server');
|
||||
|
||||
marionette('Homescreen - Pinned Sites', function() {
|
||||
var client = marionette.client({
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
var pinning, home, server, system;
|
||||
|
||||
suiteSetup(function(done) {
|
||||
Server.create(__dirname + '/fixtures/', function(err, _server) {
|
||||
server = _server;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
server.stop();
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
pinning = new Pinning(client);
|
||||
system = client.loader.getAppClass('system');
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
test('Pinning a site adds pin to homescreen', function() {
|
||||
var numIcons = home.visibleIcons.length;
|
||||
var url = server.url('sample.html');
|
||||
|
||||
client.switchToFrame();
|
||||
pinning.openAndPinSiteFromBrowser(url);
|
||||
system.dismissBanner();
|
||||
|
||||
system.tapHome();
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
|
||||
client.waitFor(function() {
|
||||
return numIcons + 1 === home.visibleIcons.length;
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
74
gaia/apps/homescreen/test/marionette/pinning_the_web_test.js
Executable file
74
gaia/apps/homescreen/test/marionette/pinning_the_web_test.js
Executable file
|
|
@ -0,0 +1,74 @@
|
|||
'use strict';
|
||||
/* global __dirname */
|
||||
|
||||
var Server = require('../../../../shared/test/integration/server');
|
||||
var Rocketbar = require('../../../system/test/marionette/lib/pinning_the_web');
|
||||
|
||||
marionette('Homescreen - Pin the web', function() {
|
||||
var options = require(__dirname + '/client_options.js');
|
||||
options.settings['layers.async-pan-zoom.enabled'] = false;
|
||||
options.settings['dev.gaia.pinning_the_web'] = true;
|
||||
var client = marionette.client({
|
||||
profile: options,
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
var home, server, system, pinning;
|
||||
|
||||
suiteSetup(function(done) {
|
||||
Server.create(__dirname + '/fixtures/', function(err, _server) {
|
||||
server = _server;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
server.stop();
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
pinning = new Rocketbar(client);
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
// Skip test since we are disabling pinning door hanger in 2.5
|
||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1207710
|
||||
test.skip('Pinning a site from the site icon', function() {
|
||||
var url = server.url('sample.html');
|
||||
|
||||
client.switchToFrame();
|
||||
pinning.openAndPinSite(url);
|
||||
|
||||
system.tapHome();
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
|
||||
home.getIcon(url);
|
||||
});
|
||||
|
||||
test('Pinning a site from the browser context menu', function() {
|
||||
var url = server.url('sample.html');
|
||||
|
||||
client.switchToFrame();
|
||||
pinning.openAndPinSiteFromBrowser(url);
|
||||
system.tapHome();
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
|
||||
home.getIcon(url);
|
||||
});
|
||||
|
||||
test('Pinning a page adds a card to the homescreen', function() {
|
||||
var url = server.url('sample.html');
|
||||
|
||||
client.switchToFrame();
|
||||
pinning.openAndPinPage(url);
|
||||
system.tapHome();
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
|
||||
client.waitFor(function() {
|
||||
return home.visibleCards.length === 1;
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
76
gaia/apps/homescreen/test/marionette/pinning_uninstall_test.js
Executable file
76
gaia/apps/homescreen/test/marionette/pinning_uninstall_test.js
Executable file
|
|
@ -0,0 +1,76 @@
|
|||
'use strict';
|
||||
/* global __dirname */
|
||||
|
||||
var assert = require('assert');
|
||||
|
||||
var Pinning = require(
|
||||
'../../../../apps/system/test/marionette/lib/pinning_the_web');
|
||||
var Bookmark = require('../../../../apps/system/test/marionette/lib/bookmark');
|
||||
var Server = require('../../../../shared/test/integration/server');
|
||||
|
||||
marionette('Homescreen - Pinned Site Uninstall', function() {
|
||||
|
||||
var client = marionette.client({
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
var actions, pinning, home, server, system, bookmark;
|
||||
|
||||
suiteSetup(function(done) {
|
||||
Server.create(__dirname + '/fixtures/', function(err, _server) {
|
||||
server = _server;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
server.stop();
|
||||
});
|
||||
|
||||
var url;
|
||||
setup(function() {
|
||||
actions = client.loader.getActions();
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
pinning = new Pinning(client);
|
||||
bookmark = new Bookmark(client);
|
||||
system.waitForFullyLoaded();
|
||||
home.waitForLaunch();
|
||||
|
||||
url = server.url('sample.html');
|
||||
client.switchToFrame();
|
||||
pinning.openAndPinSiteFromBrowser(url);
|
||||
system.dismissBanner();
|
||||
|
||||
system.tapHome();
|
||||
home.waitForLaunch();
|
||||
});
|
||||
|
||||
test('removal of pinned site', function() {
|
||||
var icon = home.getIcon(url);
|
||||
|
||||
home.scrollIconToCenter(icon);
|
||||
actions.longPress(icon, 1).perform();
|
||||
home.waitForEditBar();
|
||||
home.removeButton.tap();
|
||||
|
||||
// Confirm the dialog to ensure it was removed.
|
||||
// Note, remove dialog for pins still lives in bookmark app.
|
||||
client.switchToFrame();
|
||||
client.switchToFrame(system.getAppIframe(Bookmark.URL));
|
||||
bookmark.pinConfirmDialog('remove');
|
||||
|
||||
// ensure the icon disappears
|
||||
client.switchToFrame();
|
||||
client.switchToFrame(system.getHomescreenIframe());
|
||||
client.helper.waitForElementToDisappear(icon);
|
||||
|
||||
// ensure pinned site is gone upon restart
|
||||
home.restart();
|
||||
client.setSearchTimeout(20);
|
||||
icon = null;
|
||||
try {
|
||||
icon = home.getIcon(server.packageManifestURL);
|
||||
} catch(e) { }
|
||||
assert.ok(!icon, 'pinned site was not removed');
|
||||
});
|
||||
});
|
||||
315
gaia/apps/homescreen/test/marionette/server/child.js
Executable file
315
gaia/apps/homescreen/test/marionette/server/child.js
Executable file
|
|
@ -0,0 +1,315 @@
|
|||
/* global Buffer, process */
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
App server for fixtures. Serves up packaged apps.
|
||||
*/
|
||||
|
||||
|
||||
var debug = require('debug')('appserver:child');
|
||||
var http = require('http');
|
||||
var fs = require('fs');
|
||||
var fsPath = require('path');
|
||||
var mime = require('mime');
|
||||
var archiver = require('archiver');
|
||||
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
|
||||
function readdirRecursive(dir) {
|
||||
dir = fsPath.join(dir, '/');
|
||||
var results = [];
|
||||
var files = fs.readdirSync(dir);
|
||||
|
||||
var file;
|
||||
while((file = files.shift())) {
|
||||
file = fsPath.join(dir, file);
|
||||
var stat = fs.statSync(file);
|
||||
if (stat.isDirectory()) {
|
||||
results = results.concat(readdirRecursive(file));
|
||||
} else {
|
||||
results.push(file);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function writeJSON(res, object) {
|
||||
var json = JSON.stringify(object);
|
||||
res.writeHead(200, {
|
||||
'Content-Length': Buffer.byteLength(json),
|
||||
'Content-Type': 'application/json'
|
||||
});
|
||||
res.end(json);
|
||||
}
|
||||
|
||||
function decorateHandlerForJSON(method) {
|
||||
return function(req, res) {
|
||||
// if it is not json just skip it
|
||||
var type = req.headers['content-type'];
|
||||
if (!type || type.indexOf('json') === -1) {
|
||||
return method.call(this, req, res);
|
||||
}
|
||||
|
||||
var buffer = '';
|
||||
req.on('data', function(data) {
|
||||
buffer += data;
|
||||
});
|
||||
|
||||
req.once('end', function() {
|
||||
req.body = JSON.parse(buffer);
|
||||
method.call(this, req, res);
|
||||
}.bind(this));
|
||||
};
|
||||
}
|
||||
|
||||
function defaultSettings() {
|
||||
return {
|
||||
corked: false,
|
||||
fail: false
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} root directory where content is.@
|
||||
* @param {Object} routes for the app server.
|
||||
* @constructor
|
||||
*/
|
||||
function AppServer(root, routes) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
// ensure we always have the slash suffix
|
||||
if (root[root.length - 1] !== '/') {
|
||||
root += '/';
|
||||
}
|
||||
|
||||
this.root = root;
|
||||
this.routes = routes;
|
||||
this._settings = {};
|
||||
}
|
||||
|
||||
AppServer.prototype = {
|
||||
__proto__: EventEmitter.prototype,
|
||||
|
||||
/**
|
||||
* @return {Object} default settings for a given url.
|
||||
*/
|
||||
settings: function(url) {
|
||||
return (this._settings[url] = this._settings[url] || defaultSettings());
|
||||
},
|
||||
|
||||
requestHandler: function() {
|
||||
var routes = this.routes;
|
||||
return function(req, res) {
|
||||
debug('request', req.method, req.url);
|
||||
var handler = routes[req.url] || routes['*'];
|
||||
var settings = this.settings(req.url);
|
||||
|
||||
// If this url has a http error on it just return without calling the
|
||||
// handler.
|
||||
if (settings.response) {
|
||||
res.writeHead(settings.response.status, settings.response.headers);
|
||||
res.end(settings.response.body);
|
||||
return;
|
||||
}
|
||||
|
||||
// routes are always invoked with the context of the app server.
|
||||
return handler.call(this, req, res);
|
||||
}.bind(this);
|
||||
},
|
||||
};
|
||||
|
||||
var routes = {
|
||||
|
||||
/**
|
||||
Change the root serving directory for this server.
|
||||
*/
|
||||
'/settings/set_root': decorateHandlerForJSON(function(req, res) {
|
||||
this.root = req.body;
|
||||
writeJSON(res, req.body);
|
||||
}),
|
||||
|
||||
/**
|
||||
Override or set the response of a particular url.
|
||||
*/
|
||||
'/settings/set_response': decorateHandlerForJSON(function(req, res) {
|
||||
var options = req.body;
|
||||
var settings = this.settings(options.path);
|
||||
|
||||
settings.response = options;
|
||||
writeJSON(res, options.path);
|
||||
}),
|
||||
|
||||
/**
|
||||
Clear the response override.
|
||||
*/
|
||||
'/settings/clear_response': decorateHandlerForJSON(function(req, res) {
|
||||
var url = req.body;
|
||||
var settings = this.settings(url);
|
||||
delete settings.response;
|
||||
writeJSON(res, url);
|
||||
}),
|
||||
|
||||
/**
|
||||
A 'corked' request will send headers but no body until 'uncork' is used.
|
||||
*/
|
||||
'/settings/cork': decorateHandlerForJSON(function(req, res) {
|
||||
var url = req.body;
|
||||
var settings = this.settings(url);
|
||||
settings.corked = true;
|
||||
|
||||
// issue a cork to the particular endpoint
|
||||
var event = 'corked ' + url;
|
||||
debug('cork event', event);
|
||||
this.emit('corked ' + url);
|
||||
writeJSON(res, url);
|
||||
}),
|
||||
|
||||
/**
|
||||
The inverse of 'cork' will uncork a uri and send the body through.
|
||||
*/
|
||||
'/settings/uncork': decorateHandlerForJSON(function(req, res) {
|
||||
var url = req.body;
|
||||
var settings = this.settings(url);
|
||||
settings.corked = false;
|
||||
|
||||
var event = 'uncorked ' + url;
|
||||
debug('uncork event', event);
|
||||
this.emit(event);
|
||||
writeJSON(res, url);
|
||||
}),
|
||||
|
||||
/**
|
||||
Fail is a combination of 'cork' and a flag which will cause the incoming http
|
||||
request to be closed after a short delay.
|
||||
*/
|
||||
'/settings/fail': decorateHandlerForJSON(function(req, res) {
|
||||
var url = req.body;
|
||||
var settings = this.settings(url);
|
||||
|
||||
// must be corked to ensure we don't send the body
|
||||
settings.corked = true;
|
||||
settings.fail = true;
|
||||
writeJSON(res, url);
|
||||
}),
|
||||
|
||||
/**
|
||||
The inverse to fail but unlike uncork this cannot be called in the middle of
|
||||
a request (for obvious reasons).
|
||||
*/
|
||||
'/settings/unfail': decorateHandlerForJSON(function(req, res) {
|
||||
var url = req.body;
|
||||
var settings = this.settings(url);
|
||||
|
||||
settings.fail = settings.corked = false;
|
||||
writeJSON(res, url);
|
||||
}),
|
||||
|
||||
'/package.manifest': function(req, res) {
|
||||
var json = JSON.parse(fs.readFileSync(this.root + '/manifest.webapp'));
|
||||
json.package_path = 'http://' + req.headers.host + '/app.zip';
|
||||
// TODO: Remove this once we add the ability to override requests
|
||||
delete json.role;
|
||||
|
||||
var body = JSON.stringify(json, null, 2);
|
||||
res.writeHead(200, {
|
||||
'Content-Length': Buffer.byteLength(body),
|
||||
'Content-Type': 'application/x-web-app-manifest+json'
|
||||
});
|
||||
res.end(body);
|
||||
},
|
||||
|
||||
'/app.zip': function(req, res) {
|
||||
var url = req.url;
|
||||
var settings = this.settings(url);
|
||||
// always write the head we need it in all cases
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'application/zip'
|
||||
});
|
||||
|
||||
var root = this.root;
|
||||
function writeZip() {
|
||||
// read the entire app directory
|
||||
var files = readdirRecursive(root);
|
||||
var zip = archiver.create('zip');
|
||||
|
||||
files.forEach(function(file) {
|
||||
debug('packaged app zip file', file);
|
||||
var zipPath = file.replace(root, '');
|
||||
zip.append(fs.createReadStream(file), { name: zipPath });
|
||||
});
|
||||
zip.finalize();
|
||||
zip.pipe(res);
|
||||
}
|
||||
|
||||
if (settings.fail) {
|
||||
process.nextTick(function() {
|
||||
req.socket.destroy();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// if we are not corked just write the zip and we are done
|
||||
if (!settings.corked) {
|
||||
return writeZip();
|
||||
} else {
|
||||
this.once('uncorked ' + url, writeZip);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
Catch all handler which serves up static assets.
|
||||
*/
|
||||
'*': function(req, res) {
|
||||
var url = req.url;
|
||||
var settings = this.settings(url);
|
||||
var filePath = fsPath.join(this.root, url);
|
||||
|
||||
// XXX: This is not a production quality server don't use sync methods in
|
||||
// in servers in your user facing node code.
|
||||
if (!fs.existsSync(filePath)) {
|
||||
res.writeHead(404);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
var contentType = mime.lookup(filePath);
|
||||
var contentLength = fs.statSync(filePath).size;
|
||||
debug('static asset', filePath, contentType, contentLength);
|
||||
|
||||
// write out the head
|
||||
res.writeHead(200, {
|
||||
'Content-Type': contentType,
|
||||
'Content-Length': contentLength
|
||||
});
|
||||
|
||||
function writeContent() {
|
||||
fs.createReadStream(filePath).pipe(res);
|
||||
}
|
||||
|
||||
if (settings.fail) {
|
||||
process.nextTick(function() {
|
||||
req.socket.destroy();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!settings.corked) {
|
||||
// if we are not corked just return the stream
|
||||
return writeContent();
|
||||
} else {
|
||||
// otherwise wait for the uncork
|
||||
this.once('uncorked ' + url, writeContent);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var port = parseInt((process.argv[3] || 0), 10);
|
||||
var appServer = new AppServer(process.argv[2], routes);
|
||||
var server = http.createServer(appServer.requestHandler());
|
||||
|
||||
server.listen(port, function() {
|
||||
if (process.send) {
|
||||
process.send({ type: 'started', port: server.address().port });
|
||||
}
|
||||
});
|
||||
74
gaia/apps/homescreen/test/marionette/server/cli.js
Executable file
74
gaia/apps/homescreen/test/marionette/server/cli.js
Executable file
|
|
@ -0,0 +1,74 @@
|
|||
/* global process, __dirname */
|
||||
'use strict';
|
||||
|
||||
// server cli stuff
|
||||
var fork = require('child_process').fork;
|
||||
var fs = require('fs');
|
||||
var fsPath = require('path');
|
||||
|
||||
function exit() {
|
||||
console.error();
|
||||
console.error.apply(console, arguments);
|
||||
commandHelp(console.error);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function commandHelp(log) {
|
||||
log = log || console.log;
|
||||
|
||||
log();
|
||||
log(' App server cli');
|
||||
log();
|
||||
log(' Comamnds: ');
|
||||
log();
|
||||
log(' help - Your looking at it');
|
||||
log(' interactive <directory> [port] - ' +
|
||||
'Begin an interactive session with the app server');
|
||||
log();
|
||||
}
|
||||
|
||||
function commandInteractive(directory, port) {
|
||||
port = parseInt((port || 60234), 10);
|
||||
|
||||
if (!directory) {
|
||||
return exit('Directory must be given');
|
||||
}
|
||||
|
||||
directory = fsPath.resolve(directory);
|
||||
|
||||
if (!fs.existsSync(directory)) {
|
||||
return exit('Given app directory "%s" does not exist', directory);
|
||||
}
|
||||
|
||||
// start the server up
|
||||
console.log('Starting server...');
|
||||
|
||||
function onError(e) {
|
||||
exit('server process failed to start: ', e);
|
||||
}
|
||||
|
||||
var proc = fork(__dirname + '/child.js', [directory, port]);
|
||||
proc.once('error', onError);
|
||||
proc.on('message', function(msg) {
|
||||
if (msg.type !== 'started') {
|
||||
return;
|
||||
}
|
||||
proc.removeListener('error', onError);
|
||||
|
||||
var interactive = require('./cli/interactive');
|
||||
interactive(proc, 'http://localhost:' + msg.port + '/');
|
||||
});
|
||||
}
|
||||
|
||||
switch (process.argv[2]) {
|
||||
case 'interactive':
|
||||
commandInteractive(process.argv[3]);
|
||||
break;
|
||||
|
||||
case 'help':
|
||||
commandHelp();
|
||||
break;
|
||||
|
||||
default:
|
||||
commandHelp();
|
||||
}
|
||||
175
gaia/apps/homescreen/test/marionette/server/cli/interactive.js
Executable file
175
gaia/apps/homescreen/test/marionette/server/cli/interactive.js
Executable file
|
|
@ -0,0 +1,175 @@
|
|||
/* global process, Buffer, module */
|
||||
'use strict';
|
||||
var http = require('http');
|
||||
var url = require('url');
|
||||
|
||||
function Interactive(serverProc, url, commands) {
|
||||
this.server = serverProc;
|
||||
this.url = url;
|
||||
this.stream = process.stdin;
|
||||
this.commands = commands;
|
||||
}
|
||||
|
||||
Interactive.prototype = {
|
||||
|
||||
help: function() {
|
||||
console.log(' Commands: ');
|
||||
console.log();
|
||||
Object.keys(this.commands).forEach(function(command) {
|
||||
var desc = this.commands[command].description;
|
||||
console.log(' %s - %s', command, desc);
|
||||
}, this);
|
||||
console.log();
|
||||
},
|
||||
|
||||
get: function(uri) {
|
||||
var req = http.get(this.url + uri);
|
||||
console.log('>> GET ' + uri);
|
||||
|
||||
req.once('response', function(res) {
|
||||
console.log(' response %s [%s]', uri, res.statusCode);
|
||||
for (var key in res.headers) {
|
||||
console.log(' response header %s : %s', key, res.headers[key]);
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
post: function(uri, body, callback) {
|
||||
var json = JSON.stringify(body);
|
||||
|
||||
var options = url.parse(this.url + uri);
|
||||
options.method = 'POST';
|
||||
var req = http.request(options);
|
||||
req.setHeader('Content-Length', Buffer.byteLength(json));
|
||||
req.setHeader('Content-Type', 'application/json');
|
||||
|
||||
req.once('response', function(res) {
|
||||
var data = '';
|
||||
res.on('data', function(buffer) {
|
||||
data += buffer;
|
||||
});
|
||||
|
||||
res.once('end', function() {
|
||||
callback(null, JSON.parse(data));
|
||||
});
|
||||
});
|
||||
|
||||
req.end(json);
|
||||
},
|
||||
|
||||
onReadable: function() {
|
||||
var stream = this.stream;
|
||||
|
||||
var content = stream.read();
|
||||
// noting to read return...
|
||||
if (!content) {
|
||||
return;
|
||||
}
|
||||
|
||||
// read until we have a newline
|
||||
if (content.indexOf('\n') === -1) {
|
||||
stream.unshift(content);
|
||||
return;
|
||||
}
|
||||
|
||||
// ignore blank lines
|
||||
if (content.trim() === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
// the first word is the command parse it out...
|
||||
var commandParts = content.split(' ').map(function(part) {
|
||||
return part.trim();
|
||||
});
|
||||
|
||||
var commandName = commandParts.shift();
|
||||
var command = this.commands[commandName];
|
||||
|
||||
if (!command) {
|
||||
console.error(commandName, ' is not a recognized command');
|
||||
return this.help();
|
||||
}
|
||||
|
||||
command.handler.apply(this, commandParts);
|
||||
},
|
||||
|
||||
start: function() {
|
||||
// don't stop reading from stdin
|
||||
this.stream.setEncoding('utf8');
|
||||
this.stream.on('readable', this.onReadable.bind(this));
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
var commands = {
|
||||
help: {
|
||||
description: 'Shows all commands',
|
||||
handler: function() { this.help(); }
|
||||
},
|
||||
|
||||
cork: {
|
||||
description: '<uri> Cork a particular url',
|
||||
handler: function(uri) {
|
||||
// leading slash for consistency with get
|
||||
this.post('settings/cork', '/' + uri, function(err, body) {
|
||||
console.log('corked : %s', body);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
uncork: {
|
||||
description: '<uri> uncork a particular uri',
|
||||
handler: function(uri) {
|
||||
// leading slash for consistency with get
|
||||
this.post('settings/uncork', '/' + uri, function(err, body) {
|
||||
console.log('uncorked : %s', body);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
fail: {
|
||||
description: '<uri> Cause a particular url to fail',
|
||||
handler: function(uri) {
|
||||
// leading slash for consistency with get
|
||||
this.post('settings/fail', '/' + uri, function(err, body) {
|
||||
console.log('failed: %s', body);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
unfail: {
|
||||
description: '<uri> remove failng status from particular url',
|
||||
handler: function(uri) {
|
||||
// leading slash for consistency with get
|
||||
this.post('settings/unfail', '/' + uri, function(err, body) {
|
||||
console.log('unfail: %s', body);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
get: {
|
||||
description: '<uri> Issue a http get an show the body',
|
||||
handler: function(uri) {
|
||||
this.get(uri);
|
||||
}
|
||||
},
|
||||
|
||||
url: {
|
||||
description: 'display the server url',
|
||||
handler: function() {
|
||||
console.log(this.url);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
function main(process, url) {
|
||||
var handler = new Interactive(process, url, commands);
|
||||
handler.start();
|
||||
|
||||
process.once('SIGINT', function() {
|
||||
process.stderr.write('Shutting down...\n');
|
||||
process.exit();
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = main;
|
||||
191
gaia/apps/homescreen/test/marionette/server/parent.js
Executable file
191
gaia/apps/homescreen/test/marionette/server/parent.js
Executable file
|
|
@ -0,0 +1,191 @@
|
|||
'use strict';
|
||||
/* global module, __dirname */
|
||||
var fork = require('child_process').fork;
|
||||
|
||||
/**
|
||||
* Issue a POST request via marionette.
|
||||
*/
|
||||
function post(client, url, json) {
|
||||
// must run in chrome so we can do cross domain xhr
|
||||
client = client.scope({ context: 'chrome' });
|
||||
return client.executeAsyncScript(function(url, json) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', url, true);
|
||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||
xhr.onload = function() {
|
||||
marionetteScriptFinished(xhr.response);
|
||||
};
|
||||
xhr.send(json);
|
||||
}, [url, JSON.stringify(json)]);
|
||||
}
|
||||
|
||||
function AppServer(root, marionette, port, proc) {
|
||||
this.root = root;
|
||||
this.marionette = marionette;
|
||||
this.url = 'http://localhost:' + port;
|
||||
this.process = proc;
|
||||
}
|
||||
|
||||
AppServer.prototype = {
|
||||
|
||||
/**
|
||||
issues a GET request to the server and returns the parsed body.
|
||||
*/
|
||||
get: function(uri) {
|
||||
// must run in chrome so we can do cross domain xhr
|
||||
var client = this.marionette.scope({ context: 'chrome' });
|
||||
|
||||
return client.executeAsyncScript(function(url) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', url, true);
|
||||
xhr.onload = function() {
|
||||
marionetteScriptFinished(xhr.response);
|
||||
};
|
||||
xhr.send();
|
||||
}, [this.url + uri]);
|
||||
},
|
||||
|
||||
/**
|
||||
Changes the root of the server.
|
||||
|
||||
@param {String} path relative to the file system.
|
||||
*/
|
||||
setRoot: function(path) {
|
||||
return post(this.marionette, this.url + '/settings/set_root', path);
|
||||
},
|
||||
|
||||
/**
|
||||
Set the http response of a particular url.
|
||||
|
||||
@param {String} path to set the response of.
|
||||
@param {Number} status code to set.
|
||||
@param {Object} headers to override.
|
||||
@param {String} body of the request.
|
||||
*/
|
||||
setResponse: function(path, status, headers, body) {
|
||||
var options = {
|
||||
path: path,
|
||||
status: status,
|
||||
headers: headers || {},
|
||||
body: body
|
||||
};
|
||||
|
||||
return post(this.marionette, this.url + '/settings/set_response', options);
|
||||
},
|
||||
|
||||
/**
|
||||
Clear the response override set by `setResponse`
|
||||
*/
|
||||
clearResponse: function(url) {
|
||||
return post(
|
||||
this.marionette, this.url + '/settings/clear_response', url
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
Trigger a 500 error for a particular url and return an empty body.
|
||||
|
||||
@param {String} url to fail (/index.html).
|
||||
*/
|
||||
serverError: function(url) {
|
||||
return this.setResponse(url, 500, {}, '');
|
||||
},
|
||||
|
||||
/**
|
||||
Clears the server error for a particular url.
|
||||
|
||||
@param {String} url to fail (/index.html).
|
||||
*/
|
||||
clearServerError: function(url) {
|
||||
return this.clearResponse();
|
||||
},
|
||||
|
||||
/**
|
||||
Indicate to the server that all requests to the given url should be
|
||||
given a response with headers but then the socket should be closed shortly
|
||||
after that time.
|
||||
|
||||
@param {String} url to ban (/index.html).
|
||||
*/
|
||||
fail: function(url) {
|
||||
return post(this.marionette, this.url + '/settings/fail', url);
|
||||
},
|
||||
|
||||
/**
|
||||
Allow requests to the given url to proceed without failure after calling
|
||||
`.fail`.
|
||||
|
||||
@param {String} url to unban.
|
||||
*/
|
||||
unfail: function(url) {
|
||||
return post(this.marionette, this.url + '/settings/unfail', url);
|
||||
},
|
||||
|
||||
/**
|
||||
Cork the response body of the given url while allowing headers.
|
||||
|
||||
@param {String} url to cork.
|
||||
*/
|
||||
cork: function(url) {
|
||||
return post(this.marionette, this.url + '/settings/cork', url);
|
||||
},
|
||||
|
||||
/**
|
||||
Allow the body to be sent after calling `.cork`.
|
||||
|
||||
@param {String} url to uncork.
|
||||
*/
|
||||
uncork: function(url) {
|
||||
return post(this.marionette, this.url + '/settings/uncork', url);
|
||||
},
|
||||
|
||||
close: function(callback) {
|
||||
this.process.kill();
|
||||
this.process.once('exit', callback.bind(this, null));
|
||||
},
|
||||
|
||||
get manifest() {
|
||||
return JSON.parse(this.get('/manifest.webapp'));
|
||||
},
|
||||
|
||||
/**
|
||||
URI where the application zip lives this defined in child.js
|
||||
*/
|
||||
get applicationZipUri() {
|
||||
return '/app.zip';
|
||||
},
|
||||
|
||||
get manifestURL() {
|
||||
return this.url + '/manifest.webapp';
|
||||
},
|
||||
|
||||
get packageManifestURL() {
|
||||
return this.url + '/package.manifest';
|
||||
},
|
||||
|
||||
get webappUpdateURL() {
|
||||
return this.url + '/update.webapp';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a app server for use in marionette tests.
|
||||
*
|
||||
* @param {String} appRoot path to the root of the app.
|
||||
* @param {Marionette.Client} client for marionette.
|
||||
* @param {Function} callback [Error]
|
||||
*/
|
||||
module.exports = function create(appRoot, client, callback) {
|
||||
var proc = fork(__dirname + '/child.js', [appRoot]);
|
||||
|
||||
proc.once('error', callback);
|
||||
proc.on('message', function(msg) {
|
||||
if (msg.type !== 'started') {
|
||||
return;
|
||||
}
|
||||
proc.removeListener('error', callback);
|
||||
callback(null, new AppServer(appRoot, client, msg.port, proc));
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.AppServer;
|
||||
60
gaia/apps/homescreen/test/marionette/verticalhome_migration_test.js
Executable file
60
gaia/apps/homescreen/test/marionette/verticalhome_migration_test.js
Executable file
|
|
@ -0,0 +1,60 @@
|
|||
'use strict';
|
||||
/* global __dirname */
|
||||
|
||||
var assert = require('assert');
|
||||
|
||||
marionette('Homescreen - verticalhome migration', function() {
|
||||
var client = marionette.client({
|
||||
profile: require(__dirname + '/client_options_verticalhome.js')
|
||||
});
|
||||
var actions, verticalhome, home, system;
|
||||
|
||||
setup(function() {
|
||||
actions = client.loader.getActions();
|
||||
verticalhome = client.loader.getAppClass('verticalhome');
|
||||
home = client.loader.getAppClass('homescreen');
|
||||
system = client.loader.getAppClass('system');
|
||||
system.waitForFullyLoaded();
|
||||
verticalhome.waitForLaunch();
|
||||
});
|
||||
|
||||
test('App order is imported from verticalhome', function() {
|
||||
// Drag the second icon to the first position so we differ from the
|
||||
// default settings.
|
||||
var icon1 = verticalhome.getNthIcon(1);
|
||||
var icon2 = verticalhome.getNthIcon(2);
|
||||
actions.press(icon2).wait(1).move(icon1).release().wait(1).perform();
|
||||
verticalhome.exitEditMode();
|
||||
|
||||
// Restart the home screen so that icon DOM order corresponds with actual
|
||||
// order.
|
||||
verticalhome.restart();
|
||||
|
||||
// Record the names of all the apps and bookmarks (these are the only
|
||||
// items that are migrated)
|
||||
var iconNames =
|
||||
client.findElements('#icons div.icon:not(.placeholder)').
|
||||
map(function(icon) {
|
||||
return icon.findElement('.title').text();
|
||||
});
|
||||
|
||||
// Request migration to new home screen
|
||||
client.executeScript(function() {
|
||||
window.wrappedJSObject.app.itemStore.migrate(
|
||||
'app://homescreen.gaiamobile.org/manifest.webapp');
|
||||
});
|
||||
client.switchToFrame();
|
||||
home.waitForLaunch();
|
||||
|
||||
// Verify icon order matches
|
||||
var importedIconNames = home.visibleIcons.map(function(icon) {
|
||||
return home.getIconText(icon);
|
||||
});
|
||||
|
||||
var nIcons = iconNames.length;
|
||||
assert.equal(importedIconNames.length, nIcons);
|
||||
for (var i = 0; i < nIcons; i++) {
|
||||
assert.equal(importedIconNames[i], iconNames[i]);
|
||||
}
|
||||
});
|
||||
});
|
||||
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