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:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,19 @@
{
"extends": "../../../../.jshintrc",
"predef": [
"__dirname",
"module",
"suite",
"test",
"setup",
"teardown",
"suiteSetup",
"suiteTeardown",
"marionette",
"marionetteScriptFinished",
"require",
"define",
"requireApp",
"sinon"
]
}

View file

@ -0,0 +1,70 @@
/* global __dirname */
'use strict';
var assert = require('assert');
var AppInstall =
require('../../../../apps/system/test/marionette/lib/app_install');
var createAppServer = require('./server/parent');
var iconAppState = require('./lib/icon_app_state');
marionette('Vertical Home - 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/appcache';
createAppServer(app, client, function(err, _server) {
server = _server;
done(err);
});
});
var subject;
var system;
var appInstall;
setup(function() {
subject = client.loader.getAppClass('verticalhome');
system = client.loader.getAppClass('system');
appInstall = new AppInstall(client);
system.waitForFullyLoaded();
subject.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();
client.switchToFrame(system.getHomescreenIframe());
var icon = subject.getIcon(server.manifestURL);
// wait until we see the nice spinner thing
client.waitFor(function() {
return iconAppState(icon) === 'loading';
});
// 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 iconAppState(icon) !== 'loading';
});
// ensure the app launches!
subject.launchAndSwitchToApp(server.manifestURL);
assert.equal(client.title(), 'iwrotethis');
});
});

View 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');
var iconSrc = require('./lib/icon_src');
marionette('Vertical Home - 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 subject;
var system;
var appInstall;
setup(function() {
subject = client.loader.getAppClass('verticalhome');
system = client.loader.getAppClass('system');
appInstall = new AppInstall(client);
system.waitForFullyLoaded();
subject.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();
client.switchToFrame(system.getHomescreenIframe());
var icon = subject.getIcon(server.manifestURL);
// ensure the default icon is shown
client.waitFor(function() {
var src = iconSrc(icon);
return src && src.indexOf('default') !== -1;
});
// ensure the icon can be launched!
subject.launchAndSwitchToApp(server.manifestURL);
assert.equal(client.title(), 'iwrotethis');
});
});

View file

@ -0,0 +1,67 @@
/* global __dirname */
'use strict';
var assert = require('assert');
var AppInstall =
require('../../../../apps/system/test/marionette/lib/app_install');
var createAppServer = require('./server/parent');
var iconSrc = require('./lib/icon_src');
marionette('Vertical Home - 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 subject;
var system;
var appInstall;
setup(function() {
subject = client.loader.getAppClass('verticalhome');
system = client.loader.getAppClass('system');
appInstall = new AppInstall(client);
system.waitForFullyLoaded();
subject.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();
client.switchToFrame(system.getHomescreenIframe());
var icon = subject.getIcon(server.manifestURL);
// ensure the default icon is shown
client.waitFor(function() {
var src = iconSrc(icon);
return src && src.indexOf('default') !== -1;
});
// ensure the icon can be launched!
subject.launchAndSwitchToApp(server.manifestURL);
assert.equal(client.title(), 'iwrotethis');
});
});

View 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('Vertical Home - 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 subject;
var system;
var appInstall;
setup(function() {
subject = client.loader.getAppClass('verticalhome');
system = client.loader.getAppClass('system');
appInstall = new AppInstall(client);
system.waitForFullyLoaded();
subject.waitForLaunch();
});
teardown(function(done) {
server.close(done);
});
test('install app', function() {
client.switchToFrame();
appInstall.install(server.manifestURL);
subject.launchAndSwitchToApp(server.manifestURL);
assert.equal(client.title(), 'iwrotethis');
});
});

View file

@ -0,0 +1,93 @@
/* global __dirname */
'use strict';
var AppInstall =
require('../../../../apps/system/test/marionette/lib/app_install');
var createAppServer = require('./server/parent');
var iconSrc = require('./lib/icon_src');
var iconCached = require('./lib/icon_cached');
marionette('Vertical Home - Hosted app cached icon fetch', 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 subject;
var system;
var appInstall;
setup(function() {
subject = client.loader.getAppClass('verticalhome');
system = client.loader.getAppClass('system');
appInstall = new AppInstall(client);
system.waitForFullyLoaded();
subject.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();
client.switchToFrame(system.getHomescreenIframe());
var icon = subject.getIcon(server.manifestURL);
// ensure it is cached
client.waitFor(function() {
var src = iconSrc(icon);
if (!src) {
return false;
}
var hasSource = src.indexOf(iconURL) !== -1;
var isCached = iconCached(icon);
return hasSource && isCached;
});
// ensure http falls so we use the cached icon
server.fail(iconURL);
// kill the window
subject.restart();
icon = subject.getIcon(server.manifestURL);
// check for the cached icon...
client.waitFor(function() {
return iconSrc(icon) === 'blobcache';
});
// allow the request to succeed
server.unfail(iconURL);
// trigger the download of a new icon
client.executeScript(function() {
window.dispatchEvent(new CustomEvent('online'));
});
// now shows the freshly redownloaded icon
client.waitFor(function() {
var src = iconSrc(icon);
return src && src.indexOf(iconURL) !== -1;
});
});
});

View file

@ -0,0 +1,73 @@
/* global __dirname */
'use strict';
var assert = require('assert');
var AppInstall =
require('../../../../apps/system/test/marionette/lib/app_install');
var createAppServer = require('./server/parent');
var iconAppState = require('./lib/icon_app_state');
var launchIcon = require('./lib/launch_icon');
marionette('Vertical Home - 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 subject;
var system;
var appInstall;
setup(function() {
subject = client.loader.getAppClass('verticalhome');
system = client.loader.getAppClass('system');
appInstall = new AppInstall(client);
system.waitForFullyLoaded();
subject.waitForLaunch();
});
teardown(function(done) {
server.close(done);
});
function expectAppState(icon, state) {
client.waitFor(function() {
var currentState = iconAppState(icon);
return currentState === state;
});
}
test('failed state then retry and launch', function() {
client.switchToFrame();
server.fail(server.applicationZipUri);
appInstall.installPackage(server.packageManifestURL);
client.switchToFrame(system.getHomescreenIframe());
var icon = subject.getIcon(server.packageManifestURL);
expectAppState(icon, 'error');
server.unfail(server.applicationZipUri);
// helps marionette finding the icon: Bug 1046706
subject.moveIconToIndex(icon, 0);
launchIcon(icon);
subject.confirmDialog('resume');
expectAppState(icon, 'ready');
subject.launchAndSwitchToApp(server.packageManifestURL);
assert.equal(client.title(), 'iwrotethis');
});
});

View file

