summaryrefslogtreecommitdiff
path: root/toolkit/devtools/app-manager/test
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/devtools/app-manager/test')
-rw-r--r--toolkit/devtools/app-manager/test/browser.ini10
-rw-r--r--toolkit/devtools/app-manager/test/browser_manifest_editor.js197
-rw-r--r--toolkit/devtools/app-manager/test/chrome.ini11
-rw-r--r--toolkit/devtools/app-manager/test/head.js175
-rw-r--r--toolkit/devtools/app-manager/test/hosted_app.manifest3
-rw-r--r--toolkit/devtools/app-manager/test/manifest.webapp9
-rw-r--r--toolkit/devtools/app-manager/test/test_app_validator.html206
-rw-r--r--toolkit/devtools/app-manager/test/test_connection_store.html109
-rw-r--r--toolkit/devtools/app-manager/test/test_device_store.html87
-rw-r--r--toolkit/devtools/app-manager/test/test_projects_store.html60
-rw-r--r--toolkit/devtools/app-manager/test/test_remain_connected.html122
-rw-r--r--toolkit/devtools/app-manager/test/test_template.html256
-rw-r--r--toolkit/devtools/app-manager/test/validator/no-name-or-icon/home.html0
-rw-r--r--toolkit/devtools/app-manager/test/validator/no-name-or-icon/manifest.webapp3
-rw-r--r--toolkit/devtools/app-manager/test/validator/non-absolute-path/manifest.webapp7
-rw-r--r--toolkit/devtools/app-manager/test/validator/valid/alsoValid/manifest.webapp7
-rw-r--r--toolkit/devtools/app-manager/test/validator/valid/home.html0
-rw-r--r--toolkit/devtools/app-manager/test/validator/valid/icon.png0
-rw-r--r--toolkit/devtools/app-manager/test/validator/valid/manifest.webapp7
-rw-r--r--toolkit/devtools/app-manager/test/validator/wrong-launch-path/icon.png0
-rw-r--r--toolkit/devtools/app-manager/test/validator/wrong-launch-path/manifest.webapp7
21 files changed, 1276 insertions, 0 deletions
diff --git a/toolkit/devtools/app-manager/test/browser.ini b/toolkit/devtools/app-manager/test/browser.ini
new file mode 100644
index 000000000..f88707390
--- /dev/null
+++ b/toolkit/devtools/app-manager/test/browser.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+skip-if = e10s # Bug ?????? - devtools tests disabled with e10s
+subsuite = devtools
+support-files =
+ head.js
+ hosted_app.manifest
+ manifest.webapp
+
+[browser_manifest_editor.js]
+skip-if = true # Bug 989169 - Very intermittent, but App Manager about to be removed
diff --git a/toolkit/devtools/app-manager/test/browser_manifest_editor.js b/toolkit/devtools/app-manager/test/browser_manifest_editor.js
new file mode 100644
index 000000000..676eff6f1
--- /dev/null
+++ b/toolkit/devtools/app-manager/test/browser_manifest_editor.js
@@ -0,0 +1,197 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const {Services} = Cu.import("resource://gre/modules/Services.jsm");
+
+const MANIFEST_EDITOR_ENABLED = "devtools.appmanager.manifestEditor.enabled";
+
+let gManifestWindow, gManifestEditor;
+
+function test() {
+ waitForExplicitFinish();
+
+ Task.spawn(function() {
+ Services.prefs.setBoolPref(MANIFEST_EDITOR_ENABLED, true);
+ let tab = yield openAppManager();
+ yield selectProjectsPanel();
+ yield addSamplePackagedApp();
+ yield showSampleProjectDetails();
+
+ gManifestWindow = getManifestWindow();
+ gManifestEditor = getProjectsWindow().UI.manifestEditor;
+ yield changeManifestValue("name", "the best app");
+ yield changeManifestValueBad("name", "the worst app");
+ yield addNewManifestProperty("developer", "foo", "bar");
+
+ // add duplicate property in the same parent doesn't create duplicates
+ yield addNewManifestProperty("developer", "foo", "bar2");
+
+ // add propery with same key in other parent is allowed
+ yield addNewManifestProperty("tester", "foo", "new");
+
+ yield addNewManifestPropertyBad("developer", "blob", "bob");
+ yield removeManifestProperty("developer", "foo");
+ gManifestWindow = null;
+ gManifestEditor = null;
+
+ yield removeSamplePackagedApp();
+ yield removeTab(tab);
+ Services.prefs.setBoolPref(MANIFEST_EDITOR_ENABLED, false);
+ finish();
+ });
+}
+
+// Wait until the animation from commitHierarchy has completed
+function waitForUpdate() {
+ return waitForTime(gManifestEditor.editor.lazyEmptyDelay + 1);
+}
+
+function changeManifestValue(key, value) {
+ return Task.spawn(function() {
+ let propElem = gManifestWindow.document
+ .querySelector("[id ^= '" + key + "']");
+ is(propElem.querySelector(".name").value, key,
+ "Key doesn't match expected value");
+
+ let valueElem = propElem.querySelector(".value");
+ EventUtils.sendMouseEvent({ type: "mousedown" }, valueElem, gManifestWindow);
+
+ let valueInput = propElem.querySelector(".element-value-input");
+ valueInput.value = '"' + value + '"';
+ EventUtils.sendKey("RETURN", gManifestWindow);
+
+ yield waitForUpdate();
+ // Elements have all been replaced, re-select them
+ propElem = gManifestWindow.document.querySelector("[id ^= '" + key + "']");
+ valueElem = propElem.querySelector(".value");
+ is(valueElem.value, '"' + value + '"',
+ "Value doesn't match expected value");
+
+ is(gManifestEditor.manifest[key], value,
+ "Manifest doesn't contain expected value");
+ });
+}
+
+function changeManifestValueBad(key, value) {
+ return Task.spawn(function() {
+ let propElem = gManifestWindow.document
+ .querySelector("[id ^= '" + key + "']");
+ is(propElem.querySelector(".name").value, key,
+ "Key doesn't match expected value");
+
+ let valueElem = propElem.querySelector(".value");
+ EventUtils.sendMouseEvent({ type: "mousedown" }, valueElem, gManifestWindow);
+
+ let valueInput = propElem.querySelector(".element-value-input");
+ // Leaving out quotes will result in an error, so no change should be made.
+ valueInput.value = value;
+ EventUtils.sendKey("RETURN", gManifestWindow);
+
+ yield waitForUpdate();
+ // Elements have all been replaced, re-select them
+ propElem = gManifestWindow.document.querySelector("[id ^= '" + key + "']");
+ valueElem = propElem.querySelector(".value");
+ isnot(valueElem.value, '"' + value + '"',
+ "Value was changed, but it should not have been");
+
+ isnot(gManifestEditor.manifest[key], value,
+ "Manifest was changed, but it should not have been");
+ });
+}
+
+function addNewManifestProperty(parent, key, value) {
+ info("Adding new property - parent: " + parent + "; key: " + key + "; value: " + value + "\n\n");
+ return Task.spawn(function() {
+ let parentElem = gManifestWindow.document
+ .querySelector("[id ^= '" + parent + "']");
+ ok(parentElem, "Found parent element: " + parentElem.id);
+
+ let addPropertyElem = parentElem.querySelector(".variables-view-add-property");
+ ok(addPropertyElem, "Found add-property button");
+
+ EventUtils.sendMouseEvent({ type: "mousedown" }, addPropertyElem, gManifestWindow);
+
+ let nameInput = parentElem.querySelector(".element-name-input");
+ nameInput.value = key;
+ EventUtils.sendKey("TAB", gManifestWindow);
+
+ let valueInput = parentElem.querySelector(".element-value-input");
+ valueInput.value = '"' + value + '"';
+ EventUtils.sendKey("RETURN", gManifestWindow);
+
+ yield waitForUpdate();
+
+ parentElem = gManifestWindow.document.querySelector("[id ^= '" + parent + "']");
+ let elems = parentElem.querySelectorAll("[id ^= '" + key + "']");
+ is(elems.length, 1, "No duplicate property is added");
+
+ let newElem = elems[0];
+ let nameElem = newElem.querySelector(".name");
+ is(nameElem.value, key, "Key doesn't match expected Key");
+
+ ok(key in gManifestEditor.manifest[parent],
+ "Manifest doesn't contain expected key");
+
+ let valueElem = newElem.querySelector(".value");
+ is(valueElem.value, '"' + value + '"',
+ "Value doesn't match expected value");
+
+ is(gManifestEditor.manifest[parent][key], value,
+ "Manifest doesn't contain expected value");
+ });
+}
+
+function addNewManifestPropertyBad(parent, key, value) {
+ return Task.spawn(function() {
+ let parentElem = gManifestWindow.document
+ .querySelector("[id ^= '" + parent + "']");
+ ok(parentElem,
+ "Found parent element");
+ let addPropertyElem = parentElem
+ .querySelector(".variables-view-add-property");
+ ok(addPropertyElem,
+ "Found add-property button");
+
+ EventUtils.sendMouseEvent({ type: "mousedown" }, addPropertyElem, gManifestWindow);
+
+ let nameInput = parentElem.querySelector(".element-name-input");
+ nameInput.value = key;
+ EventUtils.sendKey("TAB", gManifestWindow);
+
+ let valueInput = parentElem.querySelector(".element-value-input");
+ // Leaving out quotes will result in an error, so no change should be made.
+ valueInput.value = value;
+ EventUtils.sendKey("RETURN", gManifestWindow);
+
+ yield waitForUpdate();
+
+ let newElem = gManifestWindow.document.querySelector("[id ^= '" + key + "']");
+ ok(!newElem, "Key was added, but it should not have been");
+ ok(!(key in gManifestEditor.manifest[parent]),
+ "Manifest contains key, but it should not");
+ });
+}
+
+function removeManifestProperty(parent, key) {
+ info("*** Remove property test ***");
+
+ return Task.spawn(function() {
+ let parentElem = gManifestWindow.document
+ .querySelector("[id ^= '" + parent + "']");
+ ok(parentElem, "Found parent element");
+
+ let keyExists = key in gManifestEditor.manifest[parent];
+ ok(keyExists,
+ "The manifest contains the key under the expected parent");
+
+ let newElem = gManifestWindow.document.querySelector("[id ^= '" + key + "']");
+ let removePropertyButton = newElem.querySelector(".variables-view-delete");
+ ok(removePropertyButton, "The remove property button was found");
+ removePropertyButton.click();
+
+ yield waitForUpdate();
+
+ ok(!(key in gManifestEditor.manifest[parent]), "Property was successfully removed");
+ });
+}
diff --git a/toolkit/devtools/app-manager/test/chrome.ini b/toolkit/devtools/app-manager/test/chrome.ini
new file mode 100644
index 000000000..803059f45
--- /dev/null
+++ b/toolkit/devtools/app-manager/test/chrome.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+support-files =
+ hosted_app.manifest
+ validator/*
+
+[test_connection_store.html]
+[test_device_store.html]
+[test_projects_store.html]
+[test_remain_connected.html]
+[test_template.html]
+[test_app_validator.html]
diff --git a/toolkit/devtools/app-manager/test/head.js b/toolkit/devtools/app-manager/test/head.js
new file mode 100644
index 000000000..a8cb42214
--- /dev/null
+++ b/toolkit/devtools/app-manager/test/head.js
@@ -0,0 +1,175 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const {utils: Cu, classes: Cc, interfaces: Ci} = Components;
+
+const {Promise: promise} =
+ Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js", {});
+const {devtools} =
+ Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+const {require} = devtools;
+
+const {AppProjects} = require("devtools/app-manager/app-projects");
+
+const APP_MANAGER_URL = "about:app-manager";
+const TEST_BASE =
+ "chrome://mochitests/content/browser/browser/devtools/app-manager/test/";
+const HOSTED_APP_MANIFEST = TEST_BASE + "hosted_app.manifest";
+
+const PACKAGED_APP_DIR_PATH = getTestFilePath(".");
+
+gDevTools.testing = true;
+SimpleTest.registerCleanupFunction(() => {
+ gDevTools.testing = false;
+});
+
+function addTab(url, targetWindow = window) {
+ info("Adding tab: " + url);
+
+ let deferred = promise.defer();
+ let targetBrowser = targetWindow.gBrowser;
+
+ targetWindow.focus();
+ let tab = targetBrowser.selectedTab = targetBrowser.addTab(url);
+ let linkedBrowser = tab.linkedBrowser;
+
+ linkedBrowser.addEventListener("load", function onLoad() {
+ linkedBrowser.removeEventListener("load", onLoad, true);
+ info("Tab added and finished loading: " + url);
+ deferred.resolve(tab);
+ }, true);
+
+ return deferred.promise;
+}
+
+function removeTab(tab, targetWindow = window) {
+ info("Removing tab.");
+
+ let deferred = promise.defer();
+ let targetBrowser = targetWindow.gBrowser;
+ let tabContainer = targetBrowser.tabContainer;
+
+ tabContainer.addEventListener("TabClose", function onClose(aEvent) {
+ tabContainer.removeEventListener("TabClose", onClose, false);
+ info("Tab removed and finished closing.");
+ deferred.resolve();
+ }, false);
+
+ targetBrowser.removeTab(tab);
+
+ return deferred.promise;
+}
+
+function openAppManager() {
+ return addTab(APP_MANAGER_URL);
+}
+
+function addSampleHostedApp() {
+ info("Adding sample hosted app");
+ let projectsWindow = getProjectsWindow();
+ let projectsDocument = projectsWindow.document;
+ let url = projectsDocument.querySelector("#url-input");
+ url.value = HOSTED_APP_MANIFEST;
+ return projectsWindow.UI.addHosted();
+}
+
+function removeSampleHostedApp() {
+ info("Removing sample hosted app");
+ return AppProjects.remove(HOSTED_APP_MANIFEST);
+}
+
+function addSamplePackagedApp() {
+ info("Adding sample packaged app");
+ let appDir = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsIFile);
+ appDir.initWithPath(PACKAGED_APP_DIR_PATH);
+ return getProjectsWindow().UI.addPackaged(appDir);
+}
+
+function removeSamplePackagedApp() {
+ info("Removing sample packaged app");
+ return AppProjects.remove(PACKAGED_APP_DIR_PATH);
+}
+
+function getProjectsWindow() {
+ return content.document.querySelector(".projects-panel").contentWindow;
+}
+
+function getManifestWindow() {
+ return getProjectsWindow().document.querySelector(".variables-view")
+ .contentWindow;
+}
+
+function waitForProjectsPanel(deferred = promise.defer()) {
+ info("Wait for projects panel");
+
+ let projectsWindow = getProjectsWindow();
+ let projectsUI = projectsWindow.UI;
+ if (!projectsUI) {
+ info("projectsUI false");
+ projectsWindow.addEventListener("load", function onLoad() {
+ info("got load event");
+ projectsWindow.removeEventListener("load", onLoad);
+ waitForProjectsPanel(deferred);
+ });
+ return deferred.promise;
+ }
+
+ if (projectsUI.isReady) {
+ info("projectsUI ready");
+ deferred.resolve();
+ return deferred.promise;
+ }
+
+ info("projectsUI not ready");
+ projectsUI.once("ready", deferred.resolve);
+ return deferred.promise;
+}
+
+function selectProjectsPanel() {
+ return Task.spawn(function() {
+ let projectsButton = content.document.querySelector(".projects-button");
+ EventUtils.sendMouseEvent({ type: "click" }, projectsButton, content);
+
+ yield waitForProjectsPanel();
+ });
+}
+
+function waitForProjectSelection() {
+ info("Wait for project selection");
+
+ let deferred = promise.defer();
+ getProjectsWindow().UI.once("project-selected", deferred.resolve);
+ return deferred.promise;
+}
+
+function selectFirstProject() {
+ return Task.spawn(function() {
+ let projectsFrame = content.document.querySelector(".projects-panel");
+ let projectsWindow = projectsFrame.contentWindow;
+ let projectsDoc = projectsWindow.document;
+ let projectItem = projectsDoc.querySelector(".project-item");
+ EventUtils.sendMouseEvent({ type: "click" }, projectItem, projectsWindow);
+
+ yield waitForProjectSelection();
+ });
+}
+
+function showSampleProjectDetails() {
+ return Task.spawn(function() {
+ yield selectProjectsPanel();
+ yield selectFirstProject();
+ });
+}
+
+function waitForTick() {
+ let deferred = promise.defer();
+ executeSoon(deferred.resolve);
+ return deferred.promise;
+}
+
+function waitForTime(aDelay) {
+ let deferred = promise.defer();
+ setTimeout(deferred.resolve, aDelay);
+ return deferred.promise;
+}
diff --git a/toolkit/devtools/app-manager/test/hosted_app.manifest b/toolkit/devtools/app-manager/test/hosted_app.manifest
new file mode 100644
index 000000000..c23776aff
--- /dev/null
+++ b/toolkit/devtools/app-manager/test/hosted_app.manifest
@@ -0,0 +1,3 @@
+{
+ "name": "My hosted app"
+}
diff --git a/toolkit/devtools/app-manager/test/manifest.webapp b/toolkit/devtools/app-manager/test/manifest.webapp
new file mode 100644
index 000000000..caa670bf4
--- /dev/null
+++ b/toolkit/devtools/app-manager/test/manifest.webapp
@@ -0,0 +1,9 @@
+{
+ "name": "My packaged app",
+ "developer": {
+ "name": "Foo Bar"
+ },
+ "tester" : {
+ "who": "qa"
+ }
+}
diff --git a/toolkit/devtools/app-manager/test/test_app_validator.html b/toolkit/devtools/app-manager/test/test_app_validator.html
new file mode 100644
index 000000000..e9376d644
--- /dev/null
+++ b/toolkit/devtools/app-manager/test/test_app_validator.html
@@ -0,0 +1,206 @@
+<!DOCTYPE html>
+
+<html>
+
+ <head>
+ <meta charset="utf8">
+ <title></title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ </head>
+
+ <body>
+
+ <script type="application/javascript;version=1.8">
+ const Cu = Components.utils;
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+ Cu.import("resource://testing-common/httpd.js");
+ const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+ const {require} = devtools;
+
+ const {AppValidator} = require("devtools/app-manager/app-validator");
+ const {Services} = Cu.import("resource://gre/modules/Services.jsm");
+ const nsFile = Components.Constructor("@mozilla.org/file/local;1",
+ "nsILocalFile", "initWithPath");
+ const cr = Cc["@mozilla.org/chrome/chrome-registry;1"]
+ .getService(Ci.nsIChromeRegistry);
+ const strings = Services.strings.createBundle("chrome://browser/locale/devtools/app-manager.properties");
+ let httpserver, origin;
+
+ window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ httpserver = new HttpServer();
+ httpserver.start(-1);
+ origin = "http://localhost:" + httpserver.identity.primaryPort + "/";
+
+ next();
+ }
+
+ function createHosted(path, manifestFile="/manifest.webapp") {
+ let dirPath = getTestFilePath("validator/" + path);
+ httpserver.registerDirectory("/", nsFile(dirPath));
+ return new AppValidator({
+ type: "hosted",
+ location: origin + manifestFile
+ });
+ }
+
+ function createPackaged(path) {
+ let dirPath = getTestFilePath("validator/" + path);
+ return new AppValidator({
+ type: "packaged",
+ location: dirPath
+ });
+ }
+
+ function next() {
+ let test = tests.shift();
+ if (test) {
+ try {
+ test();
+ } catch(e) {
+ console.error("exception", String(e), e, e.stack);
+ }
+ } else {
+ httpserver.stop(function() {
+ SimpleTest.finish();
+ });
+ }
+ }
+
+ let tests = [
+ // Test a 100% valid example
+ function () {
+ let validator = createHosted("valid");
+ validator.validate().then(() => {
+ is(validator.errors.length, 0, "valid app got no error");
+ is(validator.warnings.length, 0, "valid app got no warning");
+
+ next();
+ });
+ },
+
+ function () {
+ let validator = createPackaged("valid");
+ validator.validate().then(() => {
+ is(validator.errors.length, 0, "valid packaged app got no error");
+ is(validator.warnings.length, 0, "valid packaged app got no warning");
+
+ next();
+ });
+ },
+
+ // Test a launch path that returns a 404
+ function () {
+ let validator = createHosted("wrong-launch-path");
+ validator.validate().then(() => {
+ is(validator.errors.length, 1, "app with non-existant launch path got an error");
+ is(validator.errors[0], strings.formatStringFromName("validator.accessFailedLaunchPathBadHttpCode", [origin + "wrong-path.html", 404], 2),
+ "with the right error message");
+ is(validator.warnings.length, 0, "but no warning");
+ next();
+ });
+ },
+ function () {
+ let validator = createPackaged("wrong-launch-path");
+ validator.validate().then(() => {
+ is(validator.errors.length, 1, "app with wrong path got an error");
+ let file = nsFile(validator.project.location);
+ file.append("wrong-path.html");
+ let url = Services.io.newFileURI(file);
+ is(validator.errors[0], strings.formatStringFromName("validator.accessFailedLaunchPath", [url.spec], 1),
+ "with the expected message");
+ is(validator.warnings.length, 0, "but no warning");
+
+ next();
+ });
+ },
+
+ // Test when using a non-absolute path for launch_path
+ function () {
+ let validator = createHosted("non-absolute-path");
+ validator.validate().then(() => {
+ is(validator.errors.length, 1, "app with non absolute path got an error");
+ is(validator.errors[0], strings.formatStringFromName("validator.nonAbsoluteLaunchPath", ["non-absolute.html"], 1),
+ "with expected message");
+ is(validator.warnings.length, 0, "but no warning");
+ next();
+ });
+ },
+ function () {
+ let validator = createPackaged("non-absolute-path");
+ validator.validate().then(() => {
+ is(validator.errors.length, 1, "app with non absolute path got an error");
+ is(validator.errors[0], strings.formatStringFromName("validator.nonAbsoluteLaunchPath", ["non-absolute.html"], 1),
+ "with expected message");
+ is(validator.warnings.length, 0, "but no warning");
+ next();
+ });
+ },
+
+ // Test multiple failures (missing name [error] and icon [warning])
+ function () {
+ let validator = createHosted("no-name-or-icon");
+ validator.validate().then(() => {
+ checkNoNameOrIcon(validator);
+ });
+ },
+ function () {
+ let validator = createPackaged("no-name-or-icon");
+ validator.validate().then(() => {
+ checkNoNameOrIcon(validator);
+ });
+ },
+
+ // Test a regular URL instead of a direct link to the manifest
+ function () {
+ let validator = createHosted("valid", "/");
+ validator.validate().then(() => {
+ is(validator.warnings.length, 0, "manifest found got no warning");
+ is(validator.errors.length, 0, "manifest found got no error");
+
+ next();
+ });
+ },
+
+ // Test finding a manifest at origin's root
+ function () {
+ let validator = createHosted("valid", "/unexisting-dir");
+ validator.validate().then(() => {
+ is(validator.warnings.length, 0, "manifest found at origin root got no warning");
+ is(validator.errors.length, 0, "manifest found at origin root got no error");
+
+ next();
+ });
+ },
+
+ // Test priorization of manifest.webapp at provided location instead of a manifest located at origin's root
+ function() {
+ let validator = createHosted("valid", "/alsoValid");
+ validator.validate().then(() => {
+ is(validator.manifest.name, "valid at subfolder", "manifest at subfolder was used");
+
+ next();
+ });
+ }
+ ];
+
+ function checkNoNameOrIcon(validator) {
+ is(validator.errors.length, 1, "app with no name has an error");
+ is(validator.errors[0],
+ strings.GetStringFromName("validator.missNameManifestProperty"),
+ "with expected message");
+ is(validator.warnings.length, 1, "app with no icon has a warning");
+ is(validator.warnings[0],
+ strings.GetStringFromName("validator.missIconsManifestProperty"),
+ "with expected message");
+ next();
+ }
+
+ </script>
+ </body>
+</html>
diff --git a/toolkit/devtools/app-manager/test/test_connection_store.html b/toolkit/devtools/app-manager/test/test_connection_store.html
new file mode 100644
index 000000000..ef2f0fd0f
--- /dev/null
+++ b/toolkit/devtools/app-manager/test/test_connection_store.html
@@ -0,0 +1,109 @@
+<!DOCTYPE html>
+
+<!--
+Bug 901519 - [app manager] data store for connections
+-->
+
+<html>
+
+ <head>
+ <meta charset="utf8">
+ <title></title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ </head>
+
+ <body>
+
+ <div id="root">
+ <span id="status" template='{"type":"textContent","path":"status"}'></span>
+ <span id="host" template='{"type":"textContent","path":"host"}'></span>
+ <span id="port" template='{"type":"textContent","path":"port"}'></span>
+ </div>
+
+ <script type="application/javascript;version=1.8" src="chrome://browser/content/devtools/app-manager/template.js"></script>
+ <script type="application/javascript;version=1.8">
+ const Cu = Components.utils;
+ Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
+
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ Cu.import("resource://gre/modules/Services.jsm");
+ Cu.import("resource:///modules/devtools/gDevTools.jsm");
+
+ const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+ const {require} = devtools;
+
+ const {ConnectionManager} = require("devtools/client/connection-manager");
+ const ConnectionStore = require("devtools/app-manager/connection-store");
+
+ let connection = ConnectionManager.createConnection();
+ let store = new ConnectionStore(connection);
+
+ let root = document.querySelector("#root");
+ let status = root.querySelector("#status");
+ let host = root.querySelector("#host");
+ let port = root.querySelector("#port");
+ let template = new Template(root, store, () => {});
+ template.start();
+
+ connection.host = "foobar";
+ connection.port = 42;
+
+ is(host.textContent, "foobar", "host updated");
+ is(port.textContent, 42, "port updated");
+
+ let been_through_connecting = false;
+ let been_through_connected = false;
+ let been_through_disconnected = false;
+
+ is(status.textContent, "disconnected", "status updated (diconnected)");
+
+ connection.once("connecting", (e) => {
+ SimpleTest.executeSoon(() => {
+ been_through_connecting = true;
+ is(status.textContent, "connecting", "status updated (connecting)");
+ })
+ });
+
+ connection.once("connected", (e) => {
+ SimpleTest.executeSoon(() => {
+ been_through_connected = true;
+ is(status.textContent, "connected", "status updated (connected)");
+ connection.disconnect();
+ })
+ });
+
+ connection.once("disconnected", (e) => {
+ SimpleTest.executeSoon(() => {
+ been_through_disconnected = true;
+ is(status.textContent, "disconnected", "status updated (disconnected)");
+ connection.destroy();
+ finishup();
+ })
+ });
+
+ function finishup() {
+ ok(been_through_connecting &&
+ been_through_connected &&
+ been_through_disconnected, "All updates happened");
+ DebuggerServer.destroy();
+ SimpleTest.finish();
+ }
+
+ connection.host = null; // force pipe
+ connection.port = null;
+
+ connection.connect();
+ }
+
+ </script>
+ </body>
+</html>
diff --git a/toolkit/devtools/app-manager/test/test_device_store.html b/toolkit/devtools/app-manager/test/test_device_store.html
new file mode 100644
index 000000000..1c2767d17
--- /dev/null
+++ b/toolkit/devtools/app-manager/test/test_device_store.html
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+
+<!--
+Bug 901520 - [app manager] data store for device
+-->
+
+<html>
+
+ <head>
+ <meta charset="utf8">
+ <title></title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ </head>
+
+ <body>
+
+ <script type="application/javascript;version=1.8" src="chrome://browser/content/devtools/app-manager/template.js"></script>
+ <script type="application/javascript;version=1.8">
+ const Cu = Components.utils;
+ Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
+
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ function compare(o1, o2, msg) {
+ is(JSON.stringify(o1), JSON.stringify(o2), msg);
+ }
+
+ window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ Cu.import("resource://gre/modules/Services.jsm");
+ Cu.import("resource:///modules/devtools/gDevTools.jsm");
+
+
+ const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+ const {require} = devtools;
+
+ const {ConnectionManager} = require("devtools/client/connection-manager");
+ const DeviceStore = require("devtools/app-manager/device-store");
+
+ let {getDeviceFront} = devtools.require("devtools/server/actors/device");
+
+ let connection = ConnectionManager.createConnection();
+ let store = new DeviceStore(connection);
+
+ connection.once("connected", function() {
+ store.on("set", function check(event, path, value) {
+ if (path.join(".") != "description") return;
+ store.off("set", check);
+ info("Connected");
+ connection.client.listTabs((resp) => {
+ info("List tabs response");
+ let deviceFront = getDeviceFront(connection.client, resp);
+ deviceFront.getDescription().then(json => {
+ info("getDescription response: " + JSON.stringify(json));
+ json.dpi = Math.ceil(json.dpi);
+ for (let key in json) {
+ compare(json[key], store.object.description[key], "description." + key + " is valid");
+ compare(json[key], value[key], "description." + key + " is valid");
+ }
+ connection.disconnect();
+ }).then(null, (error) => ok(false, "Error:" + error));
+ });
+ });
+ });
+
+ connection.once("disconnected", function() {
+ compare(store.object, {description:{},permissions:[],tabs:[]}, "empty store after disconnect")
+ connection.destroy();
+ DebuggerServer.destroy();
+ SimpleTest.finish();
+ });
+
+ compare(store.object, {description:{},permissions:[],tabs:[]}, "empty store before disconnect")
+
+ connection.connect();
+
+ }
+
+ </script>
+ </body>
+</html>
diff --git a/toolkit/devtools/app-manager/test/test_projects_store.html b/toolkit/devtools/app-manager/test/test_projects_store.html
new file mode 100644
index 000000000..907404c19
--- /dev/null
+++ b/toolkit/devtools/app-manager/test/test_projects_store.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+
+<!--
+Bug 907206 - data store for local apps
+-->
+
+<html>
+
+ <head>
+ <meta charset="utf8">
+ <title></title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ </head>
+
+ <body>
+
+ <script type="application/javascript;version=1.8">
+ const Cu = Components.utils;
+
+ window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+ const {require} = devtools;
+
+ const { AppProjects } = require("devtools/app-manager/app-projects");
+
+ function testHosted(projects) {
+ let manifestURL = document.location.href.replace("test_projects_store.html", "hosted_app/webapp.manifest");
+ AppProjects.addHosted(manifestURL)
+ .then(function (app) {
+ is(projects.length, 1,
+ "Hosted app has been added");
+ is(projects[0], app);
+ is(app.type, "hosted", "valid type");
+ is(app.location, manifestURL, "valid location");
+ is(AppProjects.get(manifestURL), app,
+ "get() returns the same app object");
+ AppProjects.remove(manifestURL)
+ .then(function () {
+ is(projects.length, 0,
+ "Hosted app has been removed");
+ SimpleTest.finish();
+ });
+ });
+ }
+
+ AppProjects.once("ready", function (event, projects) {
+ is(projects, AppProjects.store.object.projects,
+ "The ready event data is the store projects list");
+ testHosted(projects);
+ });
+
+ }
+
+ </script>
+ </body>
+</html>
diff --git a/toolkit/devtools/app-manager/test/test_remain_connected.html b/toolkit/devtools/app-manager/test/test_remain_connected.html
new file mode 100644
index 000000000..819e3d6c0
--- /dev/null
+++ b/toolkit/devtools/app-manager/test/test_remain_connected.html
@@ -0,0 +1,122 @@
+<!DOCTYPE html>
+
+<!--
+Bug 912646 - Closing app toolbox causes phone to disconnect
+-->
+
+<html>
+
+ <head>
+ <meta charset="utf8">
+ <title></title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ </head>
+
+ <body>
+
+ <script type="application/javascript;version=1.8">
+ const Cu = Components.utils;
+
+ Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
+
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ Cu.import("resource:///modules/devtools/gDevTools.jsm");
+
+ const {devtools} =
+ Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+ const {require} = devtools;
+
+ const {Connection, ConnectionManager} =
+ require("devtools/client/connection-manager");
+ const ConnectionStore =
+ require("devtools/app-manager/connection-store");
+
+ let connection = ConnectionManager.createConnection();
+
+ connection.host = null; // force pipe
+ connection.port = null;
+
+ let been_through_connecting = false;
+ let been_through_connected = false;
+ let been_through_disconnected = false;
+
+ is(connection.status, Connection.Status.DISCONNECTED,
+ "status updated (diconnected)");
+
+ connection.once("connecting", () => {
+ SimpleTest.executeSoon(() => {
+ been_through_connecting = true;
+ is(connection.status, Connection.Status.CONNECTING,
+ "status updated (connecting)");
+ })
+ });
+
+ connection.once("connected", () => {
+ SimpleTest.executeSoon(() => {
+ been_through_connected = true;
+ is(connection.status, Connection.Status.CONNECTED,
+ "status updated (connected)");
+ cycleToolbox();
+ })
+ });
+
+ function cycleToolbox() {
+ connection.client.listTabs(response => {
+ let options = {
+ form: response.tabs[0],
+ client: connection.client,
+ chrome: true
+ };
+ devtools.TargetFactory.forRemoteTab(options).then(target => {
+ let hostType = devtools.Toolbox.HostType.WINDOW;
+ gDevTools.showToolbox(target,
+ null,
+ hostType).then(toolbox => {
+ SimpleTest.executeSoon(() => {
+ toolbox.once("destroyed", onDestroyToolbox);
+ toolbox.destroy();
+ });
+ });
+ });
+ });
+ }
+
+ function onDestroyToolbox() {
+ is(connection.status, Connection.Status.CONNECTED,
+ "toolbox cycled, still connected");
+ connection.disconnect();
+ }
+
+ connection.once("disconnected", () => {
+ SimpleTest.executeSoon(() => {
+ been_through_disconnected = true;
+ is(connection.status, Connection.Status.DISCONNECTED,
+ "status updated (disconnected)");
+ connection.destroy();
+ finishUp();
+ })
+ });
+
+ function finishUp() {
+ ok(been_through_connecting &&
+ been_through_connected &&
+ been_through_disconnected, "All updates happened");
+ DebuggerServer.destroy();
+ SimpleTest.finish();
+ }
+
+ connection.connect();
+ }
+
+ </script>
+ </body>
+</html>
diff --git a/toolkit/devtools/app-manager/test/test_template.html b/toolkit/devtools/app-manager/test/test_template.html
new file mode 100644
index 000000000..b116e2268
--- /dev/null
+++ b/toolkit/devtools/app-manager/test/test_template.html
@@ -0,0 +1,256 @@
+<!DOCTYPE html>
+
+<html>
+
+ <head>
+ <meta charset="utf8">
+ <title></title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ </head>
+
+ <div id="root">
+ <span template='{"type":"textContent","path":"title"}'></span>
+ <span template='{"type":"attribute","name":"title","path":"title"}'></span>
+ <span template='{"type":"localizedContent","paths":["foo2.foo_l10n","foo2.bar_l10n"],"property":"foo2"}'></span>
+ <div template-for='{"path":"mop","childSelector":"#template-for"}'></div>
+ <div template-loop='{"arrayPath":"foo1.bar1","childSelector":"#template-loop"}'></div>
+ </div>
+
+ <div id="ref0">
+ <span template='{"type":"textContent","path":"title"}'>ttt</span>
+ <span title="ttt" template='{"type":"attribute","name":"title","path":"title"}'></span>
+ <span template='{"type":"localizedContent","paths":["foo2.foo_l10n","foo2.bar_l10n"],"property":"foo2"}'>foo2:foo_l10n/bar_l10n</span>
+ <div template-for='{"path":"mop","childSelector":"#template-for"}'><span template='{"type":"textContent","path":"name","rootPath":"mop"}'>meh</span></div>
+ <div template-loop='{"arrayPath":"foo1.bar1","childSelector":"#template-loop"}'>
+ <div>
+ <span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.0"}'>xx0</span>
+ <span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.0"}'>a</span>
+ <span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.0"}'>b</span>
+ </div>
+ <div>
+ <span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.1"}'>xx1</span>
+ <span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.1"}'>a</span>
+ <span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.1"}'>b</span>
+ </div>
+ </div>
+ </div>
+
+
+ <div id="ref1">
+ <span template='{"type":"textContent","path":"title"}'>xxx</span>
+ <span title="xxx" template='{"type":"attribute","name":"title","path":"title"}'></span>
+ <span template='{"type":"localizedContent","paths":["foo2.foo_l10n","foo2.bar_l10n"],"property":"foo2"}'>foo2:foo2_l10n/bar_l10n</span>
+ <div template-for='{"path":"mop","childSelector":"#template-for"}'><span template='{"type":"textContent","path":"name","rootPath":"mop"}'>meh2</span></div>
+ <div template-loop='{"arrayPath":"foo1.bar1","childSelector":"#template-loop"}'>
+ <div>
+ <span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.0"}'>xx0</span>
+ <span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.0"}'>a</span>
+ <span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.0"}'>b</span>
+ </div>
+ <div>
+ <span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.1"}'>xx1</span>
+ <span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.1"}'>a</span>
+ <span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.1"}'>b</span>
+ </div>
+ </div>
+ </div>
+
+ <div id="ref2">
+ <span template='{"type":"textContent","path":"title"}'>xxx</span>
+ <span title="xxx" template='{"type":"attribute","name":"title","path":"title"}'></span>
+ <span template='{"type":"localizedContent","paths":["foo2.foo_l10n","foo2.bar_l10n"],"property":"foo2"}'>foo2:yyy/zzz</span>
+ <div template-for='{"path":"","childSelector":"#template-for"}'></div>
+ <div template-loop='{"arrayPath":"foo1.bar1","childSelector":"#template-loop"}'>
+ <div>
+ <span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.0"}'>xx0</span>
+ <span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.0"}'>a</span>
+ <span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.0"}'>b</span>
+ </div>
+ <div>
+ <span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.1"}'>xx1</span>
+ <span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.1"}'>a</span>
+ <span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.1"}'>b</span>
+ </div>
+ </div>
+ </div>
+
+ <div id="ref3">
+ <span template='{"type":"textContent","path":"title"}'>xxx</span>
+ <span title="xxx" template='{"type":"attribute","name":"title","path":"title"}'></span>
+ <span template='{"type":"localizedContent","paths":["foo2.foo_l10n","foo2.bar_l10n"],"property":"foo2"}'>foo2:yyy/zzz</span>
+ <div template-for='{"path":"","childSelector":"#template-for"}'></div>
+ <div template-loop='{"arrayPath":"foo1.bar1","childSelector":"#template-loop"}'>
+ <div>
+ <span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.0"}'>xx0</span>
+ <span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.0"}'>a</span>
+ <span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.0"}'>b</span>
+ </div>
+ <div>
+ <span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.1"}'>xx1</span>
+ <span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.1"}'>a</span>
+ <span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.1"}'>b</span>
+ </div>
+ <div>
+ <span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.2"}'>xx2</span>
+ <span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.2"}'>a</span>
+ <span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.2"}'>b</span>
+ </div>
+ <div>
+ <span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.3"}'>xx3</span>
+ <span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.3"}'>a</span>
+ <span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.3"}'>b</span>
+ </div>
+ <div>
+ <span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.4"}'>xx4</span>
+ <span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.4"}'>a</span>
+ <span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.4"}'>b</span>
+ </div>
+ </div>
+ </div>
+
+ <div id="ref4">
+ <span template='{"type":"textContent","path":"title"}'>xxx</span>
+ <span title="xxx" template='{"type":"attribute","name":"title","path":"title"}'></span>
+ <span template='{"type":"localizedContent","paths":["foo2.foo_l10n","foo2.bar_l10n"],"property":"foo2"}'>foo2:yyy/zzz</span>
+ <div template-for='{"path":"","childSelector":"#template-for"}'></div>
+ <div template-loop='{"arrayPath":"foo1.bar1","childSelector":"#template-loop"}'>
+ <div>
+ <span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.0"}'>xx0</span>
+ <span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.0"}'>a</span>
+ <span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.0"}'>b</span>
+ </div>
+ <div>
+ <span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.1"}'>xx1</span>
+ <span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.1"}'>a</span>
+ <span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.1"}'>b</span>
+ </div>
+ <div>
+ <span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.2"}'>xx2</span>
+ <span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.2"}'>a</span>
+ <span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.2"}'>b</span>
+ </div>
+ <div>
+ <span template='{"type":"textContent","path":"idx","rootPath":"foo1.bar1.3"}'>xx3</span>
+ <span template='{"type":"textContent","path":"a","rootPath":"foo1.bar1.3"}'>a</span>
+ <span template='{"type":"textContent","path":"b","rootPath":"foo1.bar1.3"}'>b</span>
+ </div>
+ </div>
+ </div>
+
+
+
+ <template id="template-loop">
+ <div>
+ <span template='{"type":"textContent","path":"idx"}'></span>
+ <span template='{"type":"textContent","path":"a"}'></span>
+ <span template='{"type":"textContent","path":"b"}'></span>
+ </div>
+ </template>
+
+ <template id="template-for">
+ <span template='{"type":"textContent","path":"name"}'></span>
+ </template>
+
+ <script type="application/javascript;version=1.8" src="chrome://browser/content/devtools/app-manager/template.js"></script>
+ <script type="application/javascript;version=1.8">
+ SimpleTest.waitForExplicitFinish();
+
+ const Cu = Components.utils;
+ Cu.import("resource:///modules/devtools/gDevTools.jsm");
+ const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+ const {require} = devtools;
+ const ObservableObject = require("devtools/shared/observable-object");
+
+ let data = {
+ title: "ttt",
+ mop: {
+ name: "meh",
+ },
+ foo1: {
+ bar1: [
+ {idx: "xx0", a: "a", b: "b"},
+ {idx: "xx1", a: "a", b: "b"},
+ ],
+ },
+ foo2: {
+ foo_l10n: "foo_l10n",
+ bar_l10n: "bar_l10n"
+ },
+ };
+
+ let store = new ObservableObject(data);
+
+ let changes = [
+ {
+ exec: function() {},
+ reference: document.querySelector("#ref0")
+ },
+ {
+ exec: function() {
+ store.object.title = "xxx";
+ store.object.foo2.foo_l10n = "foo2_l10n";
+ store.object.mop.name = "meh2";
+ },
+ reference: document.querySelector("#ref1")
+ },
+ {
+ exec: function() {
+ store.object.foo2 = {
+ foo_l10n: "yyy",
+ bar_l10n: "zzz",
+ }
+ let forElt = document.querySelector("#root > [template-for]");
+ forElt.setAttribute("template-for", '{"path":"","childSelector":"#template-for"}');
+ t._processFor(forElt);
+ },
+ reference: document.querySelector("#ref2")
+ },
+ {
+ exec: function() {
+ let items = [];
+ for (let i = 2; i < 5; i++) {
+ items.push({idx: "xx" + i, a: "a", b: "b"});
+ }
+
+ store.object.foo1.bar1 = store.object.foo1.bar1.concat(items);
+ },
+ reference: document.querySelector("#ref3")
+ },
+ {
+ exec: function() {
+ store.object.foo1.bar1.pop();
+ },
+ reference: document.querySelector("#ref4")
+ },
+ ];
+
+ function compare(node1, node2) {
+ let text1 = node1.innerHTML;
+ let text2 = node2.innerHTML;
+ text1 = text1.replace(/\n/g,"");
+ text2 = text2.replace(/\n/g,"");
+ text1 = text1.replace(/\s+/g,"");
+ text2 = text2.replace(/\s+/g,"");
+ return text1 == text2;
+ }
+
+
+ let root = document.querySelector("#root");
+
+ let t = new Template(root, store, (prop, args) => {
+ return prop + ":" + args.join("/");
+ });
+
+ t.start();
+
+ for (let i = 0; i < changes.length; i++) {
+ let change = changes[i];
+ change.exec();
+ ok(compare(change.reference, root), "Content " + i + " looks good.");
+ }
+ SimpleTest.finish();
+
+ </script>
+</html>
diff --git a/toolkit/devtools/app-manager/test/validator/no-name-or-icon/home.html b/toolkit/devtools/app-manager/test/validator/no-name-or-icon/home.html
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/toolkit/devtools/app-manager/test/validator/no-name-or-icon/home.html
diff --git a/toolkit/devtools/app-manager/test/validator/no-name-or-icon/manifest.webapp b/toolkit/devtools/app-manager/test/validator/no-name-or-icon/manifest.webapp
new file mode 100644
index 000000000..149e3fb79
--- /dev/null
+++ b/toolkit/devtools/app-manager/test/validator/no-name-or-icon/manifest.webapp
@@ -0,0 +1,3 @@
+{
+ "launch_path": "/home.html"
+}
diff --git a/toolkit/devtools/app-manager/test/validator/non-absolute-path/manifest.webapp b/toolkit/devtools/app-manager/test/validator/non-absolute-path/manifest.webapp
new file mode 100644
index 000000000..64744067f
--- /dev/null
+++ b/toolkit/devtools/app-manager/test/validator/non-absolute-path/manifest.webapp
@@ -0,0 +1,7 @@
+{
+ "name": "non-absolute path",
+ "icons": {
+ "128": "/icon.png"
+ },
+ "launch_path": "non-absolute.html"
+}
diff --git a/toolkit/devtools/app-manager/test/validator/valid/alsoValid/manifest.webapp b/toolkit/devtools/app-manager/test/validator/valid/alsoValid/manifest.webapp
new file mode 100644
index 000000000..20bd97bba
--- /dev/null
+++ b/toolkit/devtools/app-manager/test/validator/valid/alsoValid/manifest.webapp
@@ -0,0 +1,7 @@
+{
+ "name": "valid at subfolder",
+ "launch_path": "/home.html",
+ "icons": {
+ "128": "/icon.png"
+ }
+}
diff --git a/toolkit/devtools/app-manager/test/validator/valid/home.html b/toolkit/devtools/app-manager/test/validator/valid/home.html
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/toolkit/devtools/app-manager/test/validator/valid/home.html
diff --git a/toolkit/devtools/app-manager/test/validator/valid/icon.png b/toolkit/devtools/app-manager/test/validator/valid/icon.png
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/toolkit/devtools/app-manager/test/validator/valid/icon.png
diff --git a/toolkit/devtools/app-manager/test/validator/valid/manifest.webapp b/toolkit/devtools/app-manager/test/validator/valid/manifest.webapp
new file mode 100644
index 000000000..2c22a1567
--- /dev/null
+++ b/toolkit/devtools/app-manager/test/validator/valid/manifest.webapp
@@ -0,0 +1,7 @@
+{
+ "name": "valid",
+ "launch_path": "/home.html",
+ "icons": {
+ "128": "/icon.png"
+ }
+}
diff --git a/toolkit/devtools/app-manager/test/validator/wrong-launch-path/icon.png b/toolkit/devtools/app-manager/test/validator/wrong-launch-path/icon.png
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/toolkit/devtools/app-manager/test/validator/wrong-launch-path/icon.png
diff --git a/toolkit/devtools/app-manager/test/validator/wrong-launch-path/manifest.webapp b/toolkit/devtools/app-manager/test/validator/wrong-launch-path/manifest.webapp
new file mode 100644
index 000000000..08057bae1
--- /dev/null
+++ b/toolkit/devtools/app-manager/test/validator/wrong-launch-path/manifest.webapp
@@ -0,0 +1,7 @@
+{
+ "name": "valid",
+ "launch_path": "/wrong-path.html",
+ "icons": {
+ "128": "/icon.png"
+ }
+}