summaryrefslogtreecommitdiffstats
path: root/devtools/client/projecteditor/test
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /devtools/client/projecteditor/test
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'devtools/client/projecteditor/test')
-rw-r--r--devtools/client/projecteditor/test/.eslintrc.js6
-rw-r--r--devtools/client/projecteditor/test/browser.ini31
-rw-r--r--devtools/client/projecteditor/test/browser_projecteditor_app_options.js87
-rw-r--r--devtools/client/projecteditor/test/browser_projecteditor_confirm_unsaved.js60
-rw-r--r--devtools/client/projecteditor/test/browser_projecteditor_contextmenu_01.js27
-rw-r--r--devtools/client/projecteditor/test/browser_projecteditor_contextmenu_02.js66
-rw-r--r--devtools/client/projecteditor/test/browser_projecteditor_delete_file.js85
-rw-r--r--devtools/client/projecteditor/test/browser_projecteditor_editing_01.js70
-rw-r--r--devtools/client/projecteditor/test/browser_projecteditor_editors_image.js74
-rw-r--r--devtools/client/projecteditor/test/browser_projecteditor_external_change.js84
-rw-r--r--devtools/client/projecteditor/test/browser_projecteditor_immediate_destroy.js93
-rw-r--r--devtools/client/projecteditor/test/browser_projecteditor_init.js18
-rw-r--r--devtools/client/projecteditor/test/browser_projecteditor_menubar_01.js28
-rw-r--r--devtools/client/projecteditor/test/browser_projecteditor_menubar_02.js123
-rw-r--r--devtools/client/projecteditor/test/browser_projecteditor_new_file.js13
-rw-r--r--devtools/client/projecteditor/test/browser_projecteditor_rename_file_01.js19
-rw-r--r--devtools/client/projecteditor/test/browser_projecteditor_rename_file_02.js26
-rw-r--r--devtools/client/projecteditor/test/browser_projecteditor_saveall.js64
-rw-r--r--devtools/client/projecteditor/test/browser_projecteditor_stores.js16
-rw-r--r--devtools/client/projecteditor/test/browser_projecteditor_tree_selection_01.js98
-rw-r--r--devtools/client/projecteditor/test/browser_projecteditor_tree_selection_02.js76
-rw-r--r--devtools/client/projecteditor/test/head.js391
-rw-r--r--devtools/client/projecteditor/test/helper_edits.js53
-rw-r--r--devtools/client/projecteditor/test/helper_homepage.html1
24 files changed, 1609 insertions, 0 deletions
diff --git a/devtools/client/projecteditor/test/.eslintrc.js b/devtools/client/projecteditor/test/.eslintrc.js
new file mode 100644
index 000000000..8d15a76d9
--- /dev/null
+++ b/devtools/client/projecteditor/test/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+ // Extend from the shared list of defined globals for mochitests.
+ "extends": "../../../.eslintrc.mochitests.js"
+};
diff --git a/devtools/client/projecteditor/test/browser.ini b/devtools/client/projecteditor/test/browser.ini
new file mode 100644
index 000000000..e7fdc7ae5
--- /dev/null
+++ b/devtools/client/projecteditor/test/browser.ini
@@ -0,0 +1,31 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+ head.js
+ helper_homepage.html
+ helper_edits.js
+
+[browser_projecteditor_app_options.js]
+[browser_projecteditor_confirm_unsaved.js]
+[browser_projecteditor_contextmenu_01.js]
+skip-if = asan # Bug 1083140
+[browser_projecteditor_contextmenu_02.js]
+skip-if = true # Bug 1173950
+[browser_projecteditor_delete_file.js]
+skip-if = e10s # Frequent failures in e10s - Bug 1020027
+[browser_projecteditor_rename_file_01.js]
+[browser_projecteditor_rename_file_02.js]
+[browser_projecteditor_editing_01.js]
+[browser_projecteditor_editors_image.js]
+[browser_projecteditor_external_change.js]
+[browser_projecteditor_immediate_destroy.js]
+[browser_projecteditor_init.js]
+[browser_projecteditor_menubar_01.js]
+[browser_projecteditor_menubar_02.js]
+skip-if = true # Bug 1173950
+[browser_projecteditor_new_file.js]
+[browser_projecteditor_saveall.js]
+[browser_projecteditor_stores.js]
+[browser_projecteditor_tree_selection_01.js]
+[browser_projecteditor_tree_selection_02.js]
diff --git a/devtools/client/projecteditor/test/browser_projecteditor_app_options.js b/devtools/client/projecteditor/test/browser_projecteditor_app_options.js
new file mode 100644
index 000000000..aa608e205
--- /dev/null
+++ b/devtools/client/projecteditor/test/browser_projecteditor_app_options.js
@@ -0,0 +1,87 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that options can be changed without resetting the whole
+// editor.
+add_task(function* () {
+
+ let TEMP_PATH = buildTempDirectoryStructure();
+ let projecteditor = yield addProjectEditorTab();
+
+ let resourceBeenAdded = promise.defer();
+ projecteditor.project.once("resource-added", () => {
+ info("A resource has been added");
+ resourceBeenAdded.resolve();
+ });
+
+ info("About to set project to: " + TEMP_PATH);
+ yield projecteditor.setProjectToAppPath(TEMP_PATH, {
+ name: "Test",
+ iconUrl: "chrome://devtools/skin/images/tool-options.svg",
+ projectOverviewURL: SAMPLE_WEBAPP_URL
+ });
+
+ info("Making sure a resource has been added before continuing");
+ yield resourceBeenAdded.promise;
+
+ info("From now on, if a resource is added it should fail");
+ projecteditor.project.on("resource-added", failIfResourceAdded);
+
+ info("Getting ahold and validating the project header DOM");
+ let header = projecteditor.document.querySelector(".entry-group-title");
+ let image = header.querySelector(".project-image");
+ let nameLabel = header.querySelector(".project-name-label");
+ let statusElement = header.querySelector(".project-status");
+ is(statusElement.getAttribute("status"), "unknown", "The status starts out as unknown.");
+ is(nameLabel.textContent, "Test", "The name label has been set correctly");
+ is(image.getAttribute("src"), "chrome://devtools/skin/images/tool-options.svg", "The icon has been set correctly");
+
+ info("About to set project with new options.");
+ yield projecteditor.setProjectToAppPath(TEMP_PATH, {
+ name: "Test2",
+ iconUrl: "chrome://devtools/skin/images/tool-inspector.svg",
+ projectOverviewURL: SAMPLE_WEBAPP_URL,
+ validationStatus: "error"
+ });
+
+ info("Getting ahold of and validating the project header DOM");
+ is(statusElement.getAttribute("status"), "error", "The status has been set correctly.");
+ is(nameLabel.textContent, "Test2", "The name label has been set correctly");
+ is(image.getAttribute("src"), "chrome://devtools/skin/images/tool-inspector.svg", "The icon has been set correctly");
+
+ info("About to set project with new options.");
+ yield projecteditor.setProjectToAppPath(TEMP_PATH, {
+ name: "Test3",
+ iconUrl: "chrome://devtools/skin/images/tool-webconsole.svg",
+ projectOverviewURL: SAMPLE_WEBAPP_URL,
+ validationStatus: "warning"
+ });
+
+ info("Getting ahold of and validating the project header DOM");
+ is(statusElement.getAttribute("status"), "warning", "The status has been set correctly.");
+ is(nameLabel.textContent, "Test3", "The name label has been set correctly");
+ is(image.getAttribute("src"), "chrome://devtools/skin/images/tool-webconsole.svg", "The icon has been set correctly");
+
+ info("About to set project with new options.");
+ yield projecteditor.setProjectToAppPath(TEMP_PATH, {
+ name: "Test4",
+ iconUrl: "chrome://devtools/skin/images/tool-debugger.svg",
+ projectOverviewURL: SAMPLE_WEBAPP_URL,
+ validationStatus: "valid"
+ });
+
+ info("Getting ahold of and validating the project header DOM");
+ is(statusElement.getAttribute("status"), "valid", "The status has been set correctly.");
+ is(nameLabel.textContent, "Test4", "The name label has been set correctly");
+ is(image.getAttribute("src"), "chrome://devtools/skin/images/tool-debugger.svg", "The icon has been set correctly");
+
+ info("Test finished, cleaning up");
+ projecteditor.project.off("resource-added", failIfResourceAdded);
+});
+
+function failIfResourceAdded() {
+ ok(false, "A resource has been added, but it shouldn't have been");
+}
diff --git a/devtools/client/projecteditor/test/browser_projecteditor_confirm_unsaved.js b/devtools/client/projecteditor/test/browser_projecteditor_confirm_unsaved.js
new file mode 100644
index 000000000..72640d243
--- /dev/null
+++ b/devtools/client/projecteditor/test/browser_projecteditor_confirm_unsaved.js
@@ -0,0 +1,60 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+loadHelperScript("helper_edits.js");
+
+// Test that a prompt shows up when requested if a file is unsaved.
+add_task(function* () {
+ let projecteditor = yield addProjectEditorTabForTempDirectory();
+ ok(true, "ProjectEditor has loaded");
+
+ let resources = projecteditor.project.allResources();
+ yield selectFile(projecteditor, resources[2]);
+ let editor = projecteditor.currentEditor;
+ let originalText = editor.editor.getText();
+
+ ok(!projecteditor.hasUnsavedResources, "There are no unsaved resources");
+ ok(projecteditor.confirmUnsaved(), "When there are no unsaved changes, confirmUnsaved() is true");
+ editor.editor.setText("bar");
+ editor.editor.setText(originalText);
+ ok(!projecteditor.hasUnsavedResources, "There are no unsaved resources");
+ ok(projecteditor.confirmUnsaved(), "When an editor has changed but is still the original text, confirmUnsaved() is true");
+
+ editor.editor.setText("bar");
+
+ checkConfirmYes(projecteditor);
+ checkConfirmNo(projecteditor);
+});
+
+function checkConfirmYes(projecteditor, container) {
+ function confirmYes(aSubject) {
+ info("confirm dialog observed as expected, going to click OK");
+ Services.obs.removeObserver(confirmYes, "common-dialog-loaded");
+ Services.obs.removeObserver(confirmYes, "tabmodal-dialog-loaded");
+ aSubject.Dialog.ui.button0.click();
+ }
+
+ Services.obs.addObserver(confirmYes, "common-dialog-loaded", false);
+ Services.obs.addObserver(confirmYes, "tabmodal-dialog-loaded", false);
+
+ ok(projecteditor.hasUnsavedResources, "There are unsaved resources");
+ ok(projecteditor.confirmUnsaved(), "When there are unsaved changes, clicking OK makes confirmUnsaved() true");
+}
+
+function checkConfirmNo(projecteditor, container) {
+ function confirmNo(aSubject) {
+ info("confirm dialog observed as expected, going to click cancel");
+ Services.obs.removeObserver(confirmNo, "common-dialog-loaded");
+ Services.obs.removeObserver(confirmNo, "tabmodal-dialog-loaded");
+ aSubject.Dialog.ui.button1.click();
+ }
+
+ Services.obs.addObserver(confirmNo, "common-dialog-loaded", false);
+ Services.obs.addObserver(confirmNo, "tabmodal-dialog-loaded", false);
+
+ ok(projecteditor.hasUnsavedResources, "There are unsaved resources");
+ ok(!projecteditor.confirmUnsaved(), "When there are unsaved changes, clicking cancel makes confirmUnsaved() false");
+}
diff --git a/devtools/client/projecteditor/test/browser_projecteditor_contextmenu_01.js b/devtools/client/projecteditor/test/browser_projecteditor_contextmenu_01.js
new file mode 100644
index 000000000..44ffe1722
--- /dev/null
+++ b/devtools/client/projecteditor/test/browser_projecteditor_contextmenu_01.js
@@ -0,0 +1,27 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that context menus append to the correct document.
+
+add_task(function* () {
+ let projecteditor = yield addProjectEditorTabForTempDirectory({
+ menubar: false
+ });
+ ok(projecteditor, "ProjectEditor has loaded");
+
+ let contextMenuPopup = projecteditor.document.querySelector("#context-menu-popup");
+ let textEditorContextMenuPopup = projecteditor.document.querySelector("#texteditor-context-popup");
+ ok(contextMenuPopup, "The menu has loaded in the projecteditor document");
+ ok(textEditorContextMenuPopup, "The menu has loaded in the projecteditor document");
+
+ let projecteditor2 = yield addProjectEditorTabForTempDirectory();
+ contextMenuPopup = projecteditor2.document.getElementById("context-menu-popup");
+ textEditorContextMenuPopup = projecteditor2.document.getElementById("texteditor-context-popup");
+ ok(!contextMenuPopup, "The menu has NOT loaded in the projecteditor document");
+ ok(!textEditorContextMenuPopup, "The menu has NOT loaded in the projecteditor document");
+ ok(content.document.querySelector("#context-menu-popup"), "The menu has loaded in the specified element");
+ ok(content.document.querySelector("#texteditor-context-popup"), "The menu has loaded in the specified element");
+});
diff --git a/devtools/client/projecteditor/test/browser_projecteditor_contextmenu_02.js b/devtools/client/projecteditor/test/browser_projecteditor_contextmenu_02.js
new file mode 100644
index 000000000..cf43b3e21
--- /dev/null
+++ b/devtools/client/projecteditor/test/browser_projecteditor_contextmenu_02.js
@@ -0,0 +1,66 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+loadHelperScript("helper_edits.js");
+
+// Test context menu enabled / disabled state in editor
+
+add_task(function* () {
+ let projecteditor = yield addProjectEditorTabForTempDirectory();
+ ok(projecteditor, "ProjectEditor has loaded");
+
+ let {textEditorContextMenuPopup} = projecteditor;
+
+ // Update menu items for a clean slate, so previous tests cannot
+ // affect paste, and possibly other side effects
+ projecteditor._updateMenuItems();
+
+ let cmdDelete = textEditorContextMenuPopup.querySelector("[command=cmd_delete]");
+ let cmdSelectAll = textEditorContextMenuPopup.querySelector("[command=cmd_selectAll]");
+ let cmdCut = textEditorContextMenuPopup.querySelector("[command=cmd_cut]");
+ let cmdCopy = textEditorContextMenuPopup.querySelector("[command=cmd_copy]");
+ let cmdPaste = textEditorContextMenuPopup.querySelector("[command=cmd_paste]");
+
+ info("Opening resource");
+ let resource = projecteditor.project.allResources()[2];
+ yield selectFile(projecteditor, resource);
+ let editor = projecteditor.currentEditor;
+ editor.editor.focus();
+
+ info("Opening context menu on resource");
+ yield openContextMenuForEditor(editor, textEditorContextMenuPopup);
+
+ is(cmdDelete.getAttribute("disabled"), "true", "cmdDelete is disabled");
+ is(cmdSelectAll.getAttribute("disabled"), "", "cmdSelectAll is enabled");
+ is(cmdCut.getAttribute("disabled"), "true", "cmdCut is disabled");
+ is(cmdCopy.getAttribute("disabled"), "true", "cmdCopy is disabled");
+ is(cmdPaste.getAttribute("disabled"), "", "cmdPaste is enabled");
+
+ info("Setting a selection and repening context menu on resource");
+ yield closeContextMenuForEditor(editor, textEditorContextMenuPopup);
+ editor.editor.setSelection({line: 0, ch: 0}, {line: 0, ch: 2});
+ yield openContextMenuForEditor(editor, textEditorContextMenuPopup);
+
+ is(cmdDelete.getAttribute("disabled"), "", "cmdDelete is enabled");
+ is(cmdSelectAll.getAttribute("disabled"), "", "cmdSelectAll is enabled");
+ is(cmdCut.getAttribute("disabled"), "", "cmdCut is enabled");
+ is(cmdCopy.getAttribute("disabled"), "", "cmdCopy is enabled");
+ is(cmdPaste.getAttribute("disabled"), "", "cmdPaste is enabled");
+});
+
+function* openContextMenuForEditor(editor, contextMenu) {
+ let editorDoc = editor.editor.container.contentDocument;
+ let shown = onPopupShow(contextMenu);
+ EventUtils.synthesizeMouse(editorDoc.body, 2, 2,
+ {type: "contextmenu", button: 2}, editorDoc.defaultView);
+ yield shown;
+}
+function* closeContextMenuForEditor(editor, contextMenu) {
+ let editorDoc = editor.editor.container.contentDocument;
+ let hidden = onPopupHidden(contextMenu);
+ contextMenu.hidePopup();
+ yield hidden;
+}
diff --git a/devtools/client/projecteditor/test/browser_projecteditor_delete_file.js b/devtools/client/projecteditor/test/browser_projecteditor_delete_file.js
new file mode 100644
index 000000000..446c1dbcb
--- /dev/null
+++ b/devtools/client/projecteditor/test/browser_projecteditor_delete_file.js
@@ -0,0 +1,85 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test tree selection functionality
+
+add_task(function* () {
+ let projecteditor = yield addProjectEditorTabForTempDirectory();
+ ok(true, "ProjectEditor has loaded");
+
+ let root = [...projecteditor.project.allStores()][0].root;
+ is(root.path, TEMP_PATH, "The root store is set to the correct temp path.");
+ for (let child of root.children) {
+ yield deleteWithContextMenu(projecteditor, projecteditor.projectTree.getViewContainer(child));
+ }
+
+ yield testDeleteOnRoot(projecteditor, projecteditor.projectTree.getViewContainer(root));
+});
+
+
+function openContextMenuOn(node) {
+ EventUtils.synthesizeMouseAtCenter(
+ node,
+ {button: 2, type: "contextmenu"},
+ node.ownerDocument.defaultView
+ );
+}
+
+function* testDeleteOnRoot(projecteditor, container) {
+ let popup = projecteditor.contextMenuPopup;
+ let oncePopupShown = onPopupShow(popup);
+ openContextMenuOn(container.label);
+ yield oncePopupShown;
+
+ let deleteCommand = popup.querySelector("[command=cmd-delete]");
+ ok(deleteCommand, "Delete command exists in popup");
+ is(deleteCommand.getAttribute("hidden"), "true", "Delete command is hidden");
+}
+
+function deleteWithContextMenu(projecteditor, container) {
+ let defer = promise.defer();
+
+ let popup = projecteditor.contextMenuPopup;
+ let resource = container.resource;
+ info("Going to attempt deletion for: " + resource.path);
+
+ onPopupShow(popup).then(function () {
+ let deleteCommand = popup.querySelector("[command=cmd-delete]");
+ ok(deleteCommand, "Delete command exists in popup");
+ is(deleteCommand.getAttribute("hidden"), "", "Delete command is visible");
+ is(deleteCommand.getAttribute("disabled"), "", "Delete command is enabled");
+
+ function onConfirmShown(aSubject) {
+ info("confirm dialog observed as expected");
+ Services.obs.removeObserver(onConfirmShown, "common-dialog-loaded");
+ Services.obs.removeObserver(onConfirmShown, "tabmodal-dialog-loaded");
+
+ projecteditor.project.on("refresh-complete", function refreshComplete() {
+ projecteditor.project.off("refresh-complete", refreshComplete);
+ OS.File.stat(resource.path).then(() => {
+ ok(false, "The file was not deleted");
+ defer.resolve();
+ }, (ex) => {
+ ok(ex instanceof OS.File.Error && ex.becauseNoSuchFile, "OS.File.stat promise was rejected because the file is gone");
+ defer.resolve();
+ });
+ });
+
+ // Click the 'OK' button
+ aSubject.Dialog.ui.button0.click();
+ }
+
+ Services.obs.addObserver(onConfirmShown, "common-dialog-loaded", false);
+ Services.obs.addObserver(onConfirmShown, "tabmodal-dialog-loaded", false);
+
+ deleteCommand.click();
+ popup.hidePopup();
+ });
+
+ openContextMenuOn(container.label);
+
+ return defer.promise;
+}
diff --git a/devtools/client/projecteditor/test/browser_projecteditor_editing_01.js b/devtools/client/projecteditor/test/browser_projecteditor_editing_01.js
new file mode 100644
index 000000000..c7ff1c0be
--- /dev/null
+++ b/devtools/client/projecteditor/test/browser_projecteditor_editing_01.js
@@ -0,0 +1,70 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("destroy");
+
+loadHelperScript("helper_edits.js");
+
+// Test ProjectEditor basic functionality
+add_task(function* () {
+ let projecteditor = yield addProjectEditorTabForTempDirectory();
+ let TEMP_PATH = projecteditor.project.allPaths()[0];
+
+ is(getTempFile("").path, TEMP_PATH, "Temp path is set correctly.");
+
+ ok(projecteditor.currentEditor, "There is an editor for projecteditor");
+ let resources = projecteditor.project.allResources();
+
+ for (let data of helperEditData) {
+ info("Processing " + data.path);
+ let resource = resources.filter(r=>r.basename === data.basename)[0];
+ yield selectFile(projecteditor, resource);
+ yield testEditFile(projecteditor, getTempFile(data.path).path, data.newContent);
+ }
+});
+
+function* testEditFile(projecteditor, filePath, newData) {
+ info("Testing file editing for: " + filePath);
+
+ let initialData = yield getFileData(filePath);
+ let editor = projecteditor.currentEditor;
+ let resource = projecteditor.resourceFor(editor);
+ let viewContainer = projecteditor.projectTree.getViewContainer(resource);
+ let originalTreeLabel = viewContainer.label.textContent;
+
+ is(resource.path, filePath, "Resource path is set correctly");
+ is(editor.editor.getText(), initialData, "Editor is loaded with correct file contents");
+
+ info("Setting text in the editor and doing checks before saving");
+
+ editor.editor.undo();
+ editor.editor.undo();
+ is(editor.editor.getText(), initialData, "Editor is still loaded with correct contents after undo");
+
+ editor.editor.setText(newData);
+ is(editor.editor.getText(), newData, "Editor has been filled with new data");
+ is(viewContainer.label.textContent, "*" + originalTreeLabel, "Label is marked as changed");
+
+ info("Saving the editor and checking to make sure the file gets saved on disk");
+
+ editor.save(resource);
+
+ let savedResource = yield onceEditorSave(projecteditor);
+
+ is(viewContainer.label.textContent, originalTreeLabel, "Label is unmarked as changed");
+ is(savedResource.path, filePath, "The saved resouce path matches the original file path");
+ is(savedResource, resource, "The saved resource is the same as the original resource");
+
+ let savedData = yield getFileData(filePath);
+ is(savedData, newData, "Data has been correctly saved to disk");
+
+ info("Finished checking saving for " + filePath);
+
+}
diff --git a/devtools/client/projecteditor/test/browser_projecteditor_editors_image.js b/devtools/client/projecteditor/test/browser_projecteditor_editors_image.js
new file mode 100644
index 000000000..0b19cb5d1
--- /dev/null
+++ b/devtools/client/projecteditor/test/browser_projecteditor_editors_image.js
@@ -0,0 +1,74 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("destroy");
+
+loadHelperScript("helper_edits.js");
+
+// Test ProjectEditor image editor functionality
+add_task(function* () {
+ let projecteditor = yield addProjectEditorTabForTempDirectory();
+ let TEMP_PATH = projecteditor.project.allPaths()[0];
+
+ is(getTempFile("").path, TEMP_PATH, "Temp path is set correctly.");
+
+ ok(projecteditor.currentEditor, "There is an editor for projecteditor");
+ let resources = projecteditor.project.allResources();
+
+ let helperImageData = [
+ {
+ basename: "16x16.png",
+ path: "img/icons/16x16.png"
+ },
+ {
+ basename: "32x32.png",
+ path: "img/icons/32x32.png"
+ },
+ {
+ basename: "128x128.png",
+ path: "img/icons/128x128.png"
+ },
+ ];
+
+ for (let data of helperImageData) {
+ info("Processing " + data.path);
+ let resource = resources.filter(r=>r.basename === data.basename)[0];
+ yield selectFile(projecteditor, resource);
+ yield testEditor(projecteditor, getTempFile(data.path).path);
+ }
+});
+
+function* testEditor(projecteditor, filePath) {
+ info("Testing file editing for: " + filePath);
+
+ let editor = projecteditor.currentEditor;
+ let resource = projecteditor.resourceFor(editor);
+
+ is(resource.path, filePath, "Resource path is set correctly");
+
+ let images = editor.elt.querySelectorAll("image");
+ is(images.length, 1, "There is one image inside the editor");
+ is(images[0], editor.image, "The image property is set correctly with the DOM");
+ is(editor.image.getAttribute("src"), resource.uri, "The image has the resource URL");
+
+ info("Selecting another resource, then reselecting this one");
+ projecteditor.projectTree.selectResource(resource.store.root);
+ yield onceEditorActivated(projecteditor);
+ projecteditor.projectTree.selectResource(resource);
+ yield onceEditorActivated(projecteditor);
+
+ editor = projecteditor.currentEditor;
+ images = editor.elt.querySelectorAll("image");
+ ok(images.length, 1, "There is one image inside the editor");
+ is(images[0], editor.image, "The image property is set correctly with the DOM");
+ is(editor.image.getAttribute("src"), resource.uri, "The image has the resource URL");
+
+ info("Finished checking saving for " + filePath);
+}
diff --git a/devtools/client/projecteditor/test/browser_projecteditor_external_change.js b/devtools/client/projecteditor/test/browser_projecteditor_external_change.js
new file mode 100644
index 000000000..12d90a869
--- /dev/null
+++ b/devtools/client/projecteditor/test/browser_projecteditor_external_change.js
@@ -0,0 +1,84 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+loadHelperScript("helper_edits.js");
+
+// Test ProjectEditor reaction to external changes (made outside of the)
+// editor.
+add_task(function* () {
+ let projecteditor = yield addProjectEditorTabForTempDirectory();
+ let TEMP_PATH = projecteditor.project.allPaths()[0];
+
+ is(getTempFile("").path, TEMP_PATH, "Temp path is set correctly.");
+
+ ok(projecteditor.currentEditor, "There is an editor for projecteditor");
+ let resources = projecteditor.project.allResources();
+
+ for (let data of helperEditData) {
+ info("Processing " + data.path);
+ let resource = resources.filter(r=>r.basename === data.basename)[0];
+ yield selectFile(projecteditor, resource);
+ yield testChangeFileExternally(projecteditor, getTempFile(data.path).path, data.newContent);
+ yield testChangeUnsavedFileExternally(projecteditor, getTempFile(data.path).path, data.newContent + "[changed]");
+ }
+});
+
+function* testChangeUnsavedFileExternally(projecteditor, filePath, newData) {
+ info("Testing file external changes for: " + filePath);
+
+ let editor = projecteditor.currentEditor;
+ let resource = projecteditor.resourceFor(editor);
+ let initialData = yield getFileData(filePath);
+
+ is(resource.path, filePath, "Resource path is set correctly");
+ is(editor.editor.getText(), initialData, "Editor is loaded with correct file contents");
+
+ info("Editing but not saving file in project editor");
+ ok(editor.isClean(), "Editor is clean");
+ editor.editor.setText("foobar");
+ ok(!editor.isClean(), "Editor is dirty");
+
+ info("Editor has been selected, writing to file externally");
+ yield writeToFile(resource.path, newData);
+
+ info("Selecting another resource, then reselecting this one");
+ projecteditor.projectTree.selectResource(resource.store.root);
+ yield onceEditorActivated(projecteditor);
+ projecteditor.projectTree.selectResource(resource);
+ yield onceEditorActivated(projecteditor);
+
+ editor = projecteditor.currentEditor;
+ info("Checking to make sure the editor is now populated correctly");
+ is(editor.editor.getText(), "foobar", "Editor has not been updated with new file contents");
+
+ info("Finished checking saving for " + filePath);
+}
+
+function* testChangeFileExternally(projecteditor, filePath, newData) {
+ info("Testing file external changes for: " + filePath);
+
+ let editor = projecteditor.currentEditor;
+ let resource = projecteditor.resourceFor(editor);
+ let initialData = yield getFileData(filePath);
+
+ is(resource.path, filePath, "Resource path is set correctly");
+ is(editor.editor.getText(), initialData, "Editor is loaded with correct file contents");
+
+ info("Editor has been selected, writing to file externally");
+ yield writeToFile(resource.path, newData);
+
+ info("Selecting another resource, then reselecting this one");
+ projecteditor.projectTree.selectResource(resource.store.root);
+ yield onceEditorActivated(projecteditor);
+ projecteditor.projectTree.selectResource(resource);
+ yield onceEditorActivated(projecteditor);
+
+ editor = projecteditor.currentEditor;
+ info("Checking to make sure the editor is now populated correctly");
+ is(editor.editor.getText(), newData, "Editor has been updated with correct file contents");
+
+ info("Finished checking saving for " + filePath);
+}
diff --git a/devtools/client/projecteditor/test/browser_projecteditor_immediate_destroy.js b/devtools/client/projecteditor/test/browser_projecteditor_immediate_destroy.js
new file mode 100644
index 000000000..0773be55c
--- /dev/null
+++ b/devtools/client/projecteditor/test/browser_projecteditor_immediate_destroy.js
@@ -0,0 +1,93 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("destroy");
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: this.window is null");
+
+// Test that projecteditor can be destroyed in various states of loading
+// without causing any leaks or exceptions.
+
+add_task(function* () {
+
+ info("Testing tab closure when projecteditor is in various states");
+ let loaderUrl = "chrome://devtools/content/projecteditor/chrome/content/projecteditor-test.xul";
+
+ yield addTab(loaderUrl).then(() => {
+ let iframe = content.document.getElementById("projecteditor-iframe");
+ ok(iframe, "Tab has placeholder iframe for projecteditor");
+
+ info("Closing the tab without doing anything");
+ gBrowser.removeCurrentTab();
+ });
+
+ yield addTab(loaderUrl).then(() => {
+ let iframe = content.document.getElementById("projecteditor-iframe");
+ ok(iframe, "Tab has placeholder iframe for projecteditor");
+
+ let projecteditor = ProjectEditor.ProjectEditor();
+ ok(projecteditor, "ProjectEditor has been initialized");
+
+ info("Closing the tab before attempting to load");
+ gBrowser.removeCurrentTab();
+ });
+
+ yield addTab(loaderUrl).then(() => {
+ let iframe = content.document.getElementById("projecteditor-iframe");
+ ok(iframe, "Tab has placeholder iframe for projecteditor");
+
+ let projecteditor = ProjectEditor.ProjectEditor();
+ ok(projecteditor, "ProjectEditor has been initialized");
+
+ projecteditor.load(iframe);
+
+ info("Closing the tab after a load is requested, but before load is finished");
+ gBrowser.removeCurrentTab();
+ });
+
+ yield addTab(loaderUrl).then(() => {
+ let iframe = content.document.getElementById("projecteditor-iframe");
+ ok(iframe, "Tab has placeholder iframe for projecteditor");
+
+ let projecteditor = ProjectEditor.ProjectEditor();
+ ok(projecteditor, "ProjectEditor has been initialized");
+
+ return projecteditor.load(iframe).then(() => {
+ info("Closing the tab after a load has been requested and finished");
+ gBrowser.removeCurrentTab();
+ });
+ });
+
+ yield addTab(loaderUrl).then(() => {
+ let iframe = content.document.getElementById("projecteditor-iframe");
+ ok(iframe, "Tab has placeholder iframe for projecteditor");
+
+ let projecteditor = ProjectEditor.ProjectEditor(iframe);
+ ok(projecteditor, "ProjectEditor has been initialized");
+
+ let loadedDone = promise.defer();
+ projecteditor.loaded.then(() => {
+ ok(false, "Loaded has finished after destroy() has been called");
+ loadedDone.resolve();
+ }, () => {
+ ok(true, "Loaded has been rejected after destroy() has been called");
+ loadedDone.resolve();
+ });
+
+ projecteditor.destroy();
+
+ return loadedDone.promise.then(() => {
+ gBrowser.removeCurrentTab();
+ });
+ });
+
+ finish();
+});
+
+
diff --git a/devtools/client/projecteditor/test/browser_projecteditor_init.js b/devtools/client/projecteditor/test/browser_projecteditor_init.js
new file mode 100644
index 000000000..3ee947e0d
--- /dev/null
+++ b/devtools/client/projecteditor/test/browser_projecteditor_init.js
@@ -0,0 +1,18 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that projecteditor can be initialized.
+
+function test() {
+ info("Initializing projecteditor");
+ addProjectEditorTab().then((projecteditor) => {
+ ok(projecteditor, "Load callback has been called");
+ ok(projecteditor.shells, "ProjectEditor has shells");
+ ok(projecteditor.project, "ProjectEditor has a project");
+ finish();
+ });
+}
+
diff --git a/devtools/client/projecteditor/test/browser_projecteditor_menubar_01.js b/devtools/client/projecteditor/test/browser_projecteditor_menubar_01.js
new file mode 100644
index 000000000..1641169e7
--- /dev/null
+++ b/devtools/client/projecteditor/test/browser_projecteditor_menubar_01.js
@@ -0,0 +1,28 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that menu bar appends to the correct document.
+
+add_task(function* () {
+ let projecteditor = yield addProjectEditorTabForTempDirectory({
+ menubar: false
+ });
+ ok(projecteditor, "ProjectEditor has loaded");
+
+ let fileMenu = projecteditor.document.getElementById("file-menu");
+ let editMenu = projecteditor.document.getElementById("edit-menu");
+ ok(fileMenu, "The menu has loaded in the projecteditor document");
+ ok(editMenu, "The menu has loaded in the projecteditor document");
+
+ let projecteditor2 = yield addProjectEditorTabForTempDirectory();
+ let menubar = projecteditor2.menubar;
+ fileMenu = projecteditor2.document.getElementById("file-menu");
+ editMenu = projecteditor2.document.getElementById("edit-menu");
+ ok(!fileMenu, "The menu has NOT loaded in the projecteditor document");
+ ok(!editMenu, "The menu has NOT loaded in the projecteditor document");
+ ok(content.document.querySelector("#file-menu"), "The menu has loaded in the specified element");
+ ok(content.document.querySelector("#edit-menu"), "The menu has loaded in the specified element");
+});
diff --git a/devtools/client/projecteditor/test/browser_projecteditor_menubar_02.js b/devtools/client/projecteditor/test/browser_projecteditor_menubar_02.js
new file mode 100644
index 000000000..d0d41f743
--- /dev/null
+++ b/devtools/client/projecteditor/test/browser_projecteditor_menubar_02.js
@@ -0,0 +1,123 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+loadHelperScript("helper_edits.js");
+
+// Test menu bar enabled / disabled state.
+
+add_task(function* () {
+ let projecteditor = yield addProjectEditorTabForTempDirectory();
+ let menubar = projecteditor.menubar;
+
+ // Update menu items for a clean slate, so previous tests cannot
+ // affect paste, and possibly other side effects
+ projecteditor._updateMenuItems();
+
+ // let projecteditor = yield addProjectEditorTabForTempDirectory();
+ ok(projecteditor, "ProjectEditor has loaded");
+
+ let fileMenu = menubar.querySelector("#file-menu");
+ let editMenu = menubar.querySelector("#edit-menu");
+ ok(fileMenu, "The menu has loaded in the projecteditor document");
+ ok(editMenu, "The menu has loaded in the projecteditor document");
+
+ let cmdNew = fileMenu.querySelector("[command=cmd-new]");
+ let cmdSave = fileMenu.querySelector("[command=cmd-save]");
+ let cmdSaveas = fileMenu.querySelector("[command=cmd-saveas]");
+
+ let cmdUndo = editMenu.querySelector("[command=cmd_undo]");
+ let cmdRedo = editMenu.querySelector("[command=cmd_redo]");
+ let cmdCut = editMenu.querySelector("[command=cmd_cut]");
+ let cmdCopy = editMenu.querySelector("[command=cmd_copy]");
+ let cmdPaste = editMenu.querySelector("[command=cmd_paste]");
+
+ info("Checking initial state of menus");
+ yield openAndCloseMenu(fileMenu);
+ yield openAndCloseMenu(editMenu);
+
+ is(cmdNew.getAttribute("disabled"), "", "File menu item is enabled");
+ is(cmdSave.getAttribute("disabled"), "true", "File menu item is disabled");
+ is(cmdSaveas.getAttribute("disabled"), "true", "File menu item is disabled");
+
+ is(cmdUndo.getAttribute("disabled"), "true", "Edit menu item is disabled");
+ is(cmdRedo.getAttribute("disabled"), "true", "Edit menu item is disabled");
+ is(cmdCut.getAttribute("disabled"), "true", "Edit menu item is disabled");
+ is(cmdCopy.getAttribute("disabled"), "true", "Edit menu item is disabled");
+ is(cmdPaste.getAttribute("disabled"), "true", "Edit menu item is disabled");
+
+ projecteditor.menuEnabled = false;
+
+ info("Checking with menuEnabled = false");
+ yield openAndCloseMenu(fileMenu);
+ yield openAndCloseMenu(editMenu);
+
+ is(cmdNew.getAttribute("disabled"), "true", "File menu item is disabled");
+ is(cmdSave.getAttribute("disabled"), "true", "File menu item is disabled");
+ is(cmdSaveas.getAttribute("disabled"), "true", "File menu item is disabled");
+
+ is(cmdUndo.getAttribute("disabled"), "true", "Edit menu item is disabled");
+ is(cmdRedo.getAttribute("disabled"), "true", "Edit menu item is disabled");
+ is(cmdCut.getAttribute("disabled"), "true", "Edit menu item is disabled");
+ is(cmdCopy.getAttribute("disabled"), "true", "Edit menu item is disabled");
+ is(cmdPaste.getAttribute("disabled"), "true", "Edit menu item is disabled");
+
+ info("Checking with menuEnabled=true");
+ projecteditor.menuEnabled = true;
+
+ yield openAndCloseMenu(fileMenu);
+ yield openAndCloseMenu(editMenu);
+
+ is(cmdNew.getAttribute("disabled"), "", "File menu item is enabled");
+ is(cmdSave.getAttribute("disabled"), "true", "File menu item is disabled");
+ is(cmdSaveas.getAttribute("disabled"), "true", "File menu item is disabled");
+
+ is(cmdUndo.getAttribute("disabled"), "true", "Edit menu item is disabled");
+ is(cmdRedo.getAttribute("disabled"), "true", "Edit menu item is disabled");
+ is(cmdCut.getAttribute("disabled"), "true", "Edit menu item is disabled");
+ is(cmdCopy.getAttribute("disabled"), "true", "Edit menu item is disabled");
+ is(cmdPaste.getAttribute("disabled"), "true", "Edit menu item is disabled");
+
+ info("Checking with resource selected");
+ let resource = projecteditor.project.allResources()[2];
+ yield selectFile(projecteditor, resource);
+ let editor = projecteditor.currentEditor;
+
+ let onChange = promise.defer();
+
+ projecteditor.on("onEditorChange", () => {
+ info("onEditorChange has been detected");
+ onChange.resolve();
+ });
+ editor.editor.focus();
+ EventUtils.synthesizeKey("f", { }, projecteditor.window);
+
+ yield onChange;
+ yield openAndCloseMenu(fileMenu);
+ yield openAndCloseMenu(editMenu);
+
+ is(cmdNew.getAttribute("disabled"), "", "File menu item is enabled");
+ is(cmdSave.getAttribute("disabled"), "", "File menu item is enabled");
+ is(cmdSaveas.getAttribute("disabled"), "", "File menu item is enabled");
+
+ // Use editor.canUndo() to see if this is failing - the menu disabled property
+ // should be in sync with this because of isCommandEnabled in editor.js.
+ info('cmdUndo.getAttribute("disabled") is: "' + cmdUndo.getAttribute("disabled") + '"');
+ ok(editor.editor.canUndo(), "Edit menu item is enabled");
+
+ is(cmdRedo.getAttribute("disabled"), "true", "Edit menu item is disabled");
+ is(cmdCut.getAttribute("disabled"), "true", "Edit menu item is disabled");
+ is(cmdCopy.getAttribute("disabled"), "true", "Edit menu item is disabled");
+ is(cmdPaste.getAttribute("disabled"), "", "Edit menu item is enabled");
+});
+
+function* openAndCloseMenu(menu) {
+ let shown = onPopupShow(menu);
+ EventUtils.synthesizeMouseAtCenter(menu, {}, menu.ownerDocument.defaultView);
+ yield shown;
+ let hidden = onPopupHidden(menu);
+ EventUtils.synthesizeMouseAtCenter(menu, {}, menu.ownerDocument.defaultView);
+ yield hidden;
+}
diff --git a/devtools/client/projecteditor/test/browser_projecteditor_new_file.js b/devtools/client/projecteditor/test/browser_projecteditor_new_file.js
new file mode 100644
index 000000000..aaaee0369
--- /dev/null
+++ b/devtools/client/projecteditor/test/browser_projecteditor_new_file.js
@@ -0,0 +1,13 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test tree selection functionality
+
+add_task(function* () {
+ let projecteditor = yield addProjectEditorTabForTempDirectory();
+ ok(projecteditor, "ProjectEditor has loaded");
+
+});
diff --git a/devtools/client/projecteditor/test/browser_projecteditor_rename_file_01.js b/devtools/client/projecteditor/test/browser_projecteditor_rename_file_01.js
new file mode 100644
index 000000000..914fa73cc
--- /dev/null
+++ b/devtools/client/projecteditor/test/browser_projecteditor_rename_file_01.js
@@ -0,0 +1,19 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test file rename functionality
+
+add_task(function* () {
+ let projecteditor = yield addProjectEditorTabForTempDirectory();
+ ok(true, "ProjectEditor has loaded");
+
+ let root = [...projecteditor.project.allStores()][0].root;
+ is(root.path, TEMP_PATH, "The root store is set to the correct temp path.");
+ for (let child of root.children) {
+ yield renameWithContextMenu(projecteditor,
+ projecteditor.projectTree.getViewContainer(child), ".renamed");
+ }
+});
diff --git a/devtools/client/projecteditor/test/browser_projecteditor_rename_file_02.js b/devtools/client/projecteditor/test/browser_projecteditor_rename_file_02.js
new file mode 100644
index 000000000..a2964da2a
--- /dev/null
+++ b/devtools/client/projecteditor/test/browser_projecteditor_rename_file_02.js
@@ -0,0 +1,26 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test file rename functionality with non ascii characters
+
+add_task(function* () {
+ let projecteditor = yield addProjectEditorTabForTempDirectory();
+ ok(true, "ProjectEditor has loaded");
+
+ let root = [...projecteditor.project.allStores()][0].root;
+ is(root.path, TEMP_PATH, "The root store is set to the correct temp path.");
+
+ let childrenList = [];
+ for (let child of root.children) {
+ yield renameWithContextMenu(projecteditor,
+ projecteditor.projectTree.getViewContainer(child), ".ren\u0061\u0308med");
+ childrenList.push(child.basename + ".ren\u0061\u0308med");
+ }
+ for (let child of root.children) {
+ is(childrenList.indexOf(child.basename) == -1, false,
+ "Failed to update tree with non-ascii character");
+ }
+});
diff --git a/devtools/client/projecteditor/test/browser_projecteditor_saveall.js b/devtools/client/projecteditor/test/browser_projecteditor_saveall.js
new file mode 100644
index 000000000..2468ea4fc
--- /dev/null
+++ b/devtools/client/projecteditor/test/browser_projecteditor_saveall.js
@@ -0,0 +1,64 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("destroy");
+
+loadHelperScript("helper_edits.js");
+
+// Test ProjectEditor basic functionality
+add_task(function* () {
+ let projecteditor = yield addProjectEditorTabForTempDirectory();
+ let TEMP_PATH = projecteditor.project.allPaths()[0];
+
+ is(getTempFile("").path, TEMP_PATH, "Temp path is set correctly.");
+
+ ok(projecteditor.currentEditor, "There is an editor for projecteditor");
+ let resources = projecteditor.project.allResources();
+
+ for (let data of helperEditData) {
+ info("Processing " + data.path);
+ let resource = resources.filter(r=>r.basename === data.basename)[0];
+ yield selectFile(projecteditor, resource);
+ yield editFile(projecteditor, getTempFile(data.path).path, data.newContent);
+ }
+
+ info("Saving all resources");
+ ok(projecteditor.hasUnsavedResources, "hasUnsavedResources");
+ yield projecteditor.saveAllFiles();
+ ok(!projecteditor.hasUnsavedResources, "!hasUnsavedResources");
+ for (let data of helperEditData) {
+ let filePath = getTempFile(data.path).path;
+ info("Asserting that data at " + filePath + " has been saved");
+ let resource = resources.filter(r=>r.basename === data.basename)[0];
+ yield selectFile(projecteditor, resource);
+ let editor = projecteditor.currentEditor;
+ let savedData = yield getFileData(filePath);
+ is(savedData, data.newContent, "Data has been correctly saved to disk");
+ }
+});
+
+function* editFile(projecteditor, filePath, newData) {
+ info("Testing file editing for: " + filePath);
+
+ let initialData = yield getFileData(filePath);
+ let editor = projecteditor.currentEditor;
+ let resource = projecteditor.resourceFor(editor);
+ let viewContainer = projecteditor.projectTree.getViewContainer(resource);
+ let originalTreeLabel = viewContainer.label.textContent;
+
+ is(resource.path, filePath, "Resource path is set correctly");
+ is(editor.editor.getText(), initialData, "Editor is loaded with correct file contents");
+
+ info("Setting text in the editor");
+
+ editor.editor.setText(newData);
+ is(editor.editor.getText(), newData, "Editor has been filled with new data");
+ is(viewContainer.label.textContent, "*" + originalTreeLabel, "Label is marked as changed");
+}
diff --git a/devtools/client/projecteditor/test/browser_projecteditor_stores.js b/devtools/client/projecteditor/test/browser_projecteditor_stores.js
new file mode 100644
index 000000000..c85a7526b
--- /dev/null
+++ b/devtools/client/projecteditor/test/browser_projecteditor_stores.js
@@ -0,0 +1,16 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test ProjectEditor basic functionality
+add_task(function* () {
+ let projecteditor = yield addProjectEditorTabForTempDirectory();
+ let TEMP_PATH = projecteditor.project.allPaths()[0];
+ is(getTempFile("").path, TEMP_PATH, "Temp path is set correctly.");
+
+ is(projecteditor.project.allPaths().length, 1, "1 path is set");
+ projecteditor.project.removeAllStores();
+ is(projecteditor.project.allPaths().length, 0, "No paths are remaining");
+});
diff --git a/devtools/client/projecteditor/test/browser_projecteditor_tree_selection_01.js b/devtools/client/projecteditor/test/browser_projecteditor_tree_selection_01.js
new file mode 100644
index 000000000..0a98f7122
--- /dev/null
+++ b/devtools/client/projecteditor/test/browser_projecteditor_tree_selection_01.js
@@ -0,0 +1,98 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test tree selection functionality
+
+add_task(function* () {
+ let projecteditor = yield addProjectEditorTabForTempDirectory();
+ let TEMP_PATH = projecteditor.project.allPaths()[0];
+
+ is(getTempFile("").path, TEMP_PATH, "Temp path is set correctly.");
+
+ ok(projecteditor.currentEditor, "There is an editor for projecteditor");
+ let resources = projecteditor.project.allResources();
+
+ is(
+ resources.map(r=>r.basename).join("|"),
+ TEMP_FOLDER_NAME + "|css|styles.css|data|img|icons|128x128.png|16x16.png|32x32.png|vector.svg|fake.png|js|script.js|index.html|LICENSE|README.md",
+ "Resources came through in proper order"
+ );
+
+ for (let i = 0; i < resources.length; i++) {
+ yield selectFileFirstLoad(projecteditor, resources[i]);
+ }
+ for (let i = 0; i < resources.length; i++) {
+ yield selectFileSubsequentLoad(projecteditor, resources[i]);
+ }
+ for (let i = 0; i < resources.length; i++) {
+ yield selectFileSubsequentLoad(projecteditor, resources[i]);
+ }
+});
+
+function* selectFileFirstLoad(projecteditor, resource) {
+ ok(resource && resource.path, "A valid resource has been passed in for selection " + (resource && resource.path));
+ projecteditor.projectTree.selectResource(resource);
+ let container = projecteditor.projectTree.getViewContainer(resource);
+
+ if (resource.isRoot) {
+ ok(container.expanded, "The root directory is expanded by default.");
+ container.line.click();
+ ok(container.expanded, "Clicking on the line does not toggles expansion.");
+ return;
+ }
+ if (resource.isDir) {
+ ok(!container.expanded, "A directory is not expanded by default.");
+ container.line.click();
+ ok(container.expanded, "Clicking on the line toggles expansion.");
+ container.line.click();
+ ok(!container.expanded, "Clicking on the line toggles expansion.");
+ return;
+ }
+
+ let [editorCreated, editorLoaded, editorActivated] = yield promise.all([
+ onceEditorCreated(projecteditor),
+ onceEditorLoad(projecteditor),
+ onceEditorActivated(projecteditor)
+ ]);
+
+ is(editorCreated, projecteditor.currentEditor, "Editor has been created for " + resource.path);
+ is(editorActivated, projecteditor.currentEditor, "Editor has been activated for " + resource.path);
+ is(editorLoaded, projecteditor.currentEditor, "Editor has been loaded for " + resource.path);
+}
+
+function* selectFileSubsequentLoad(projecteditor, resource) {
+ ok(resource && resource.path, "A valid resource has been passed in for selection " + (resource && resource.path));
+ projecteditor.projectTree.selectResource(resource);
+
+ if (resource.isDir) {
+ return;
+ }
+
+ // Make sure text editors are focused immediately when selected.
+ let focusPromise = promise.resolve();
+ if (projecteditor.currentEditor.editor) {
+ focusPromise = onEditorFocus(projecteditor.currentEditor);
+ }
+
+ // Only activated should fire the next time
+ // (may add load() if we begin checking for changes from disk)
+ let [editorActivated] = yield promise.all([
+ onceEditorActivated(projecteditor)
+ ]);
+
+ is(editorActivated, projecteditor.currentEditor, "Editor has been activated for " + resource.path);
+
+ yield focusPromise;
+}
+
+function onEditorFocus(editor) {
+ let def = promise.defer();
+ editor.on("focus", function focus() {
+ editor.off("focus", focus);
+ def.resolve();
+ });
+ return def.promise;
+}
diff --git a/devtools/client/projecteditor/test/browser_projecteditor_tree_selection_02.js b/devtools/client/projecteditor/test/browser_projecteditor_tree_selection_02.js
new file mode 100644
index 000000000..51826e4dc
--- /dev/null
+++ b/devtools/client/projecteditor/test/browser_projecteditor_tree_selection_02.js
@@ -0,0 +1,76 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("destroy");
+
+// Test that files get reselected in the tree when their editor
+// is focused. https://bugzilla.mozilla.org/show_bug.cgi?id=1011116.
+
+add_task(function* () {
+ let projecteditor = yield addProjectEditorTabForTempDirectory();
+ let TEMP_PATH = projecteditor.project.allPaths()[0];
+
+ is(getTempFile("").path, TEMP_PATH, "Temp path is set correctly.");
+
+ ok(projecteditor.currentEditor, "There is an editor for projecteditor");
+ let resources = projecteditor.project.allResources();
+
+ is(
+ resources.map(r=>r.basename).join("|"),
+ TEMP_FOLDER_NAME + "|css|styles.css|data|img|icons|128x128.png|16x16.png|32x32.png|vector.svg|fake.png|js|script.js|index.html|LICENSE|README.md",
+ "Resources came through in proper order"
+ );
+
+ for (let i = 0; i < resources.length; i++) {
+ yield selectAndRefocusFile(projecteditor, resources[i]);
+ }
+});
+
+function* selectAndRefocusFile(projecteditor, resource) {
+ ok(resource && resource.path, "A valid resource has been passed in for selection " + (resource && resource.path));
+ projecteditor.projectTree.selectResource(resource);
+
+ if (resource.isDir) {
+ return;
+ }
+
+ let [editorCreated, editorLoaded, editorActivated] = yield promise.all([
+ onceEditorCreated(projecteditor),
+ onceEditorLoad(projecteditor),
+ onceEditorActivated(projecteditor)
+ ]);
+
+ if (projecteditor.currentEditor.editor) {
+ // This is a text editor. Go ahead and select a directory then refocus
+ // the editor to make sure it is reselected in tree.
+ let treeContainer = projecteditor.projectTree.getViewContainer(getDirectoryInStore(resource));
+ treeContainer.line.click();
+ EventUtils.synthesizeMouseAtCenter(treeContainer.elt, {}, treeContainer.elt.ownerDocument.defaultView);
+ let waitForTreeSelect = onTreeSelection(projecteditor);
+ projecteditor.currentEditor.focus();
+ yield waitForTreeSelect;
+
+ is(projecteditor.projectTree.getSelectedResource(), resource, "The resource gets reselected in the tree");
+ }
+}
+
+// Return a directory to select in the tree.
+function getDirectoryInStore(resource) {
+ return resource.store.root.childrenSorted.filter(r=>r.isDir)[0];
+}
+
+function onTreeSelection(projecteditor) {
+ let def = promise.defer();
+ projecteditor.projectTree.on("selection", function selection() {
+ projecteditor.projectTree.off("focus", selection);
+ def.resolve();
+ });
+ return def.promise;
+}
diff --git a/devtools/client/projecteditor/test/head.js b/devtools/client/projecteditor/test/head.js
new file mode 100644
index 000000000..d5d9ce849
--- /dev/null
+++ b/devtools/client/projecteditor/test/head.js
@@ -0,0 +1,391 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var Cu = Components.utils;
+const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const {TargetFactory} = require("devtools/client/framework/target");
+const {console} = Cu.import("resource://gre/modules/Console.jsm", {});
+const promise = require("promise");
+const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", {});
+const {NetUtil} = Cu.import("resource://gre/modules/NetUtil.jsm", {});
+const ProjectEditor = require("devtools/client/projecteditor/lib/projecteditor");
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const flags = require("devtools/shared/flags");
+
+const TEST_URL_ROOT = "http://mochi.test:8888/browser/devtools/client/projecteditor/test/";
+const SAMPLE_WEBAPP_URL = TEST_URL_ROOT + "/helper_homepage.html";
+var TEMP_PATH;
+var TEMP_FOLDER_NAME = "ProjectEditor" + (new Date().getTime());
+
+// All test are asynchronous
+waitForExplicitFinish();
+
+// Uncomment this pref to dump all devtools emitted events to the console.
+// Services.prefs.setBoolPref("devtools.dump.emit", true);
+
+// Set the testing flag and reset it when the test ends
+flags.testing = true;
+registerCleanupFunction(() => flags.testing = false);
+
+// Clear preferences that may be set during the course of tests.
+registerCleanupFunction(() => {
+ // Services.prefs.clearUserPref("devtools.dump.emit");
+ TEMP_PATH = null;
+ TEMP_FOLDER_NAME = null;
+});
+
+// Auto close the toolbox and close the test tabs when the test ends
+registerCleanupFunction(() => {
+ try {
+ let target = TargetFactory.forTab(gBrowser.selectedTab);
+ gDevTools.closeToolbox(target);
+ } catch (ex) {
+ dump(ex);
+ }
+ while (gBrowser.tabs.length > 1) {
+ gBrowser.removeCurrentTab();
+ }
+});
+
+/**
+ * Add a new test tab in the browser and load the given url.
+ * @param {String} url The url to be loaded in the new tab
+ * @return a promise that resolves to the tab object when the url is loaded
+ */
+function addTab(url) {
+ info("Adding a new tab with URL: '" + url + "'");
+ let def = promise.defer();
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab(url);
+ BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(function () {
+ info("URL '" + url + "' loading complete");
+ waitForFocus(() => {
+ def.resolve(tab);
+ }, content);
+ });
+
+ return def.promise;
+}
+
+/**
+ * Some tests may need to import one or more of the test helper scripts.
+ * A test helper script is simply a js file that contains common test code that
+ * is either not common-enough to be in head.js, or that is located in a separate
+ * directory.
+ * The script will be loaded synchronously and in the test's scope.
+ * @param {String} filePath The file path, relative to the current directory.
+ * Examples:
+ * - "helper_attributes_test_runner.js"
+ * - "../../../commandline/test/helpers.js"
+ */
+function loadHelperScript(filePath) {
+ let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
+ Services.scriptloader.loadSubScript(testDir + "/" + filePath, this);
+}
+
+function addProjectEditorTabForTempDirectory(opts = {}) {
+ try {
+ TEMP_PATH = buildTempDirectoryStructure();
+ } catch (e) {
+ // Bug 1037292 - The test servers sometimes are unable to
+ // write to the temporary directory due to locked files
+ // or access denied errors. Try again if this failed.
+ info("Project Editor temp directory creation failed. Trying again.");
+ TEMP_PATH = buildTempDirectoryStructure();
+ }
+ let customOpts = {
+ name: "Test",
+ iconUrl: "chrome://devtools/skin/images/tool-options.svg",
+ projectOverviewURL: SAMPLE_WEBAPP_URL
+ };
+
+ info("Adding a project editor tab for editing at: " + TEMP_PATH);
+ return addProjectEditorTab(opts).then((projecteditor) => {
+ return projecteditor.setProjectToAppPath(TEMP_PATH, customOpts).then(() => {
+ return projecteditor;
+ });
+ });
+}
+
+function addProjectEditorTab(opts = {}) {
+ return addTab("chrome://devtools/content/projecteditor/chrome/content/projecteditor-test.xul").then(() => {
+ let iframe = content.document.getElementById("projecteditor-iframe");
+ if (opts.menubar !== false) {
+ opts.menubar = content.document.querySelector("menubar");
+ }
+ let projecteditor = ProjectEditor.ProjectEditor(iframe, opts);
+
+
+ ok(iframe, "Tab has placeholder iframe for projecteditor");
+ ok(projecteditor, "ProjectEditor has been initialized");
+
+ return projecteditor.loaded.then((projecteditor) => {
+ return projecteditor;
+ });
+ });
+}
+
+/**
+ * Build a temporary directory as a workspace for this loader
+ * https://developer.mozilla.org/en-US/Add-ons/Code_snippets/File_I_O
+ */
+function buildTempDirectoryStructure() {
+
+ let dirName = TEMP_FOLDER_NAME;
+ info("Building a temporary directory at " + dirName);
+
+ // First create (and remove) the temp dir to discard any changes
+ let TEMP_DIR = FileUtils.getDir("TmpD", [dirName], true);
+ TEMP_DIR.remove(true);
+
+ // Now rebuild our fake project.
+ TEMP_DIR = FileUtils.getDir("TmpD", [dirName], true);
+
+ FileUtils.getDir("TmpD", [dirName, "css"], true);
+ FileUtils.getDir("TmpD", [dirName, "data"], true);
+ FileUtils.getDir("TmpD", [dirName, "img", "icons"], true);
+ FileUtils.getDir("TmpD", [dirName, "js"], true);
+
+ let htmlFile = FileUtils.getFile("TmpD", [dirName, "index.html"]);
+ htmlFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
+ writeToFileSync(htmlFile, [
+ "<!DOCTYPE html>",
+ '<html lang="en">',
+ " <head>",
+ ' <meta charset="utf-8" />',
+ " <title>ProjectEditor Temp File</title>",
+ ' <link rel="stylesheet" href="style.css" />',
+ " </head>",
+ ' <body id="home">',
+ " <p>ProjectEditor Temp File</p>",
+ " </body>",
+ "</html>"].join("\n")
+ );
+
+ let readmeFile = FileUtils.getFile("TmpD", [dirName, "README.md"]);
+ readmeFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
+ writeToFileSync(readmeFile, [
+ "## Readme"
+ ].join("\n")
+ );
+
+ let licenseFile = FileUtils.getFile("TmpD", [dirName, "LICENSE"]);
+ licenseFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
+ writeToFileSync(licenseFile, [
+ "/* This Source Code Form is subject to the terms of the Mozilla Public",
+ " * License, v. 2.0. If a copy of the MPL was not distributed with this",
+ " * file, You can obtain one at http://mozilla.org/MPL/2.0/. */"
+ ].join("\n")
+ );
+
+ let cssFile = FileUtils.getFile("TmpD", [dirName, "css", "styles.css"]);
+ cssFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
+ writeToFileSync(cssFile, [
+ "body {",
+ " background: red;",
+ "}"
+ ].join("\n")
+ );
+
+ FileUtils.getFile("TmpD", [dirName, "js", "script.js"]).createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
+
+ FileUtils.getFile("TmpD", [dirName, "img", "fake.png"]).createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
+ FileUtils.getFile("TmpD", [dirName, "img", "icons", "16x16.png"]).createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
+ FileUtils.getFile("TmpD", [dirName, "img", "icons", "32x32.png"]).createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
+ FileUtils.getFile("TmpD", [dirName, "img", "icons", "128x128.png"]).createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
+ FileUtils.getFile("TmpD", [dirName, "img", "icons", "vector.svg"]).createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
+
+ return TEMP_DIR.path;
+}
+
+// https://developer.mozilla.org/en-US/Add-ons/Code_snippets/File_I_O#Writing_to_a_file
+function writeToFile(file, data) {
+ if (typeof file === "string") {
+ file = new FileUtils.File(file);
+ }
+ info("Writing to file: " + file.path + " (exists? " + file.exists() + ")");
+ let defer = promise.defer();
+ var ostream = FileUtils.openSafeFileOutputStream(file);
+
+ var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].
+ createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+ var istream = converter.convertToInputStream(data);
+
+ // The last argument (the callback) is optional.
+ NetUtil.asyncCopy(istream, ostream, function (status) {
+ if (!Components.isSuccessCode(status)) {
+ // Handle error!
+ info("ERROR WRITING TEMP FILE", status);
+ }
+ defer.resolve();
+ });
+ return defer.promise;
+}
+
+// This is used when setting up the test.
+// You should typically use the async version of this, writeToFile.
+// https://developer.mozilla.org/en-US/Add-ons/Code_snippets/File_I_O#More
+function writeToFileSync(file, data) {
+ // file is nsIFile, data is a string
+ var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Components.interfaces.nsIFileOutputStream);
+
+ // use 0x02 | 0x10 to open file for appending.
+ foStream.init(file, 0x02 | 0x08 | 0x20, 0o666, 0);
+ // write, create, truncate
+ // In a c file operation, we have no need to set file mode with or operation,
+ // directly using "r" or "w" usually.
+
+ // if you are sure there will never ever be any non-ascii text in data you can
+ // also call foStream.write(data, data.length) directly
+ var converter = Components.classes["@mozilla.org/intl/converter-output-stream;1"].
+ createInstance(Components.interfaces.nsIConverterOutputStream);
+ converter.init(foStream, "UTF-8", 0, 0);
+ converter.writeString(data);
+ converter.close(); // this closes foStream
+}
+
+function getTempFile(path) {
+ let parts = [TEMP_FOLDER_NAME];
+ parts = parts.concat(path.split("/"));
+ return FileUtils.getFile("TmpD", parts);
+}
+
+// https://developer.mozilla.org/en-US/Add-ons/Code_snippets/File_I_O#Writing_to_a_file
+function* getFileData(file) {
+ if (typeof file === "string") {
+ file = new FileUtils.File(file);
+ }
+ let def = promise.defer();
+
+ NetUtil.asyncFetch({
+ uri: NetUtil.newURI(file),
+ loadUsingSystemPrincipal: true
+ }, function (inputStream, status) {
+ if (!Components.isSuccessCode(status)) {
+ info("ERROR READING TEMP FILE", status);
+ }
+
+ // Detect if an empty file is loaded
+ try {
+ inputStream.available();
+ } catch (e) {
+ def.resolve("");
+ return;
+ }
+
+ var data = NetUtil.readInputStreamToString(inputStream, inputStream.available());
+ def.resolve(data);
+ });
+
+ return def.promise;
+}
+
+/**
+ * Rename the resource of the provided container using the context menu.
+ *
+ * @param {ProjectEditor} projecteditor the current project editor instance
+ * @param {Shell} container for the resource to rename
+ * @param {String} newName the name to use for renaming the resource
+ * @return {Promise} a promise that resolves when the resource has been renamed
+ */
+var renameWithContextMenu = Task.async(function* (projecteditor,
+ container, newName) {
+ let popup = projecteditor.contextMenuPopup;
+ let resource = container.resource;
+ info("Going to attempt renaming for: " + resource.path);
+
+ let waitForPopupShow = onPopupShow(popup);
+ openContextMenu(container.label);
+ yield waitForPopupShow;
+
+ let renameCommand = popup.querySelector("[command=cmd-rename]");
+ ok(renameCommand, "Rename command exists in popup");
+ is(renameCommand.getAttribute("hidden"), "", "Rename command is visible");
+ is(renameCommand.getAttribute("disabled"), "", "Rename command is enabled");
+
+ renameCommand.click();
+ popup.hidePopup();
+ let input = container.elt.childNodes[0].childNodes[1];
+ input.value = resource.basename + newName;
+
+ let waitForProjectRefresh = onceProjectRefreshed(projecteditor);
+ EventUtils.synthesizeKey("VK_RETURN", {}, projecteditor.window);
+ yield waitForProjectRefresh;
+
+ try {
+ yield OS.File.stat(resource.path + newName);
+ ok(true, "File is renamed");
+ } catch (e) {
+ ok(false, "Failed to rename file");
+ }
+});
+
+function onceEditorCreated(projecteditor) {
+ let def = promise.defer();
+ projecteditor.once("onEditorCreated", (editor) => {
+ def.resolve(editor);
+ });
+ return def.promise;
+}
+
+function onceEditorLoad(projecteditor) {
+ let def = promise.defer();
+ projecteditor.once("onEditorLoad", (editor) => {
+ def.resolve(editor);
+ });
+ return def.promise;
+}
+
+function onceEditorActivated(projecteditor) {
+ let def = promise.defer();
+ projecteditor.once("onEditorActivated", (editor) => {
+ def.resolve(editor);
+ });
+ return def.promise;
+}
+
+function onceEditorSave(projecteditor) {
+ let def = promise.defer();
+ projecteditor.once("onEditorSave", (editor, resource) => {
+ def.resolve(resource);
+ });
+ return def.promise;
+}
+
+function onceProjectRefreshed(projecteditor) {
+ return new Promise(resolve => {
+ projecteditor.project.on("refresh-complete", function refreshComplete() {
+ projecteditor.project.off("refresh-complete", refreshComplete);
+ resolve();
+ });
+ });
+}
+
+function onPopupShow(menu) {
+ let defer = promise.defer();
+ menu.addEventListener("popupshown", function onpopupshown() {
+ menu.removeEventListener("popupshown", onpopupshown);
+ defer.resolve();
+ });
+ return defer.promise;
+}
+
+function onPopupHidden(menu) {
+ let defer = promise.defer();
+ menu.addEventListener("popuphidden", function onpopuphidden() {
+ menu.removeEventListener("popuphidden", onpopuphidden);
+ defer.resolve();
+ });
+ return defer.promise;
+}
+
+function openContextMenu(node) {
+ EventUtils.synthesizeMouseAtCenter(
+ node,
+ {button: 2, type: "contextmenu"},
+ node.ownerDocument.defaultView
+ );
+}
diff --git a/devtools/client/projecteditor/test/helper_edits.js b/devtools/client/projecteditor/test/helper_edits.js
new file mode 100644
index 000000000..d8e83672b
--- /dev/null
+++ b/devtools/client/projecteditor/test/helper_edits.js
@@ -0,0 +1,53 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var helperEditData = [
+ {
+ basename: "styles.css",
+ path: "css/styles.css",
+ newContent: "body,html { color: orange; }"
+ },
+ {
+ basename: "index.html",
+ path: "index.html",
+ newContent: "<h1>Changed Content Again</h1>"
+ },
+ {
+ basename: "LICENSE",
+ path: "LICENSE",
+ newContent: "My new license"
+ },
+ {
+ basename: "README.md",
+ path: "README.md",
+ newContent: "My awesome readme"
+ },
+ {
+ basename: "script.js",
+ path: "js/script.js",
+ newContent: "alert('hi')"
+ },
+ {
+ basename: "vector.svg",
+ path: "img/icons/vector.svg",
+ newContent: "<svg></svg>"
+ },
+];
+
+function* selectFile(projecteditor, resource) {
+ ok(resource && resource.path, "A valid resource has been passed in for selection " + (resource && resource.path));
+ projecteditor.projectTree.selectResource(resource);
+
+ if (resource.isDir) {
+ return;
+ }
+
+ let [editorActivated] = yield promise.all([
+ onceEditorActivated(projecteditor)
+ ]);
+
+ is(editorActivated, projecteditor.currentEditor, "Editor has been activated for " + resource.path);
+}
diff --git a/devtools/client/projecteditor/test/helper_homepage.html b/devtools/client/projecteditor/test/helper_homepage.html
new file mode 100644
index 000000000..a4402a9bd
--- /dev/null
+++ b/devtools/client/projecteditor/test/helper_homepage.html
@@ -0,0 +1 @@
+<h1>ProjectEditor tests</h1> \ No newline at end of file