@ -0,0 +1,54 @@
/* global __dirname */
'use strict';
var assert = require('assert');
var AppInstall =
require('../../../../apps/system/test/marionette/lib/app_install');
var iconAppState = require('./lib/icon_app_state');
var createAppServer = require('./server/parent');
marionette('Vertical Home - 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 subject;
var system;
var appInstall;
setup(function() {
subject = client.loader.getAppClass('verticalhome');
system = client.loader.getAppClass('system');
appInstall = new AppInstall(client);
system.waitForFullyLoaded();
subject.waitForLaunch();
});
teardown(function(done) {
server.close(done);
});
test('install app', function() {
client.switchToFrame();
appInstall.installPackage(server.packageManifestURL);
client.switchToFrame(system.getHomescreenIframe());
var appIcon = subject.getIcon(server.packageManifestURL);
// ensure the app is ready
client.waitFor(function() {
return iconAppState(appIcon) === 'ready';
});
subject.launchAndSwitchToApp(server.packageManifestURL);
assert.equal(client.title(), 'iwrotethis');
});
});

View file

@ -0,0 +1,67 @@
/* global __dirname */
'use strict';
var AppInstall =
require('../../../../apps/system/test/marionette/lib/app_install');
var AppInstall =
require('../../../../apps/system/test/marionette/lib/app_install');
var createAppServer = require('./server/parent');
var iconAppState = require('./lib/icon_app_state');
marionette('Vertical Home - 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 subject;
var system;
var appInstall;
setup(function() {
subject = client.loader.getAppClass('verticalhome');
system = client.loader.getAppClass('system');
appInstall = new AppInstall(client);
system.waitForFullyLoaded();
subject.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 appIcon = subject.getIcon(server.packageManifestURL);
// wait until the icon is spinning!
client.waitFor(function() {
return iconAppState(appIcon) === 'loading';
});
// let the rest of the app come through
server.uncork(server.applicationZipUri);
// wait until it is no longer loading
client.waitFor(function() {
return iconAppState(appIcon) !== 'loading';
});
});
});

View file

@ -0,0 +1,90 @@
/* global __dirname */
'use strict';
var assert = require('assert');
var AppInstall =
require('../../../../apps/system/test/marionette/lib/app_install');
var createAppServer = require('./server/parent');
var iconAppState = require('./lib/icon_app_state');
var launchIcon = require('./lib/launch_icon');
marionette('Vertical Home - Packaged App Resuming Downloads', function() {
var client = marionette.client({
profile: require(__dirname + '/client_options.js'),
desiredCapabilities: { raisesAccessibilityExceptions: false }
});
var server;
var subject;
var system;
var appInstall;
setup(function(done) {
var app = __dirname + '/fixtures/template_app';
createAppServer(app, client, function(err, _server) {
server = _server;
subject = client.loader.getAppClass('verticalhome');
system = client.loader.getAppClass('system');
appInstall = new AppInstall(client);
system.waitForFullyLoaded();
subject.waitForLaunch();
done(err);
});
});
teardown(function(done) {
server.close(done);
});
function expectAppState(icon, state) {
client.waitFor(function() {
var currentState = iconAppState(icon);
return currentState === state;
});
}
test('failed state then retry and launch', function() {
client.switchToFrame();
server.cork(server.applicationZipUri);
appInstall.installPackage(server.packageManifestURL);
client.switchToFrame(system.getHomescreenIframe());
// pause the download
var icon = subject.getIcon(server.packageManifestURL);
// this helps marionette finding the icon: Bug 1046706
subject.moveIconToIndex(icon, 0);
launchIcon(icon);
subject.confirmDialog('pause');
expectAppState(icon, 'paused');
// resume the download
launchIcon(icon);
subject.confirmDialog('resume');
expectAppState(icon, 'loading');
// pause it again!
launchIcon(icon);
subject.confirmDialog('pause');
expectAppState(icon, 'paused');
// uncork so next resume works...
server.uncork(server.applicationZipUri);
// finally download the entire app
launchIcon(icon);
subject.confirmDialog('resume');
expectAppState(icon, 'ready');
// now it should work!
subject.launchAndSwitchToApp(server.packageManifestURL);
assert.equal(client.title(), 'iwrotethis');
});
});

View file

@ -0,0 +1,106 @@
/* global __dirname */
'use strict';
var assert = require('assert');
var AppInstall =
require('../../../../apps/system/test/marionette/lib/app_install');
var AppInstall =
require('../../../../apps/system/test/marionette/lib/app_install');
var createAppServer = require('./server/parent');
var iconAppState = require('./lib/icon_app_state');
var launchIcon = require('./lib/launch_icon');
marionette('Vertical Home - Packaged App Update', 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 subject;
var system;
var appInstall;
setup(function() {
subject = client.loader.getAppClass('verticalhome');
system = client.loader.getAppClass('system');
appInstall = new AppInstall(client);
// wait for the system app to be running
system.waitForFullyLoaded();
subject.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 expectAppState(icon, state) {
client.waitFor(function() {
var currentState = iconAppState(icon);
return currentState === state;
});
}
test('resume update', function() {
var appIcon = subject.getIcon(server.packageManifestURL);
// helps marionette finding the icon: Bug 1046706
subject.moveIconToIndex(appIcon, 0);
// ensure the app is installed before updating it
client.waitFor(function() {
return iconAppState(appIcon) === 'ready';
});
// 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 iconAppState(appIcon) === 'loading';
});
// pause the update
launchIcon(appIcon);
subject.confirmDialog('pause');
expectAppState(appIcon, 'paused');
// resume the update
launchIcon(appIcon);
subject.confirmDialog('resume');
expectAppState(appIcon, 'loading');
// Wait for the download to be complete.
server.uncork('/app.zip');
client.waitFor(function() {
return iconAppState(appIcon) === 'ready';
});
// verify the app was really updated
subject.launchAndSwitchToApp(server.packageManifestURL);
assert.equal(client.title(), 'updatedwow');
});
});

View file

@ -0,0 +1,65 @@
/* global __dirname */
'use strict';
var AppInstall =
require('../../../../apps/system/test/marionette/lib/app_install');
var AppInstall =
require('../../../../apps/system/test/marionette/lib/app_install');
var createAppServer = require('./server/parent');
var iconAppState = require('./lib/icon_app_state');
marionette('Vertical Home - 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 subject;
var system;
var appInstall;
setup(function() {
subject = client.loader.getAppClass('verticalhome');
system = client.loader.getAppClass('system');
appInstall = new AppInstall(client);
system.waitForFullyLoaded();
subject.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 appIcon = subject.getIcon(server.packageManifestURL);
// wait until the icon is spinning!
client.waitFor(function() {
return iconAppState(appIcon) === 'loading';
});
// let the rest of the app come through
server.uncork(server.applicationZipUri);
// wait until the icon is hidden
client.helper.waitForElementToDisappear(appIcon);
});
});

