diff options
Diffstat (limited to 'devtools/client/projecteditor/test')
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 |