View file

@ -0,0 +1,102 @@
/* global __dirname */
'use strict';
var assert = require('assert');
var AppInstall =
require('../../../../apps/system/test/marionette/lib/app_install');
var AppInstall =
require('../../../../apps/system/test/marionette/lib/app_install');
var createAppServer = require('./server/parent');
var iconAppState = require('./lib/icon_app_state');
var iconSrc = require('./lib/icon_src');
marionette('Vertical Home - 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 subject;
var system;
var appInstall;
setup(function() {
subject = client.loader.getAppClass('verticalhome');
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.
subject.waitForLaunch();
client.switchToFrame();
// install the app
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 appIcon = subject.getIcon(server.packageManifestURL);
// ensure the app is installed before updating it
client.waitFor(function() {
return iconAppState(appIcon) === 'ready';
});
var iconTitle = appIcon.findElement('.title').text();
// 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 iconAppState(appIcon) === 'loading';
});
// Wait for the download to be complete.
server.uncork('/app.zip');
client.waitFor(function() {
return iconAppState(appIcon) === 'ready';
});
// See bug 826555 for rationale for the title not changing.
assert.equal(
appIcon.findElement('.title').text(),
iconTitle,
'app name should not be updated'
);
// Ensure the icon is updated
client.waitFor(function() {
var src = iconSrc(appIcon);
return src.indexOf(server.manifest.icons[128]) !== -1;
});
// verify the app was really updated
subject.launchAndSwitchToApp(server.packageManifestURL);
assert.equal(client.title(), 'updatedwow');
});
});

View file

@ -0,0 +1,71 @@
/* global __dirname */
'use strict';
var assert = require('assert');
var AppInstall =
require('../../../../apps/system/test/marionette/lib/app_install');
var AppInstall =
require('../../../../apps/system/test/marionette/lib/app_install');
var createAppServer = require('./server/parent');
var iconAppState = require('./lib/icon_app_state');
marionette('Vertical Home - 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 subject;
var system;
var appInstall;
setup(function() {
subject = client.loader.getAppClass('verticalhome');
system = client.loader.getAppClass('system');
appInstall = new AppInstall(client);
// wait for the system app to be running
system.waitForFullyLoaded();
client.apps.launch(subject.URL);
subject.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 appIcon = subject.getIcon(server.packageManifestURL);
// ensure the app is installed before updating it
client.waitFor(function() {
return iconAppState(appIcon) === 'ready';
});
// 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
subject.launchAndSwitchToApp(server.packageManifestURL);
assert.equal(client.title(), 'iwrotethis');
});
});

View file

@ -0,0 +1,128 @@
/* global __dirname */
'use strict';
var assert = require('assert');
var AppInstall =
require('../../../../apps/system/test/marionette/lib/app_install');
var createAppServer = require('./server/parent');
var iconSrc = require('./lib/icon_src');
var iconAppState = require('./lib/icon_app_state');
var launchIcon = require('./lib/launch_icon');
marionette('Vertical Home - Hosted app failed icon fetch', 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 subject;
var system;
var appInstall;
setup(function() {
subject = client.loader.getAppClass('verticalhome');
system = client.loader.getAppClass('system');
appInstall = new AppInstall(client);
system.waitForFullyLoaded();
subject.waitForLaunch();
});
teardown(function(done) {
server.close(done);
});
function expectAppState(icon, state) {
client.waitFor(function() {
var currentState = iconAppState(icon);
return currentState === state;
});
}
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 appIcon = subject.getIcon(server.packageManifestURL);
expectAppState(appIcon, 'loading');
// this helps marionette finding the icon: Bug 1046706
subject.moveIconToIndex(appIcon, 0);
// stop the download
launchIcon(appIcon);
subject.confirmDialog('pause');
expectAppState(appIcon, 'paused');
// Restart the download
server.uncork(server.applicationZipUri);
// resume the download from the ui
launchIcon(appIcon);
subject.confirmDialog('resume');
// wait until we are showing our desired icon
var iconURL = server.manifest.icons['128'];
client.waitFor(function() {
appIcon = subject.getIcon(server.packageManifestURL);
var src = iconSrc(appIcon);
return src && src.indexOf(iconURL) !== -1;
});
});
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();
client.switchToFrame(system.getHomescreenIframe());
var icon = subject.getIcon(server.manifestURL);
// ensure the default icon is shown
client.waitFor(function() {
var src = iconSrc(icon);
return src && src.indexOf('default') !== -1;
});
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
client.waitFor(function() {
var src = iconSrc(icon);
return src && src.indexOf(iconURL) !== -1;
});
// ensure the icon can be launched!
subject.launchAndSwitchToApp(server.manifestURL);
assert.equal(client.title(), 'iwrotethis');
});
});

View file

@ -0,0 +1,33 @@
/* global __dirname */
'use strict';
var assert = require('assert');
marionette('Vertical Home - 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('verticalhome');
system = client.loader.getAppClass('system');
system.waitForFullyLoaded();
client.apps.launch(home.URL);
home.waitForLaunch();
});
test('short_name', function() {
var title = home.getIcon(manifestUrl).findElement('.title');
assert.equal(title.text(), 'Short name');
});
});

View file

@ -0,0 +1,89 @@
/* global __dirname */
'use strict';
var assert = require('assert');
var AppInstall =
require('../../../../apps/system/test/marionette/lib/app_install');
var createAppServer = require('./server/parent');
var getIconId = require('./lib/icon_id');
marionette('Vertical - App uninstall while pending', function() {
var client = marionette.client({
profile: require(__dirname + '/client_options.js'),
desiredCapabilities: { raisesAccessibilityExceptions: false }
});
var actions, home, system, appInstall;
var selectors;
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('verticalhome');
system = client.loader.getAppClass('system');
selectors = home.Selectors;
// 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());
// this helps marionette finding the icon: Bug 1046706
var icon = home.getIcon(server.packageManifestURL);
home.moveIconToIndex(icon, 0);
home.enterEditMode();
});
test('use edit mode to uninstall the app', function() {
var icon = home.getIcon(server.packageManifestURL);
var iconId = getIconId(icon);
var remove = icon.findElement('.remove');
// 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
remove.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);
home.restart();
var allIconIds = home.getIconIdentifiers();
assert.ok(allIconIds.indexOf(iconId) === -1, 'app was removed');
});
});

View file

@ -0,0 +1,75 @@
/* global __dirname */
'use strict';
var assert = require('assert');
var AppInstall =
require('../../../../apps/system/test/marionette/lib/app_install');
var createAppServer = require('./server/parent');
var getIconId = require('./lib/icon_id');
marionette('Vertical - App Uninstall', function() {
var client = marionette.client({
profile: require(__dirname + '/client_options.js'),
desiredCapabilities: { raisesAccessibilityExceptions: false }
});
var actions, home, system, appInstall;
var selectors;
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('verticalhome');
system = client.loader.getAppClass('system');
system.waitForFullyLoaded();
selectors = home.Selectors;
client.apps.launch(home.URL);
home.waitForLaunch();
// install an app
client.switchToFrame();
appInstall.install(server.manifestURL);
client.switchToFrame(system.getHomescreenIframe());
home.enterEditMode();
});
test('use edit mode to uninstall the app', function() {
var icon = home.getIcon(server.manifestURL);
var iconId = getIconId(icon);
// helps marionette finding the icon: Bug 1046706
home.moveIconToIndex(icon, 0);
var remove = icon.findElement('.remove');
// remove the icon
remove.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);
home.restart();
var allIconIds = home.getIconIdentifiers();
assert.ok(allIconIds.indexOf(iconId) === -1, 'app was removed');
});
});

View file

@ -0,0 +1,81 @@
/* global __dirname */
'use strict';
var assert = require('assert');
var AppInstall =
require('../../../../apps/system/test/marionette/lib/app_install');
var createAppServer = require('./server/parent');
var iconAppState = require('./lib/icon_app_state');
var launchIcon = require('./lib/launch_icon');
var getIconId = require('./lib/icon_id');
marionette('Vertical Home - 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 subject;
var system;
var appInstall;
setup(function() {
subject = client.loader.getAppClass('verticalhome');
system = client.loader.getAppClass('system');
appInstall = new AppInstall(client);
system.waitForFullyLoaded();
subject.waitForLaunch();
});
teardown(function(done) {
server.close(done);
});
function expectAppState(icon, state) {
client.waitFor(function() {
var currentState = iconAppState(icon);
return currentState === state;
});
}
test('remove an app that was in an unrecoverable state', function() {
client.switchToFrame();
server.serverError(server.applicationZipUri);
appInstall.installPackage(server.packageManifestURL);
client.switchToFrame(system.getHomescreenIframe());
// if the server returns a 500 error the app is assumed to be in an
// unrecoverable state.
var icon = subject.getIcon(server.packageManifestURL);
var iconId = getIconId(icon);
expectAppState(icon, 'unrecoverable');
// helps marionette finding the icon: Bug 1046706
subject.moveIconToIndex(icon, 0);
// agree to uninstall the app
launchIcon(icon);
client.switchToFrame();
subject.confirmDialog('unrecoverable');
// ensure the icon disappears
client.switchToFrame(system.getHomescreenIframe());
client.helper.waitForElementToDisappear(icon);
// verify that the app was uninstalled
subject.restart();
var allIconIds = subject.getIconIdentifiers();
assert.ok(allIconIds.indexOf(iconId) === -1, 'app was removed');
});
});

View file

@ -0,0 +1,59 @@
'use strict';
/* global __dirname */
var Bookmark = require('../../../../apps/system/test/marionette/lib/bookmark');
var Server = require('../../../../shared/test/integration/server');
marionette('Vertical - Bookmark EDIT', function() {
var options = require(__dirname + '/client_options.js');
options.settings['dev.gaia.pinning_the_web'] = false;
var client = marionette.client({
profile: options,
desiredCapabilities: { raisesAccessibilityExceptions: false }
});
var bookmark, home, server, system;
suiteSetup(function(done) {
Server.create(__dirname + '/fixtures/', function(err, _server) {
server = _server;
done();
});
});
suiteTeardown(function() {
server.stop();
});
var url;
setup(function() {
home = client.loader.getAppClass('verticalhome');
system = client.loader.getAppClass('system');
bookmark = new Bookmark(client, server);
system.waitForFullyLoaded();
client.apps.launch(home.URL);
url = server.url('sample.html');
bookmark.openAndSave(url);
system.goHome();
client.switchToFrame(system.getHomescreenIframe());
home.enterEditMode();
});
test('pressing enter after editing the bookmark', function() {
// select the icon in edit mode and click remove
var icon = home.getIcon(url);
home.moveIconToIndex(icon, 0);
icon.tap();
client.switchToFrame();
bookmark.renameAndPressEnter('renamed');
client.switchToFrame(system.getHomescreenIframe());
client.waitFor(function() {
var icon = home.getIcon(url);
return icon.text() === 'renamed';
});
});
});

View file

@ -0,0 +1,59 @@
'use strict';
/* global __dirname */
var iconSrc = require('./lib/icon_src');
var Bookmark = require('../../../../apps/system/test/marionette/lib/bookmark');
var Server = require('../../../../shared/test/integration/server');
marionette('Vertical - Bookmark Favicon Failure', function() {
var options = require(__dirname + '/client_options.js');
options.settings['dev.gaia.pinning_the_web'] = false;
var client = marionette.client({
profile: options,
desiredCapabilities: { raisesAccessibilityExceptions: false }
});
var bookmark, home, server, system;
suiteSetup(function(done) {
Server.create(__dirname + '/fixtures/invalid_favicon/',
function(err, _server) {
server = _server;
done();
});
});
suiteTeardown(function() {
server.stop();
});
setup(function() {
home = client.loader.getAppClass('verticalhome');
system = client.loader.getAppClass('system');
bookmark = new Bookmark(client, server);
system.waitForFullyLoaded();
client.apps.launch(home.URL);
});
test('Invalid icons get default icon assigned', function() {
system.tapHome();
client.switchToFrame(system.getHomescreenIframe());
var url = server.url('sample.html');
client.switchToFrame();
bookmark.openAndSave(url);
system.tapHome();
client.switchToFrame(system.getHomescreenIframe());
var icon = home.getIcon(url);
// ensure the default icon is shown
client.waitFor(function() {
var src = iconSrc(icon);
return src && src.indexOf('default') !== -1;
});
});
});

View file

@ -0,0 +1,58 @@
'use strict';
/* global __dirname */
var assert = require('assert');
var Bookmark = require('../../../../apps/system/test/marionette/lib/bookmark');
var Server = require('../../../../shared/test/integration/server');
marionette('Vertical - Bookmark', function() {
var options = require(__dirname + '/client_options.js');
options.settings['dev.gaia.pinning_the_web'] = false;
var client = marionette.client({
profile: options,
desiredCapabilities: { raisesAccessibilityExceptions: false }
});
var bookmark, 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('verticalhome');
system = client.loader.getAppClass('system');
bookmark = new Bookmark(client, server);
system.waitForFullyLoaded();
client.apps.launch(home.URL);
});
test('Bookmarking appends to last group', function() {
system.tapHome();
client.switchToFrame(system.getHomescreenIframe());
var numIcons = home.numIcons;
var numDividers = home.numDividers;
var url = server.url('sample.html');
client.switchToFrame();
bookmark.openAndSave(url);
system.tapHome();
client.switchToFrame(system.getHomescreenIframe());
client.waitFor(function() {
return numIcons + 1 === home.numIcons;
});
assert.equal(numDividers, home.numDividers);
});
});

View file

@ -0,0 +1,64 @@
'use strict';
/* global __dirname */
var assert = require('assert');
var Bookmark = require('../../../../apps/system/test/marionette/lib/bookmark');
var Server = require('../../../../shared/test/integration/server');
marionette('Vertical - Bookmark Uninstall', function() {
var options = require(__dirname + '/client_options.js');
options.settings['dev.gaia.pinning_the_web'] = false;
var client = marionette.client({
profile: options,
desiredCapabilities: { raisesAccessibilityExceptions: false }
});
var bookmark, home, server, system;
suiteSetup(function(done) {
Server.create(__dirname + '/fixtures/', function(err, _server) {
server = _server;
done();
});
});
suiteTeardown(function() {
server.stop();
});
var url;
setup(function() {
home = client.loader.getAppClass('verticalhome');
system = client.loader.getAppClass('system');
bookmark = new Bookmark(client, server);
system.waitForFullyLoaded();
client.apps.launch(home.URL);
url = server.url('sample.html');
bookmark.openAndSave(url);
system.goHome();
client.switchToFrame(system.getHomescreenIframe());
home.enterEditMode();
});
test('removal of bookmark', function() {
// select the icon in edit mode and click remove
var icon = home.getIcon(url);
home.moveIconToIndex(icon, 0);
var remove = icon.findElement('.remove');
remove.tap();
home.confirmDialog('remove');
// ensure the icon disappears
client.helper.waitForElementToDisappear(icon);
home.restart();
// ensure bookmark is gone upon restart
var allIconIds = home.getIconIdentifiers();
assert.ok(allIconIds.indexOf(url) === -1, 'bookmark was removed');
});
});

View file

@ -0,0 +1,8 @@
module.exports = {
settings: {
'devtools.pseudolocalization.enabled': false,
'language.current': 'en-US',
'homescreen.manifestURL':
'app://verticalhome.gaiamobile.org/manifest.webapp'
}
};

View file

@ -0,0 +1,31 @@
'use strict';
marionette('Vertical - Edit Mode', function() {
var client = marionette.client({
profile: require(__dirname + '/client_options.js')
});
var actions, home, system;
var selectors;
setup(function() {
actions = client.loader.getActions();
home = client.loader.getAppClass('verticalhome');
system = client.loader.getAppClass('system');
system.waitForFullyLoaded();
selectors = home.Selectors;
client.apps.launch(home.URL);
home.waitForLaunch();
});
test('Exit edit mode using done button', function() {
home.enterEditMode();
var header = client.findElement(selectors.editHeaderText);
var done = client.helper.waitForElement(selectors.editHeaderDone);
done.click();
client.helper.waitForElementToDisappear(header);
});
});

View file

@ -0,0 +1,4 @@
CACHE MANIFEST
# v1 2014-06-10
index.html
style/icons/Blank.png

View 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>

View 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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1 @@
This is an invalid favicon for testing purposes.

View 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>

View 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>

View 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"
}
},
"role": "homescreen",
"default_locale": "en-US",
"icons": {
"128": "/style/icons/Blank.png"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View 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>

View 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>

View 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

View 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>

View 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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -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>

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,74 @@
'use strict';
var assert = require('assert');
marionette('Vertical - grid', 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('verticalhome');
system = client.loader.getAppClass('system');
system.waitForFullyLoaded();
home.waitForLaunch();
});
test('apps grid is LTR in LTR language', function() {
var app1Location = home.getNthIcon(1).location();
var app2Location = home.getNthIcon(2).location();
var app3Location = home.getNthIcon(3).location();
var gridWidth = client.helper.waitForElement(home.Selectors.grid).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() {
var communicationManifestUrl =
'app://communications.gaiamobile.org/manifest.webapp';
client.settings.set('language.current', 'ar-x-psbidi');
// Localization can be async, wait for the content to update
client.waitFor(function() {
var phoneIcon = home.getIcon(communicationManifestUrl, 'dialer');
return phoneIcon.text() ===
home.localizedAppName('communications', 'dialer', 'ar-x-psbidi');
});
var app1Location = home.getNthIcon(1).location();
var app2Location = home.getNthIcon(2).location();
var app3Location = home.getNthIcon(3).location();
var gridWidth = client.helper.waitForElement(home.Selectors.grid).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');
});
});

View file

@ -0,0 +1,42 @@
'use strict';
var assert = require('assert');
marionette('Vertical - Group', 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('verticalhome');
system = client.loader.getAppClass('system');
system.waitForFullyLoaded();
client.apps.launch(home.URL);
home.waitForLaunch();
});
test('check that group backgrounds alternate', function() {
var groupSelector = '#icons .group';
var oddGroupSelector = '#icons .group.odd';
// Now get groups and make sure they alternate
var groups = client.findElements(groupSelector);
var oddGroups = client.findElements(oddGroupSelector);
assert.ok(groups.length >= 3);
assert.equal(oddGroups.length, Math.floor(groups.length / 2));
for (var i = 0, iLen = groups.length; i < iLen; i++) {
var isOdd = groups[i].getAttribute('className').indexOf('odd') !== -1;
if ((i % 2) === 1) {
assert.ok(isOdd);
} else {
assert.ok(!isOdd);
}
}
});
});

View file

@ -0,0 +1,53 @@
'use strict';
marionette('Vertical - Group', 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('verticalhome');
system = client.loader.getAppClass('system');
system.waitForFullyLoaded();
client.apps.launch(home.URL);
home.waitForLaunch();
});
test('check dragging icon into collapsed group', function() {
function scrollIntoView(el) {
el.scrollIntoView(false);
}
// Store a reference to the first icon
var firstIcon = client.helper.waitForElement(home.Selectors.firstIcon);
// Enter edit mode
home.enterEditMode();
var header = client.helper.waitForElement(home.Selectors.editHeaderText);
// Drag icon to its own group
actions.press(firstIcon).wait(1).move(header).release().wait(1).perform();
// Leave edit mode
home.exitEditMode();
// Collapse new group
var toggle = client.findElements(home.Selectors.groupToggle).pop();
toggle.scriptWith(scrollIntoView);
actions.tap(toggle).wait(1).perform();
// Long-press the home-screen in the middle of the collapsed group
var groupBg = client.findElements(home.Selectors.groupBackground).pop();
var grid = client.findElement(home.Selectors.grid);
var x = groupBg.location().x + groupBg.size().width/2;
var y = groupBg.location().y + groupBg.size().height/2;
actions.press(grid, x, y).wait(1).perform();
// Wait for the context menu to appear
client.helper.waitForElement(home.contextMenu);
});
});

View file

@ -0,0 +1,58 @@
'use strict';
var assert = require('assert');
marionette('Vertical - Group', function() {
var client = marionette.client({
profile: require(__dirname + '/client_options.js'),
desiredCapabilities: { raisesAccessibilityExceptions: false }
});
var actions, home, system;
setup(function() {
actions = client.loader.getActions();
home = client.loader.getAppClass('verticalhome');
system = client.loader.getAppClass('system');
system.waitForFullyLoaded();
client.apps.launch(home.URL);
home.waitForLaunch();
});
test('check dragging icon into collapsed group', function() {
function scrollIntoView(el) {
el.scrollIntoView(false);
}
// Store a reference to the first icon
var firstIcon = client.helper.waitForElement(home.Selectors.firstIcon);
var secondIcon = client.findElements(home.Selectors.firstIcon)[1];
var collapse = client.findElement('.group .toggle');
// Enter edit mode
home.enterEditMode();
var header = client.helper.waitForElement(home.Selectors.editHeaderText);
// Drag icon to its own group
actions.press(firstIcon).wait(1).move(header).release().wait(1).perform();
// Collapse the original group
actions.tap(collapse).wait(1).perform();
// Count the number of collapsed icons
var collapsedIconsSelector = '#icons div.icon.collapsed';
var collapsedIconsBefore =
client.findElements(collapsedIconsSelector).length;
// Drag icon into collapsed group
firstIcon.scriptWith(scrollIntoView);
actions.press(firstIcon).wait(1).move(secondIcon).release().
wait(1).perform();
// Make sure there's one more collapsed icon
var collapsedIconsAfter =
client.findElements(collapsedIconsSelector).length;
assert.equal(collapsedIconsAfter, collapsedIconsBefore + 1);
});
});

View file

@ -0,0 +1,35 @@
'use strict';
marionette('Vertical - Group', 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('verticalhome');
system = client.loader.getAppClass('system');
system.waitForFullyLoaded();
client.apps.launch(home.URL);
home.waitForLaunch();
});
test('collapse the first group', function() {
// Get a reference for the first icon
var icon = client.helper.waitForElement(home.Selectors.firstIcon);
// Retrieve the icon's manifest URL
var manifestURL = icon.getAttribute('data-identifier');
manifestURL = manifestURL.slice(0, manifestURL.lastIndexOf('-'));
// Collapse first group
var collapse = client.findElement(home.Selectors.groupToggle);
actions.tap(collapse).wait(1).perform();
// Try to launch the app
home.launchAndSwitchToApp(manifestURL);
});
});

View file

@ -0,0 +1,47 @@
'use strict';
var assert = require('assert');
marionette('Vertical - Group', 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('verticalhome');
system = client.loader.getAppClass('system');
system.waitForFullyLoaded();
client.apps.launch(home.URL);
home.waitForLaunch();
});
test('check collapsed icon size vs. column width', function() {
function getRect(el) {
return el.getBoundingClientRect();
}
// Collapse the first group
var collapse = client.findElement('.group .toggle');
actions.tap(collapse).wait(1).perform();
// Store the collapsed icon size
var icon = client.helper.waitForElement(home.Selectors.firstIcon);
var rect3col = icon.scriptWith(getRect);
// Change the column width
client.executeScript(function() {
window.wrappedJSObject.verticalPreferences.put('grid.cols', 4);
});
actions.wait(1).perform();
var rect4col = icon.scriptWith(getRect);
// Assert that the icons are the same size
assert.equal(rect3col.width, rect4col.width);
assert.equal(rect3col.height, rect4col.height);
});
});

View file

@ -0,0 +1,55 @@
'use strict';
var assert = require('assert');
marionette('Vertical - Group', 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('verticalhome');
system = client.loader.getAppClass('system');
system.waitForFullyLoaded();
client.apps.launch(home.URL);
home.waitForLaunch();
});
test('collapse the first group', function() {
function getRect(el) {
return el.getBoundingClientRect();
}
function scrollIntoView(el) {
el.scrollIntoView(false);
}
// Store the metrics of the first icon before collapsing
var icon = client.helper.waitForElement(home.Selectors.firstIcon);
var rectBefore = icon.scriptWith(getRect);
// Enter edit mode
home.enterEditMode();
var header = client.helper.waitForElement(home.Selectors.editHeaderText);
// Drag icon to its own group before collapsing (to test alignment)
actions.press(icon).wait(1).move(header).release().perform();
client.helper.waitForElement(home.Selectors.editHeaderDone).click();
// Collapse group
var collapse = client.findElements(home.Selectors.groupToggle).pop();
collapse.scriptWith(scrollIntoView);
actions.wait(1).tap(collapse).wait(1).perform();
// Retrieve the metrics of the first icon after collapsing
var rectAfter = icon.scriptWith(getRect);
// Make sure that the icon got smaller and that it's left-aligned
assert.ok(rectAfter.width < rectBefore.width);
assert.ok(rectAfter.x < rectAfter.width);
});
});

View file

@ -0,0 +1,43 @@
'use strict';
var Actions = require('marionette-client').Actions;
var assert = require('assert');
marionette('Vertical - Group', function() {
var client = marionette.client({
profile: require(__dirname + '/client_options.js')
});
var actions, home, system;
setup(function() {
actions = new Actions(client);
home = client.loader.getAppClass('verticalhome');
system = client.loader.getAppClass('system');
system.waitForFullyLoaded();
client.apps.launch(home.URL);
home.waitForLaunch();
});
test('check that dragging an expanded group collapses correctly', function() {
function getRect(el) {
return el.getBoundingClientRect();
}
// Store the metrics of the first group
var background = client.findElement(home.Selectors.groupBackground);
var bgRect = background.scriptWith(getRect);
// Initiate a drag by long-pressing at the top-middle of the background
// element, then wait for it to collapse
actions.press(background, bgRect.width/2, 0).wait(3).perform();
var bgRectAfter = background.scriptWith(getRect);
// Check that the background is smaller, but still in the same position
assert.equal(bgRect.x, bgRectAfter.x);
assert.equal(bgRect.y, bgRectAfter.y);
assert.ok(bgRectAfter.height < bgRect.height);
});
});

View file

@ -0,0 +1,41 @@
'use strict';
var Actions = require('marionette-client').Actions;
var assert = require('assert');
marionette('Vertical - Group', function() {
var client = marionette.client({
profile: require(__dirname + '/client_options.js')
});
var actions, home, system;
setup(function() {
actions = new Actions(client);
home = client.loader.getAppClass('verticalhome');
system = client.loader.getAppClass('system');
system.waitForFullyLoaded();
client.apps.launch(home.URL);
home.waitForLaunch();
});
test('check that group backgrounds highlight when hovered over', function() {
var groupSelector = '#icons .group';
// Store the metrics of the first icon before collapsing
var icon = client.helper.waitForElement(home.Selectors.firstIcon);
// Long press on the icon and don't let go so we can see the styling of
// the group underneath it
actions.press(icon).wait(1).perform();
// Check that it has the styling that would highlight its background
var group = client.findElement(groupSelector);
assert.ok(group.getAttribute('className').indexOf('drop-target') !== -1);
// Make sure the highlight is removed after releasing
actions.release().wait(1).perform();
assert.ok(group.getAttribute('className').indexOf('drop-target') === -1);
});
});

View file

@ -0,0 +1,16 @@
/* global module */
'use strict';
/**
Determines the current state of the icon on the grid.
@param {Marionette.Element} element of particular icon.
@return {String} state of the app icon.
*/
module.exports = function iconState(element) {
// XXX: this syncs up with the stamping logic in `renderIcon` in gaia_grid
return element.scriptWith(function(el) {
return el.dataset.appState;
});
};

View file

@ -0,0 +1,16 @@
/* global module */
'use strict';
/**
Helper function to determine if the icon is cached in idb.
@param {Marionette.Element} element of particular icon.
@return {String} url/uri for element.
*/
module.exports = function iconCached(element) {
// This syncs up with the logic after iconblobload event in app.js
var list = element.getAttribute('className');
return list && list.indexOf('test-icon-cached') !== 1;
};

View file

@ -0,0 +1,15 @@
/* global module */
'use strict';
/**
Return the id of a particular icon.
@param {Marionette.Element} element of particular icon.
@return {String} id for the icon.
*/
module.exports = function iconId(element) {
return element.scriptWith(function(el) {
return el.dataset.identifier;
});
};

View file

@ -0,0 +1,16 @@
/* global module */
'use strict';
/**
Helper function to determine which icon an grid item element is using.
@param {Marionette.Element} element of particular icon.
@return {String} url/uri for element.
*/
module.exports = function waitForIcon(element) {
// XXX: this syncs up with the stamping logic in `renderIcon` in gaia_grid
return element.scriptWith(function(el) {
return el.dataset.testIcon;
});
};

View file

@ -0,0 +1,38 @@
/* global module */
'use strict';
/**
Launches an icon (which may be a real app launch or download retry)
@param {Marionette.Element} element of particular icon.
*/
module.exports = function launchIcon(element) {
// XXX: work around focus issues by ensuring we clicked the thing
element.scriptWith(function(e) {
e.scrollIntoView(false);
e.__ensureClick__ = function() {
e.removeEventListener('click', e.__ensureClick__);
e.setAttribute('data-test-was-clicked', true);
};
e.addEventListener('click', e.__ensureClick__);
});
element.client.waitFor(function() {
// try to click it
element.tap();
return element.scriptWith(function(e) {
// if it was clicked clear the state if we want to run this again
if (e.getAttribute('data-test-was-clicked')) {
e.removeEventListener('click', e.__ensureClick__);
e.removeAttribute('data-test-was-clicked');
return true;
}
// otherwise try again
return false;
});
});
};

View file

@ -0,0 +1,354 @@
'use strict';
var getIconId = require('./icon_id');
/**
* Abstraction around homescreen.
* @constructor
*/
function VerticalHome(client) {
this.client = client;
this.system = client.loader.getAppClass('system');
}
/**
* @type String Origin of VerticalHome app
*/
VerticalHome.URL = 'app://verticalhome.gaiamobile.org';
VerticalHome.Selectors = {
grid: '#icons',
editHeaderText: '#edit-header h1',
editHeaderDone: '#exit-edit-mode',
editGroup: '#edit-group',
editGroupTitle: '#edit-group-title',
editGroupSave: '#edit-group-save',
editGroupTitleClear: '#edit-group-title-clear',
search: '#search',
firstIcon: '#icons div.icon:not(.placeholder)',
groupHeader: '#icons .group .header',
groupTitle: '#icons .group .header .title',
groupBackground: '#icons .group .background',
groupToggle: '#icons .group .toggle',
dividers: '#icons section.divider',
contextmenu: '#contextmenu-dialog',
themeColor: 'head meta[name="theme-color"]',
placeholders: '#icons .placeholder'
};
/**
* Launches our new homescreen and focuses on it.
*/
VerticalHome.prototype = {
URL: VerticalHome.URL,
Selectors: VerticalHome.Selectors,
get numIcons() {
return this.client.findElements(VerticalHome.Selectors.firstIcon).length;
},
get numDividers() {
return this.dividers.length;
},
get dividers() {
return this.client.findElements(VerticalHome.Selectors.dividers);
},
get contextMenu() {
return this.client.findElement(VerticalHome.Selectors.contextmenu);
},
/**
Fetch a particular type of a gaia-confirm dialog.
@param {String} type of dialog.
*/
getConfirmDialog: function(type) {
var selector = 'gaia-confirm[data-type="' + type + '"]';
return this.client.helper.waitForElement(selector);
},
/**
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 dialog = this.getConfirmDialog(type);
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;
}
});
},
/**
* Enter edit mode by long pressing a given icon, or the first icon on the
* grid if not specified.
*/
enterEditMode: function(icon) {
var actions = this.client.loader.getActions();
var firstIcon = icon ||
this.client.helper.waitForElement(VerticalHome.Selectors.firstIcon);
actions.longPress(firstIcon, 1).perform();
this.client.helper.waitForElement(VerticalHome.Selectors.editHeaderText);
},
/**
* Exits the edit mode by long-pressing the done button and waiting for it
* to disappear.
*/
exitEditMode: function() {
var done = this.client.findElement(VerticalHome.Selectors.editHeaderDone);
done.click();
this.client.helper.waitForElementToDisappear(done);
},
/**
* Emulates pressing of the hardware home button.
*/
pressHomeButton: function() {
this.client.executeScript(function() {
var home = new CustomEvent('home');
window.dispatchEvent(home);
});
},
focusRocketBar: function() {
this.client.helper.waitForElement(VerticalHome.Selectors.search).tap();
this.client.switchToFrame();
},
/**
Tap an app icon in the homescreen
@param {String} manifestURL full manifest url path.
*/
launchApp: function(manifestURL) {
// switch back to the homescreen
this.client.switchToFrame();
this.client.switchToFrame(this.system.getHomescreenIframe());
// tap the app in the homescreen
var newApp = this.getIcon(manifestURL);
newApp.tap();
// go to the system app
this.client.switchToFrame();
},
/**
Tap an app icon and switch to it's application iframe.
@param {String} manifestURL full manifest url path.
*/
launchAndSwitchToApp: function(manifestURL) {
var client = this.client.scope({ searchTimeout: 100 });
var frame;
client.waitFor(function() {
this.launchApp(manifestURL);
// wait for the app to show up
try {
frame = client.findElement(
'iframe[mozapp="' + manifestURL + '"]'
);
} catch (e) {
// try again...
return false;
}
client.switchToFrame(frame);
return true;
}.bind(this));
},
/**
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());
},
/**
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.client.findElements('[data-identifier]').map(function(el) {
return getIconId(el);
});
},
/**
Fetch an icon element on the homescreen.
@param {String} manifestURL must be a fully qualified manifest url.
@return {Marionette.Element}
*/
getIcon: function(manifestUrl, entryPoint) {
return this.client.helper.waitForElement(
'[data-identifier*="' + manifestUrl +
(entryPoint ? '-' + entryPoint : '') + '"]'
);
},
/**
Fetch the nth icon on the homescreen.
@param {number} the icon number we want
@return {Marionette.Element}
*/
getNthIcon: function(number) {
return this.client.helper.waitForElement(
'.icon:nth-of-type(' + number + ')'
);
},
/**
Get the the current meta=theme-color of the homescreen
@return {String}
*/
getThemeColor: function() {
var meta = this.client.findElement(VerticalHome.Selectors.themeColor);
return meta.getAttribute('content');
},
/**
* Waits for the homescreen to launch and switches to the frame.
*/
waitForLaunch: function() {
this.client.helper.waitForElement('body');
this.client.apps.switchToApp(VerticalHome.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;
},
containsClass: function(selector, clazz) {
return this.client.executeScript(function(selector, clazz) {
return document.querySelector(selector).classList.contains(clazz);
}, [selector, clazz]);
},
/**
* Helper function to move an icon to a specified index. Currently uses
* executeScript() and manually fiddles with the homescreen grid logic,
* this is because scripted drag/drop does not work too well within the
* vertically scrolling homescreen on b2g desktop.
* @param {Element} icon The grid icon object.
* @param {Integer} index The position to insert the icon into.
*/
moveIconToIndex: function(icon, index) {
this.client.executeScript(function(identifier, newPos) {
var app = window.wrappedJSObject.app;
var icon = app.grid.getIcon(identifier);
app.grid.moveTo(icon.detail.index, newPos);
app.grid.render();
}, [icon.getAttribute('data-identifier'), index]);
// Wait for the icon to animate into place
var actions = this.client.loader.getActions();
actions.wait(1).perform();
},
/**
* Opens the context menu by long-pressing on the first available placeholder.
* If there is no placeholder, the first icon will be dragged to the top of
* the grid to create a new group, and thus new placeholders.
*/
openContextMenu: function() {
var actions = this.client.loader.getActions();
var body = this.client.findElement('body');
actions.longPress(body, 1).perform();
}
};
module.exports = VerticalHome;

View file

@ -0,0 +1,67 @@
'use strict';
var assert = require('assert');
marionette('Vertical - 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('verticalhome');
system = client.loader.getAppClass('system');
system.waitForFullyLoaded();
home.waitForLaunch();
});
test('Localization updates icon names', function() {
var settingsManifestUrl = 'app://settings.gaiamobile.org/manifest.webapp';
var settingsIcon = home.getIcon(settingsManifestUrl);
assert.equal(
settingsIcon.text(),
home.localizedAppName('settings', 'en-US'));
client.executeScript(function() {
navigator.mozSettings.createLock().set({
'language.current': 'fr-x-psaccent'
});
});
// Localization can be async, wait for the content to update
client.waitFor(function() {
settingsIcon = home.getIcon(settingsManifestUrl);
return settingsIcon.text() ===
home.localizedAppName('settings', 'fr-x-psaccent');
});
});
test('Menu option localization', function() {
var selectors = home.Selectors;
// Change the language to french
client.executeAsyncScript(function() {
var req = navigator.mozSettings.createLock().set({
'language.current': 'fr-x-psaccent'
});
req.onsuccess = function() {
marionetteScriptFinished();
};
});
home.openContextMenu();
// Element.text() does not return the content of the shadow dom,
// so enter the shadow dom manually for now to get the text.
var expected = home.l10n('cancel');
client.waitFor(function(){
var menu = client.helper.waitForElement(selectors.contextmenu);
var content = menu.scriptWith(function(menu) {
return menu.shadowRoot.querySelector('button').innerHTML;
});
return content === expected;
});
});
});

View file

@ -0,0 +1,55 @@
/* global __dirname */
'use strict';
var Rocketbar = require(
'../../../../apps/system/test/marionette/lib/rocketbar.js');
var Server = require('../../../../shared/test/integration/server');
marionette('Vertical - Search Terms: URI scheme', function() {
var client = marionette.client({
profile: require(__dirname + '/client_options.js'),
desiredCapabilities: { raisesAccessibilityExceptions: false }
});
var home, rocketbar, search, system, server;
suiteSetup(function(done) {
Server.create(__dirname + '/fixtures/', function(err, _server) {
server = _server;
done();
});
});
suiteTeardown(function() {
server.stop();
});
setup(function() {
home = client.loader.getAppClass('verticalhome');
search = client.loader.getAppClass('search');
rocketbar = new Rocketbar(client);
system = client.loader.getAppClass('system');
system.waitForFullyLoaded();
});
function searchAndVerifyBrowser(searchTerms) {
var searchUrl = server.url('search.html') + '?q={searchTerms}';
client.settings.set('search.urlTemplate', searchUrl);
// Lauch the rocketbar and trigger its first run notice
home.waitForLaunch();
home.focusRocketBar();
search.triggerFirstRun(rocketbar);
rocketbar.enterText(searchTerms + '\uE006');
// Ensure the browser is launched
rocketbar.switchToBrowserFrame(searchTerms);
}
test('Testing URI Scheme - DATA:', function() {
searchAndVerifyBrowser('data:text/html, <html>Hello World</html>');
});
});

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

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

View 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;

View file

@ -0,0 +1,187 @@
'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';
}
};
/**
* 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;