diff options
Diffstat (limited to 'devtools/client/scratchpad/test')
43 files changed, 3855 insertions, 0 deletions
diff --git a/devtools/client/scratchpad/test/.eslintrc.js b/devtools/client/scratchpad/test/.eslintrc.js new file mode 100644 index 000000000..8d15a76d9 --- /dev/null +++ b/devtools/client/scratchpad/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/scratchpad/test/NS_ERROR_ILLEGAL_INPUT.txt b/devtools/client/scratchpad/test/NS_ERROR_ILLEGAL_INPUT.txt new file mode 100644 index 000000000..031c0597b --- /dev/null +++ b/devtools/client/scratchpad/test/NS_ERROR_ILLEGAL_INPUT.txt @@ -0,0 +1,2 @@ +Typ Datum Uhrzeit Quelle Kategorie Ereignis Benutzer Computer
+Informationen 10.08.2012 16:07:11 MSDTC Datenträger 2444 Nicht zutreffend
diff --git a/devtools/client/scratchpad/test/browser.ini b/devtools/client/scratchpad/test/browser.ini new file mode 100644 index 000000000..cc67ce1ab --- /dev/null +++ b/devtools/client/scratchpad/test/browser.ini @@ -0,0 +1,46 @@ +[DEFAULT] +tags = devtools +subsuite = devtools +support-files = head.js + +[browser_scratchpad_autocomplete.js] +[browser_scratchpad_browser_last_window_closing.js] +[browser_scratchpad_reset_undo.js] +[browser_scratchpad_display_outputs_errors.js] +[browser_scratchpad_eval_func.js] +[browser_scratchpad_goto_line_ui.js] +[browser_scratchpad_reload_and_run.js] +[browser_scratchpad_display_non_error_exceptions.js] +[browser_scratchpad_modeline.js] +[browser_scratchpad_chrome_context_pref.js] +[browser_scratchpad_help_key.js] +[browser_scratchpad_recent_files.js] +# [browser_scratchpad_confirm_close.js] +# Disable test due to bug 807234 becoming basically permanent +[browser_scratchpad_tab.js] +[browser_scratchpad_wrong_window_focus.js] +[browser_scratchpad_unsaved.js] +[browser_scratchpad_falsy.js] +[browser_scratchpad_edit_ui_updates.js] +[browser_scratchpad_revert_to_saved.js] +[browser_scratchpad_run_error_goto_line.js] +[browser_scratchpad_contexts.js] +[browser_scratchpad_execute_print.js] +[browser_scratchpad_files.js] +[browser_scratchpad_initialization.js] +[browser_scratchpad_inspect.js] +[browser_scratchpad_inspect_primitives.js] +[browser_scratchpad_long_string.js] +[browser_scratchpad_open.js] +support-files = NS_ERROR_ILLEGAL_INPUT.txt +[browser_scratchpad_open_error_console.js] +[browser_scratchpad_throw_output.js] +[browser_scratchpad_pprint-02.js] +[browser_scratchpad_pprint.js] +[browser_scratchpad_pprint_error_goto_line.js] +[browser_scratchpad_restore.js] +[browser_scratchpad_tab_switch.js] +[browser_scratchpad_ui.js] +[browser_scratchpad_close_toolbox.js] +[browser_scratchpad_remember_view_options.js] +[browser_scratchpad_disable_view_menu_items.js] diff --git a/devtools/client/scratchpad/test/browser_scratchpad_autocomplete.js b/devtools/client/scratchpad/test/browser_scratchpad_autocomplete.js new file mode 100644 index 000000000..3a6eef8b4 --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_autocomplete.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/ */ +/* Bug 968896 */ + +// Test the completions using numbers. +const source = "0x1."; +const completions = ["toExponential", "toFixed", "toString"]; +const { Task } = require("devtools/shared/task"); + +function test() { + const options = { tabContent: "test scratchpad autocomplete" }; + openTabAndScratchpad(options) + .then(Task.async(runTests)) + .then(finish, console.error); +} + + +function* runTests([win, sp]) { + const {editor} = sp; + const editorWin = editor.container.contentWindow; + + // Show the completions popup. + sp.setText(source); + sp.editor.setCursor({ line: 0, ch: source.length }); + yield keyOnce("suggestion-entered", " ", { ctrlKey: true }); + + // Get the hints popup container. + const hints = editorWin.document.querySelector(".CodeMirror-hints"); + + ok(hints, + "The hint container should exist."); + is(hints.childNodes.length, 3, + "The hint container should have the completions."); + + let i = 0; + for (let completion of completions) { + let active = hints.querySelector(".CodeMirror-hint-active"); + is(active.textContent, completion, + "Check that completion " + i++ + " is what is expected."); + yield keyOnce("suggestion-entered", "VK_DOWN"); + } + + // We should have looped around to the first suggestion again. Accept it. + yield keyOnce("after-suggest", "VK_RETURN"); + + is(sp.getText(), source + completions[0], + "Autocompletion should work and select the right element."); + + // Check that the information tooltips work. + sp.setText("5"); + yield keyOnce("show-information", " ", { ctrlKey: true, shiftKey: true }); + + // Get the information container. + const info = editorWin.document.querySelector(".CodeMirror-Tern-information"); + ok(info, + "Info tooltip should appear."); + is(info.textContent.slice(0, 6), "number", + "Info tooltip should have expected contents."); + + function keyOnce(event, key, opts = {}) { + const p = editor.once(event); + EventUtils.synthesizeKey(key, opts, editorWin); + return p; + } +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_browser_last_window_closing.js b/devtools/client/scratchpad/test/browser_scratchpad_browser_last_window_closing.js new file mode 100644 index 000000000..3a8316059 --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_browser_last_window_closing.js @@ -0,0 +1,79 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const BUTTON_POSITION_CANCEL = 1; +const BUTTON_POSITION_DONT_SAVE = 2; + + +function test() +{ + waitForExplicitFinish(); + + // Observer must be attached *before* Scratchpad is opened. + CloseObserver.init(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onTabLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onTabLoad, true); + openScratchpad(runTests); + }, true); + + content.location = "data:text/html;charset=utf8,<p>test browser last window closing</p>"; +} + + + +function runTests({ Scratchpad }) +{ + let browser = Services.wm.getEnumerator("navigator:browser").getNext(); + let oldPrompt = Services.prompt; + let button; + + Services.prompt = { + confirmEx: () => button + }; + + + Scratchpad.dirty = true; + + // Test canceling close. + button = BUTTON_POSITION_CANCEL; + CloseObserver.expectedValue = true; + browser.BrowserTryToCloseWindow(); + + // Test accepting close. + button = BUTTON_POSITION_DONT_SAVE; + CloseObserver.expectedValue = false; + browser.BrowserTryToCloseWindow(); + + // Test closing without prompt needed. + Scratchpad.dirty = false; + browser.BrowserTryToCloseWindow(); + + Services.prompt = oldPrompt; + CloseObserver.uninit(); + finish(); +} + + +var CloseObserver = { + expectedValue: null, + init: function () + { + Services.obs.addObserver(this, "browser-lastwindow-close-requested", false); + }, + + observe: function (aSubject) + { + aSubject.QueryInterface(Ci.nsISupportsPRBool); + let message = this.expectedValue ? "close" : "stay open"; + ok(this.expectedValue === aSubject.data, "Expected browser to " + message); + aSubject.data = true; + }, + + uninit: function () + { + Services.obs.removeObserver(this, "browser-lastwindow-close-requested", false); + }, +}; diff --git a/devtools/client/scratchpad/test/browser_scratchpad_chrome_context_pref.js b/devtools/client/scratchpad/test/browser_scratchpad_chrome_context_pref.js new file mode 100644 index 000000000..08528d2c2 --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_chrome_context_pref.js @@ -0,0 +1,50 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* Bug 646070 */ + +var DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled"; + +function test() +{ + waitForExplicitFinish(); + + Services.prefs.setBoolPref(DEVTOOLS_CHROME_ENABLED, true); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); + + openScratchpad(runTests); + }, true); + + content.location = "data:text/html,Scratchpad test for bug 646070 - chrome context preference"; +} + +function runTests() +{ + let sp = gScratchpadWindow.Scratchpad; + ok(sp, "Scratchpad object exists in new window"); + + let environmentMenu = gScratchpadWindow.document. + getElementById("sp-environment-menu"); + ok(environmentMenu, "Environment menu element exists"); + ok(!environmentMenu.hasAttribute("hidden"), + "Environment menu is visible"); + + let errorConsoleCommand = gScratchpadWindow.document. + getElementById("sp-cmd-errorConsole"); + ok(errorConsoleCommand, "Error console command element exists"); + ok(!errorConsoleCommand.hasAttribute("disabled"), + "Error console command is enabled"); + + let chromeContextCommand = gScratchpadWindow.document. + getElementById("sp-cmd-browserContext"); + ok(chromeContextCommand, "Chrome context command element exists"); + ok(!chromeContextCommand.hasAttribute("disabled"), + "Chrome context command is disabled"); + + Services.prefs.clearUserPref(DEVTOOLS_CHROME_ENABLED); + + finish(); +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_close_toolbox.js b/devtools/client/scratchpad/test/browser_scratchpad_close_toolbox.js new file mode 100644 index 000000000..fd1126fd4 --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_close_toolbox.js @@ -0,0 +1,38 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that closing the toolbox after having opened a scratchpad leaves the +// latter in a functioning state. + +var {Task} = require("devtools/shared/task"); +var {TargetFactory} = require("devtools/client/framework/target"); + +function test() { + const options = { + tabContent: "test closing toolbox and then reusing scratchpad" + }; + openTabAndScratchpad(options) + .then(Task.async(runTests)) + .then(finish, console.error); +} + +function* runTests([win, sp]) { + // Use the scratchpad before opening the toolbox. + const source = "window.foobar = 7;"; + sp.setText(source); + let [,, result] = yield sp.display(); + is(result, 7, "Display produced the expected output."); + + // Now open the toolbox and close it again. + let target = TargetFactory.forTab(gBrowser.selectedTab); + let toolbox = yield gDevTools.showToolbox(target, "webconsole"); + ok(toolbox, "Toolbox was opened."); + let closed = yield gDevTools.closeToolbox(target); + is(closed, true, "Toolbox was closed."); + + // Now see if using the scratcphad works as expected. + sp.setText(source); + let [,, result2] = yield sp.display(); + is(result2, 7, + "Display produced the expected output after the toolbox was gone."); +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_confirm_close.js b/devtools/client/scratchpad/test/browser_scratchpad_confirm_close.js new file mode 100644 index 000000000..a6318fa75 --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_confirm_close.js @@ -0,0 +1,230 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* Bug 653427 */ + +var tempScope = {}; +Cu.import("resource://gre/modules/NetUtil.jsm", tempScope); +Cu.import("resource://gre/modules/FileUtils.jsm", tempScope); +var NetUtil = tempScope.NetUtil; +var FileUtils = tempScope.FileUtils; + +// only finish() when correct number of tests are done +const expected = 9; +var count = 0; +function done() +{ + if (++count == expected) { + cleanup(); + finish(); + } +} + +var gFile; + +var oldPrompt = Services.prompt; +var promptButton = -1; + +function test() +{ + waitForExplicitFinish(); + + gFile = createTempFile("fileForBug653427.tmp"); + writeFile(gFile, "text", testUnsaved.call(this)); + + Services.prompt = { + confirmEx: function () { + return promptButton; + } + }; + + testNew(); + testSavedFile(); + + gBrowser.selectedTab = gBrowser.addTab(); + content.location = "data:text/html,<p>test scratchpad save file prompt on closing"; +} + +function testNew() +{ + openScratchpad(function (win) { + win.Scratchpad.close(function () { + ok(win.closed, "new scratchpad window should close without prompting"); + done(); + }); + }, {noFocus: true}); +} + +function testSavedFile() +{ + openScratchpad(function (win) { + win.Scratchpad.filename = "test.js"; + win.Scratchpad.editor.dirty = false; + win.Scratchpad.close(function () { + ok(win.closed, "scratchpad from file with no changes should close"); + done(); + }); + }, {noFocus: true}); +} + +function testUnsaved() +{ + function setFilename(aScratchpad, aFile) { + aScratchpad.setFilename(aFile); + } + + testUnsavedFileCancel(setFilename); + testUnsavedFileSave(setFilename); + testUnsavedFileDontSave(setFilename); + testCancelAfterLoad(); + + function mockSaveFile(aScratchpad) { + let SaveFileStub = function (aCallback) { + /* + * An argument for aCallback must pass Components.isSuccessCode + * + * A version of isSuccessCode in JavaScript: + * function isSuccessCode(returnCode) { + * return (returnCode & 0x80000000) == 0; + * } + */ + aCallback(1); + }; + + aScratchpad.saveFile = SaveFileStub; + } + + // Run these tests again but this time without setting a filename to + // test that Scratchpad always asks for confirmation on dirty editor. + testUnsavedFileCancel(mockSaveFile); + testUnsavedFileSave(mockSaveFile); + testUnsavedFileDontSave(); +} + +function testUnsavedFileCancel(aCallback = function () {}) +{ + openScratchpad(function (win) { + aCallback(win.Scratchpad, "test.js"); + win.Scratchpad.editor.dirty = true; + + promptButton = win.BUTTON_POSITION_CANCEL; + + win.Scratchpad.close(function () { + ok(!win.closed, "cancelling dialog shouldn't close scratchpad"); + win.close(); + done(); + }); + }, {noFocus: true}); +} + +// Test a regression where our confirmation dialog wasn't appearing +// after openFile calls. See bug 801982. +function testCancelAfterLoad() +{ + openScratchpad(function (win) { + win.Scratchpad.setRecentFile(gFile); + win.Scratchpad.openFile(0); + win.Scratchpad.editor.dirty = true; + promptButton = win.BUTTON_POSITION_CANCEL; + + let EventStub = { + called: false, + preventDefault: function () { + EventStub.called = true; + } + }; + + win.Scratchpad.onClose(EventStub, function () { + ok(!win.closed, "cancelling dialog shouldn't close scratchpad"); + ok(EventStub.called, "aEvent.preventDefault was called"); + + win.Scratchpad.editor.dirty = false; + win.close(); + done(); + }); + }, {noFocus: true}); +} + +function testUnsavedFileSave(aCallback = function () {}) +{ + openScratchpad(function (win) { + win.Scratchpad.importFromFile(gFile, true, function (status, content) { + aCallback(win.Scratchpad, gFile.path); + + let text = "new text"; + win.Scratchpad.setText(text); + + promptButton = win.BUTTON_POSITION_SAVE; + + win.Scratchpad.close(function () { + ok(win.closed, 'pressing "Save" in dialog should close scratchpad'); + readFile(gFile, function (savedContent) { + is(savedContent, text, 'prompted "Save" worked when closing scratchpad'); + done(); + }); + }); + }); + }, {noFocus: true}); +} + +function testUnsavedFileDontSave(aCallback = function () {}) +{ + openScratchpad(function (win) { + aCallback(win.Scratchpad, gFile.path); + win.Scratchpad.editor.dirty = true; + + promptButton = win.BUTTON_POSITION_DONT_SAVE; + + win.Scratchpad.close(function () { + ok(win.closed, 'pressing "Don\'t Save" in dialog should close scratchpad'); + done(); + }); + }, {noFocus: true}); +} + +function cleanup() +{ + Services.prompt = oldPrompt; + gFile.remove(false); + gFile = null; +} + +function createTempFile(name) +{ + let file = FileUtils.getFile("TmpD", [name]); + file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + file.QueryInterface(Ci.nsILocalFile); + return file; +} + +function writeFile(file, content, callback) +{ + let fout = Cc["@mozilla.org/network/file-output-stream;1"]. + createInstance(Ci.nsIFileOutputStream); + fout.init(file.QueryInterface(Ci.nsILocalFile), 0x02 | 0x08 | 0x20, + 0o644, fout.DEFER_OPEN); + + let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]. + createInstance(Ci.nsIScriptableUnicodeConverter); + converter.charset = "UTF-8"; + let fileContentStream = converter.convertToInputStream(content); + + NetUtil.asyncCopy(fileContentStream, fout, callback); +} + +function readFile(file, callback) +{ + let channel = NetUtil.newChannel({ + uri: NetUtil.newURI(file), + loadUsingSystemPrincipal: true}); + channel.contentType = "application/javascript"; + + NetUtil.asyncFetch(channel, function (inputStream, status) { + ok(Components.isSuccessCode(status), + "file was read successfully"); + + let content = NetUtil.readInputStreamToString(inputStream, + inputStream.available()); + callback(content); + }); +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_contexts.js b/devtools/client/scratchpad/test/browser_scratchpad_contexts.js new file mode 100644 index 000000000..ae1933b4d --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_contexts.js @@ -0,0 +1,149 @@ +/* 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"; + +function test() { + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); + openScratchpad(runTests); + }, true); + + content.location = "data:text/html,test context switch in Scratchpad"; +} + +function runTests() { + let sp = gScratchpadWindow.Scratchpad; + let contentMenu = gScratchpadWindow.document.getElementById("sp-menu-content"); + let chromeMenu = gScratchpadWindow.document.getElementById("sp-menu-browser"); + let notificationBox = sp.notificationBox; + + ok(contentMenu, "found #sp-menu-content"); + ok(chromeMenu, "found #sp-menu-browser"); + ok(notificationBox, "found Scratchpad.notificationBox"); + + let tests = [{ + method: "run", + prepare: function* () { + sp.setContentContext(); + + is(sp.executionContext, gScratchpadWindow.SCRATCHPAD_CONTEXT_CONTENT, + "executionContext is content"); + + is(contentMenu.getAttribute("checked"), "true", + "content menuitem is checked"); + + isnot(chromeMenu.getAttribute("checked"), "true", + "chrome menuitem is not checked"); + + ok(!notificationBox.currentNotification, + "there is no notification in content context"); + + sp.editor.setText("window.foobarBug636725 = 'aloha';"); + + let pageResult = yield inContent(function* () { + return content.wrappedJSObject.foobarBug636725; + }); + ok(!pageResult, "no content.foobarBug636725"); + }, + then: function* () { + is(content.wrappedJSObject.foobarBug636725, "aloha", + "content.foobarBug636725 has been set"); + } + }, { + method: "run", + prepare: function* () { + sp.setBrowserContext(); + + is(sp.executionContext, gScratchpadWindow.SCRATCHPAD_CONTEXT_BROWSER, + "executionContext is chrome"); + + is(chromeMenu.getAttribute("checked"), "true", + "chrome menuitem is checked"); + + isnot(contentMenu.getAttribute("checked"), "true", + "content menuitem is not checked"); + + ok(notificationBox.currentNotification, + "there is a notification in browser context"); + + let [ from, to ] = sp.editor.getPosition(31, 32); + sp.editor.replaceText("2'", from, to); + + is(sp.getText(), "window.foobarBug636725 = 'aloha2';", + "setText() worked"); + }, + then: function* () { + is(window.foobarBug636725, "aloha2", + "window.foobarBug636725 has been set"); + + delete window.foobarBug636725; + ok(!window.foobarBug636725, "no window.foobarBug636725"); + } + }, { + method: "run", + prepare: function* () { + sp.editor.replaceText("gBrowser", sp.editor.getPosition(7)); + + is(sp.getText(), "window.gBrowser", + "setText() worked with no end for the replace range"); + }, + then: function* ([, , result]) { + is(result.class, "XULElement", + "chrome context has access to chrome objects"); + } + }, { + method: "run", + prepare: function* () { + // Check that the sandbox is cached. + sp.editor.setText("typeof foobarBug636725cache;"); + }, + then: function* ([, , result]) { + is(result, "undefined", "global variable does not exist"); + } + }, { + method: "run", + prepare: function* () { + sp.editor.setText("window.foobarBug636725cache = 'foo';" + + "typeof foobarBug636725cache;"); + }, + then: function* ([, , result]) { + is(result, "string", + "global variable exists across two different executions"); + } + }, { + method: "run", + prepare: function* () { + sp.editor.setText("window.foobarBug636725cache2 = 'foo';" + + "typeof foobarBug636725cache2;"); + }, + then: function* ([, , result]) { + is(result, "string", + "global variable exists across two different executions"); + } + }, { + method: "run", + prepare: function* () { + sp.setContentContext(); + + is(sp.executionContext, gScratchpadWindow.SCRATCHPAD_CONTEXT_CONTENT, + "executionContext is content"); + + sp.editor.setText("typeof foobarBug636725cache2;"); + }, + then: function* ([, , result]) { + is(result, "undefined", + "global variable no longer exists after changing the context"); + } + }]; + + runAsyncCallbackTests(sp, tests).then(() => { + sp.setBrowserContext(); + sp.editor.setText("delete foobarBug636725cache;" + + "delete foobarBug636725cache2;"); + sp.run().then(finish); + }); +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_disable_view_menu_items.js b/devtools/client/scratchpad/test/browser_scratchpad_disable_view_menu_items.js new file mode 100644 index 000000000..ed501ce2d --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_disable_view_menu_items.js @@ -0,0 +1,66 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test if the view menu items "Larger Font" and "Smaller Font" are disabled +// when the font size reaches the maximum/minimum values. + +var {Task} = require("devtools/shared/task"); + +function test() { + const options = { + tabContent: 'test if view menu items "Larger Font" and "Smaller Font" are enabled/disabled.' + }; + openTabAndScratchpad(options) + .then(Task.async(runTests)) + .then(finish, console.error); +} + +function* runTests([win, sp]) { + yield testMaximumFontSize(win, sp); + + yield testMinimumFontSize(win, sp); +} + +const MAXIMUM_FONT_SIZE = 96; +const MINIMUM_FONT_SIZE = 6; +const NORMAL_FONT_SIZE = 12; + +var testMaximumFontSize = Task.async(function* (win, sp) { + let doc = win.document; + + Services.prefs.clearUserPref("devtools.scratchpad.editorFontSize"); + + let menu = doc.getElementById("sp-menu-larger-font"); + + for (let i = NORMAL_FONT_SIZE; i <= MAXIMUM_FONT_SIZE; i++) { + menu.doCommand(); + } + + let cmd = doc.getElementById("sp-cmd-larger-font"); + ok(cmd.getAttribute("disabled") === "true", 'Command "sp-cmd-larger-font" is disabled.'); + + menu = doc.getElementById("sp-menu-smaller-font"); + menu.doCommand(); + + ok(cmd.hasAttribute("disabled") === false, 'Command "sp-cmd-larger-font" is enabled.'); +}); + +var testMinimumFontSize = Task.async(function* (win, sp) { + let doc = win.document; + + let menu = doc.getElementById("sp-menu-smaller-font"); + + for (let i = MAXIMUM_FONT_SIZE; i >= MINIMUM_FONT_SIZE; i--) { + menu.doCommand(); + } + + let cmd = doc.getElementById("sp-cmd-smaller-font"); + ok(cmd.getAttribute("disabled") === "true", 'Command "sp-cmd-smaller-font" is disabled.'); + + menu = doc.getElementById("sp-menu-larger-font"); + menu.doCommand(); + + ok(cmd.hasAttribute("disabled") === false, 'Command "sp-cmd-smaller-font" is enabled.'); + + Services.prefs.clearUserPref("devtools.scratchpad.editorFontSize"); +}); diff --git a/devtools/client/scratchpad/test/browser_scratchpad_display_non_error_exceptions.js b/devtools/client/scratchpad/test/browser_scratchpad_display_non_error_exceptions.js new file mode 100644 index 000000000..d1f2cb513 --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_display_non_error_exceptions.js @@ -0,0 +1,110 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* Bug 756681 */ + +function test() +{ + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function browserLoad() { + gBrowser.selectedBrowser.removeEventListener("load", browserLoad, true); + openScratchpad(runTests, {"state":{"text":""}}); + }, true); + + content.location = "data:text/html, test that exceptions are output as " + + "comments correctly in Scratchpad"; +} + +function runTests() +{ + var scratchpad = gScratchpadWindow.Scratchpad; + + var message = "\"Hello World!\""; + var openComment = "\n/*\n"; + var closeComment = "\n*/"; + var error1 = "throw new Error(\"Ouch!\")"; + var error2 = "throw \"A thrown string\""; + var error3 = "throw {}"; + var error4 = "document.body.appendChild(document.body)"; + + let tests = [{ + // Display message + method: "display", + code: message, + result: message + openComment + "Hello World!" + closeComment, + label: "message display output" + }, + { + // Display error1, throw new Error("Ouch") + method: "display", + code: error1, + result: error1 + openComment + + "Exception: Error: Ouch!\n@" + scratchpad.uniqueName + ":1:7" + closeComment, + label: "error display output" + }, + { + // Display error2, throw "A thrown string" + method: "display", + code: error2, + result: error2 + openComment + "Exception: A thrown string" + closeComment, + label: "thrown string display output" + }, + { + // Display error3, throw {} + method: "display", + code: error3, + result: error3 + openComment + "Exception: [object Object]" + closeComment, + label: "thrown object display output" + }, + { + // Display error4, document.body.appendChild(document.body) + method: "display", + code: error4, + result: error4 + openComment + "Exception: HierarchyRequestError: Node cannot be inserted " + + "at the specified point in the hierarchy\n@" + + scratchpad.uniqueName + ":1:0" + closeComment, + label: "Alternative format error display output" + }, + { + // Run message + method: "run", + code: message, + result: message, + label: "message run output" + }, + { + // Run error1, throw new Error("Ouch") + method: "run", + code: error1, + result: error1 + openComment + + "Exception: Error: Ouch!\n@" + scratchpad.uniqueName + ":1:7" + closeComment, + label: "error run output" + }, + { + // Run error2, throw "A thrown string" + method: "run", + code: error2, + result: error2 + openComment + "Exception: A thrown string" + closeComment, + label: "thrown string run output" + }, + { + // Run error3, throw {} + method: "run", + code: error3, + result: error3 + openComment + "Exception: [object Object]" + closeComment, + label: "thrown object run output" + }, + { + // Run error4, document.body.appendChild(document.body) + method: "run", + code: error4, + result: error4 + openComment + "Exception: HierarchyRequestError: Node cannot be inserted " + + "at the specified point in the hierarchy\n@" + + scratchpad.uniqueName + ":1:0" + closeComment, + label: "Alternative format error run output" + }]; + + runAsyncTests(scratchpad, tests).then(finish); +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_display_outputs_errors.js b/devtools/client/scratchpad/test/browser_scratchpad_display_outputs_errors.js new file mode 100644 index 000000000..3855a873d --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_display_outputs_errors.js @@ -0,0 +1,72 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* Bug 690552 */ + +function test() +{ + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function browserLoad() { + gBrowser.selectedBrowser.removeEventListener("load", browserLoad, true); + openScratchpad(runTests, {"state":{"text":""}}); + }, true); + + content.location = "data:text/html,<p>test that exceptions are output as " + + "comments for 'display' and not sent to the console in Scratchpad"; +} + +function runTests() +{ + let scratchpad = gScratchpadWindow.Scratchpad; + + let message = "\"Hello World!\""; + let openComment = "\n/*\n"; + let closeComment = "\n*/"; + let error = "throw new Error(\"Ouch!\")"; + let syntaxError = "("; + + let tests = [{ + method: "display", + code: message, + result: message + openComment + "Hello World!" + closeComment, + label: "message display output" + }, + { + method: "display", + code: error, + result: error + openComment + "Exception: Error: Ouch!\n@" + + scratchpad.uniqueName + ":1:7" + closeComment, + label: "error display output", + }, + { + method: "display", + code: syntaxError, + result: syntaxError + openComment + "Exception: SyntaxError: expected expression, got end of script\n@" + + scratchpad.uniqueName + ":1" + closeComment, + label: "syntaxError display output", + }, + { + method: "run", + code: message, + result: message, + label: "message run output", + }, + { + method: "run", + code: error, + result: error + openComment + "Exception: Error: Ouch!\n@" + + scratchpad.uniqueName + ":1:7" + closeComment, + label: "error run output", + }, + { + method: "run", + code: syntaxError, + result: syntaxError + openComment + "Exception: SyntaxError: expected expression, got end of script\n@" + + scratchpad.uniqueName + ":1" + closeComment, + label: "syntaxError run output", + }]; + + runAsyncTests(scratchpad, tests).then(finish); +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_edit_ui_updates.js b/devtools/client/scratchpad/test/browser_scratchpad_edit_ui_updates.js new file mode 100644 index 000000000..ade87eaac --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_edit_ui_updates.js @@ -0,0 +1,206 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* Bug 699130 */ + +"use strict"; + +var WebConsoleUtils = require("devtools/client/webconsole/utils").Utils; +var DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled"; + +function test() +{ + waitForExplicitFinish(); + gBrowser.selectedTab = gBrowser.addTab(); + Services.prefs.setBoolPref(DEVTOOLS_CHROME_ENABLED, false); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); + openScratchpad(runTests); + }, true); + + content.location = "data:text/html,test Edit menu updates Scratchpad - bug 699130"; +} + +function runTests() +{ + let sp = gScratchpadWindow.Scratchpad; + let doc = gScratchpadWindow.document; + let winUtils = gScratchpadWindow.QueryInterface(Ci.nsIInterfaceRequestor). + getInterface(Ci.nsIDOMWindowUtils); + let OS = Services.appinfo.OS; + + info("will test the Edit menu"); + + let pass = 0; + + sp.setText("bug 699130: hello world! (edit menu)"); + + let editMenu = doc.getElementById("sp-edit-menu"); + ok(editMenu, "the Edit menu"); + let menubar = editMenu.parentNode; + ok(menubar, "menubar found"); + + let editMenuIndex = -1; + for (let i = 0; i < menubar.children.length; i++) { + if (menubar.children[i] === editMenu) { + editMenuIndex = i; + break; + } + } + isnot(editMenuIndex, -1, "Edit menu index is correct"); + + let menuPopup = editMenu.menupopup; + ok(menuPopup, "the Edit menupopup"); + let cutItem = doc.getElementById("menu_cut"); + ok(cutItem, "the Cut menuitem"); + let pasteItem = doc.getElementById("menu_paste"); + ok(pasteItem, "the Paste menuitem"); + + let anchor = doc.documentElement; + let isContextMenu = false; + + let oldVal = sp.editor.getText(); + + let testSelfXss = function (oldVal) { + // Self xss prevention tests (bug 994134) + info("Self xss paste tests"); + is(WebConsoleUtils.usageCount, 0, "Test for usage count getter"); + let notificationbox = doc.getElementById("scratchpad-notificationbox"); + let notification = notificationbox.getNotificationWithValue("selfxss-notification"); + ok(notification, "Self-xss notification shown"); + is(oldVal, sp.editor.getText(), "Paste blocked by self-xss prevention"); + Services.prefs.setIntPref("devtools.selfxss.count", 10); + notificationbox.removeAllNotifications(true); + openMenu(10, 10, firstShow); + }; + + let openMenu = function (aX, aY, aCallback) { + if (!editMenu || OS != "Darwin") { + menuPopup.addEventListener("popupshown", function onPopupShown() { + menuPopup.removeEventListener("popupshown", onPopupShown, false); + executeSoon(aCallback); + }, false); + } + + executeSoon(function () { + if (editMenu) { + if (OS == "Darwin") { + winUtils.forceUpdateNativeMenuAt(editMenuIndex); + executeSoon(aCallback); + } else { + editMenu.open = true; + } + } else { + menuPopup.openPopup(anchor, "overlap", aX, aY, isContextMenu, false); + } + }); + }; + + let closeMenu = function (aCallback) { + if (!editMenu || OS != "Darwin") { + menuPopup.addEventListener("popuphidden", function onPopupHidden() { + menuPopup.removeEventListener("popuphidden", onPopupHidden, false); + executeSoon(aCallback); + }, false); + } + + executeSoon(function () { + if (editMenu) { + if (OS == "Darwin") { + winUtils.forceUpdateNativeMenuAt(editMenuIndex); + executeSoon(aCallback); + } else { + editMenu.open = false; + } + } else { + menuPopup.hidePopup(); + } + }); + }; + + let firstShow = function () { + ok(!cutItem.hasAttribute("disabled"), "cut menuitem is enabled"); + closeMenu(firstHide); + }; + + let firstHide = function () { + sp.editor.setSelection({ line: 0, ch: 0 }, { line: 0, ch: 10 }); + openMenu(11, 11, showAfterSelect); + }; + + let showAfterSelect = function () { + ok(!cutItem.hasAttribute("disabled"), "cut menuitem is enabled after select"); + closeMenu(hideAfterSelect); + }; + + let hideAfterSelect = function () { + sp.editor.on("change", onCut); + waitForFocus(function () { + let selectedText = sp.editor.getSelection(); + ok(selectedText.length > 0, "non-empty selected text will be cut"); + + EventUtils.synthesizeKey("x", {accelKey: true}, gScratchpadWindow); + }, gScratchpadWindow); + }; + + let onCut = function () { + sp.editor.off("change", onCut); + openMenu(12, 12, showAfterCut); + }; + + let showAfterCut = function () { + ok(!cutItem.hasAttribute("disabled"), "cut menuitem is enabled after cut"); + ok(!pasteItem.hasAttribute("disabled"), "paste menuitem is enabled after cut"); + closeMenu(hideAfterCut); + }; + + let hideAfterCut = function () { + waitForFocus(function () { + sp.editor.on("change", onPaste); + EventUtils.synthesizeKey("v", {accelKey: true}, gScratchpadWindow); + }, gScratchpadWindow); + }; + + let onPaste = function () { + sp.editor.off("change", onPaste); + openMenu(13, 13, showAfterPaste); + }; + + let showAfterPaste = function () { + ok(!cutItem.hasAttribute("disabled"), "cut menuitem is enabled after paste"); + ok(!pasteItem.hasAttribute("disabled"), "paste menuitem is enabled after paste"); + closeMenu(hideAfterPaste); + }; + + let hideAfterPaste = function () { + if (pass == 0) { + pass++; + testContextMenu(); + } else { + Services.prefs.clearUserPref(DEVTOOLS_CHROME_ENABLED); + finish(); + } + }; + + let testContextMenu = function () { + info("will test the context menu"); + + editMenu = null; + isContextMenu = true; + + menuPopup = doc.getElementById("scratchpad-text-popup"); + ok(menuPopup, "the context menupopup"); + cutItem = doc.getElementById("cMenu_cut"); + ok(cutItem, "the Cut menuitem"); + pasteItem = doc.getElementById("cMenu_paste"); + ok(pasteItem, "the Paste menuitem"); + + sp.setText("bug 699130: hello world! (context menu)"); + openMenu(10, 10, firstShow); + }; + waitForFocus(function () { + WebConsoleUtils.usageCount = 0; + EventUtils.synthesizeKey("v", {accelKey: true}, gScratchpadWindow); + testSelfXss(oldVal); + }, gScratchpadWindow); +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_eval_func.js b/devtools/client/scratchpad/test/browser_scratchpad_eval_func.js new file mode 100644 index 000000000..3753b5a35 --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_eval_func.js @@ -0,0 +1,86 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function test() +{ + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); + openScratchpad(runTests); + }, true); + + content.location = "data:text/html;charset=utf8,test Scratchpad eval function."; +} + +function reportErrorAndQuit(error) { + DevToolsUtils.reportException("browser_scratchpad_eval_func.js", error); + ok(false); + finish(); +} + +function runTests(sw) +{ + const sp = sw.Scratchpad; + + let foo = "" + function main() { console.log(1); }; + let bar = "var bar = " + (() => { console.log(2); }); + + const fullText = + foo + "\n" + + "\n" + + bar + "\n"; + + sp.setText(fullText); + + // On the function declaration. + sp.editor.setCursor({ line: 0, ch: 18 }); + sp.evalTopLevelFunction() + .then(([text, error, result]) => { + is(text, foo, "Should re-eval foo."); + ok(!error, "Should not have got an error."); + ok(result, "Should have got a result."); + }) + + // On the arrow function. + .then(() => { + sp.editor.setCursor({ line: 2, ch: 18 }); + return sp.evalTopLevelFunction(); + }) + .then(([text, error, result]) => { + is(text, bar.replace("var ", ""), "Should re-eval bar."); + ok(!error, "Should not have got an error."); + ok(result, "Should have got a result."); + }) + + // On the empty line. + .then(() => { + sp.editor.setCursor({ line: 1, ch: 0 }); + return sp.evalTopLevelFunction(); + }) + .then(([text, error, result]) => { + is(text, fullText, + "Should get full text back since we didn't find a specific function."); + ok(!error, "Should not have got an error."); + ok(!result, "Should not have got a result."); + }) + + // Syntax error. + .then(() => { + sp.setText("function {}"); + sp.editor.setCursor({ line: 0, ch: 9 }); + return sp.evalTopLevelFunction(); + }) + .then(([text, error, result]) => { + is(text, "function {}", + "Should get the full text back since there was a parse error."); + ok(!error, "Should not have got an error"); + ok(!result, "Should not have got a result"); + ok(sp.getText().includes("SyntaxError"), + "We should have written the syntax error to the scratchpad."); + }) + + .then(finish, reportErrorAndQuit); +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_execute_print.js b/devtools/client/scratchpad/test/browser_scratchpad_execute_print.js new file mode 100644 index 000000000..029916507 --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_execute_print.js @@ -0,0 +1,116 @@ +/* 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"; + +function test() { + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onTabLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onTabLoad, true); + openScratchpad(runTests); + }, true); + + content.location = "data:text/html,<p>test run() and display() in Scratchpad"; +} + +function runTests() { + let sp = gScratchpadWindow.Scratchpad; + let tests = [{ + method: "run", + prepare: function* () { + yield inContent(function* () { + content.wrappedJSObject.foobarBug636725 = 1; + }); + sp.editor.setText("++window.foobarBug636725"); + }, + then: function* ([code, , result]) { + is(code, sp.getText(), "code is correct"); + + let pageResult = yield inContent(function* () { + return content.wrappedJSObject.foobarBug636725; + }); + is(result, pageResult, + "result is correct"); + + is(sp.getText(), "++window.foobarBug636725", + "run() does not change the editor content"); + + is(pageResult, 2, "run() updated window.foobarBug636725"); + } + }, { + method: "display", + prepare: function* () {}, + then: function* () { + let pageResult = yield inContent(function* () { + return content.wrappedJSObject.foobarBug636725; + }); + is(pageResult, 3, "display() updated window.foobarBug636725"); + + is(sp.getText(), "++window.foobarBug636725\n/*\n3\n*/", + "display() shows evaluation result in the textbox"); + + is(sp.editor.getSelection(), "\n/*\n3\n*/", "getSelection is correct"); + } + }, { + method: "run", + prepare: function* () { + sp.editor.setText("window.foobarBug636725 = 'a';\n" + + "window.foobarBug636725 = 'b';"); + sp.editor.setSelection({ line: 0, ch: 0 }, { line: 0, ch: 29 }); + }, + then: function* ([code, , result]) { + is(code, "window.foobarBug636725 = 'a';", "code is correct"); + is(result, "a", "result is correct"); + + is(sp.getText(), "window.foobarBug636725 = 'a';\n" + + "window.foobarBug636725 = 'b';", + "run() does not change the textbox value"); + + let pageResult = yield inContent(function* () { + return content.wrappedJSObject.foobarBug636725; + }); + is(pageResult, "a", "run() worked for the selected range"); + } + }, { + method: "display", + prepare: function* () { + sp.editor.setText("window.foobarBug636725 = 'c';\n" + + "window.foobarBug636725 = 'b';"); + sp.editor.setSelection({ line: 0, ch: 0 }, { line: 0, ch: 22 }); + }, + then: function* () { + let pageResult = yield inContent(function* () { + return content.wrappedJSObject.foobarBug636725; + }); + is(pageResult, "a", "display() worked for the selected range"); + + is(sp.getText(), "window.foobarBug636725" + + "\n/*\na\n*/" + + " = 'c';\n" + + "window.foobarBug636725 = 'b';", + "display() shows evaluation result in the textbox"); + + is(sp.editor.getSelection(), "\n/*\na\n*/", "getSelection is correct"); + } + }]; + + runAsyncCallbackTests(sp, tests).then(function () { + ok(sp.editor.somethingSelected(), "something is selected"); + sp.editor.dropSelection(); + ok(!sp.editor.somethingSelected(), "something is no longer selected"); + ok(!sp.editor.getSelection(), "getSelection is empty"); + + // Test undo/redo. + sp.editor.setText("foo1"); + sp.editor.setText("foo2"); + is(sp.getText(), "foo2", "editor content updated"); + sp.undo(); + is(sp.getText(), "foo1", "undo() works"); + sp.redo(); + is(sp.getText(), "foo2", "redo() works"); + + finish(); + }); +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_falsy.js b/devtools/client/scratchpad/test/browser_scratchpad_falsy.js new file mode 100644 index 000000000..9eb7efb94 --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_falsy.js @@ -0,0 +1,69 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* Bug 679467 */ + +function test() +{ + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); + openScratchpad(testFalsy); + }, true); + + content.location = "data:text/html,<p>test falsy display() values in Scratchpad"; +} + +function testFalsy() +{ + let scratchpad = gScratchpadWindow.Scratchpad; + verifyFalsies(scratchpad).then(function () { + scratchpad.setBrowserContext(); + verifyFalsies(scratchpad).then(finish); + }); +} + + +function verifyFalsies(scratchpad) +{ + let tests = [{ + method: "display", + code: "undefined", + result: "undefined\n/*\nundefined\n*/", + label: "undefined is displayed" + }, + { + method: "display", + code: "false", + result: "false\n/*\nfalse\n*/", + label: "false is displayed" + }, + { + method: "display", + code: "0", + result: "0\n/*\n0\n*/", + label: "0 is displayed" + }, + { + method: "display", + code: "null", + result: "null\n/*\nnull\n*/", + label: "null is displayed" + }, + { + method: "display", + code: "NaN", + result: "NaN\n/*\nNaN\n*/", + label: "NaN is displayed" + }, + { + method: "display", + code: "''", + result: "''\n/*\n\n*/", + label: "the empty string is displayed" + }]; + + return runAsyncTests(scratchpad, tests); +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_files.js b/devtools/client/scratchpad/test/browser_scratchpad_files.js new file mode 100644 index 000000000..d52718a81 --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_files.js @@ -0,0 +1,119 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Reference to the Scratchpad object. +var gScratchpad; + +// Reference to the temporary nsIFile we will work with. +var gFile; + +// The temporary file content. +var gFileContent = "hello.world('bug636725');"; + +function test() +{ + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); + openScratchpad(runTests); + }, true); + + content.location = "data:text/html,<p>test file open and save in Scratchpad"; +} + +function runTests() +{ + gScratchpad = gScratchpadWindow.Scratchpad; + + createTempFile("fileForBug636725.tmp", gFileContent, function (aStatus, aFile) { + ok(Components.isSuccessCode(aStatus), + "The temporary file was saved successfully"); + + gFile = aFile; + gScratchpad.importFromFile(gFile.QueryInterface(Ci.nsILocalFile), true, + fileImported); + }); +} + +function fileImported(aStatus, aFileContent) +{ + ok(Components.isSuccessCode(aStatus), + "the temporary file was imported successfully with Scratchpad"); + + is(aFileContent, gFileContent, + "received data is correct"); + + is(gScratchpad.getText(), gFileContent, + "the editor content is correct"); + + is(gScratchpad.dirty, false, + "the editor marks imported file as saved"); + + // Save the file after changes. + gFileContent += "// omg, saved!"; + gScratchpad.editor.setText(gFileContent); + + gScratchpad.exportToFile(gFile.QueryInterface(Ci.nsILocalFile), true, true, + fileExported); +} + +function fileExported(aStatus) +{ + ok(Components.isSuccessCode(aStatus), + "the temporary file was exported successfully with Scratchpad"); + + let oldContent = gFileContent; + + // Attempt another file save, with confirmation which returns false. + gFileContent += "// omg, saved twice!"; + gScratchpad.editor.setText(gFileContent); + + let oldConfirm = gScratchpadWindow.confirm; + let askedConfirmation = false; + gScratchpadWindow.confirm = function () { + askedConfirmation = true; + return false; + }; + + gScratchpad.exportToFile(gFile.QueryInterface(Ci.nsILocalFile), false, true, + fileExported2); + + gScratchpadWindow.confirm = oldConfirm; + + ok(askedConfirmation, "exportToFile() asked for overwrite confirmation"); + + gFileContent = oldContent; + + let channel = NetUtil.newChannel({ + uri: NetUtil.newURI(gFile), + loadUsingSystemPrincipal: true}); + channel.contentType = "application/javascript"; + + // Read back the temporary file. + NetUtil.asyncFetch(channel, fileRead); +} + +function fileExported2() +{ + ok(false, "exportToFile() did not cancel file overwrite"); +} + +function fileRead(aInputStream, aStatus) +{ + ok(Components.isSuccessCode(aStatus), + "the temporary file was read back successfully"); + + let updatedContent = + NetUtil.readInputStreamToString(aInputStream, aInputStream.available()); + + is(updatedContent, gFileContent, "file properly updated"); + + // Done! + gFile.remove(false); + gFile = null; + gScratchpad = null; + finish(); +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_goto_line_ui.js b/devtools/client/scratchpad/test/browser_scratchpad_goto_line_ui.js new file mode 100644 index 000000000..34c71ac0c --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_goto_line_ui.js @@ -0,0 +1,43 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* Bug 714942 */ + +function test() +{ + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function browserLoad() { + gBrowser.selectedBrowser.removeEventListener("load", browserLoad, true); + openScratchpad(runTests); + }, true); + + content.location = "data:text/html,<p>test the 'Jump to line' feature in Scratchpad"; +} + +function runTests(aWindow, aScratchpad) +{ + let editor = aScratchpad.editor; + let text = "foobar bug650345\nBug650345 bazbaz\nfoobar omg\ntest"; + editor.setText(text); + editor.setCursor({ line: 0, ch: 0 }); + + let oldPrompt = editor.openDialog; + let desiredValue; + + editor.openDialog = function (text, cb) { + cb(desiredValue); + }; + + desiredValue = 3; + EventUtils.synthesizeKey("J", {accelKey: true}, aWindow); + is(editor.getCursor().line, 2, "line is correct"); + + desiredValue = 2; + EventUtils.synthesizeKey("J", {accelKey: true}, aWindow); + is(editor.getCursor().line, 1, "line is correct (again)"); + + editor.openDialog = oldPrompt; + finish(); +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_help_key.js b/devtools/client/scratchpad/test/browser_scratchpad_help_key.js new file mode 100644 index 000000000..d5383db57 --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_help_key.js @@ -0,0 +1,59 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* Bug 650760 */ + +function test() +{ + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + content.location = "data:text/html,Test keybindings for opening Scratchpad MDN Documentation, bug 650760"; + gBrowser.selectedBrowser.addEventListener("load", function onTabLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onTabLoad, true); + + openScratchpad(runTest); + }, true); +} + +function runTest() +{ + let sp = gScratchpadWindow.Scratchpad; + ok(sp, "Scratchpad object exists in new window"); + ok(sp.editor.hasFocus(), "the editor has focus"); + + let keyid = gScratchpadWindow.document.getElementById("key_openHelp"); + let modifiers = keyid.getAttribute("modifiers"); + + let key = null; + if (keyid.getAttribute("keycode")) + key = keyid.getAttribute("keycode"); + + else if (keyid.getAttribute("key")) + key = keyid.getAttribute("key"); + + isnot(key, null, "Successfully retrieved keycode/key"); + + var aEvent = { + shiftKey: modifiers.match("shift"), + ctrlKey: modifiers.match("ctrl"), + altKey: modifiers.match("alt"), + metaKey: modifiers.match("meta"), + accelKey: modifiers.match("accel") + }; + + info("check that the MDN page is opened on \"F1\""); + let linkClicked = false; + sp.openDocumentationPage = function (event) { linkClicked = true; }; + + EventUtils.synthesizeKey(key, aEvent, gScratchpadWindow); + + is(linkClicked, true, "MDN page will open"); + finishTest(); +} + +function finishTest() +{ + gScratchpadWindow.close(); + finish(); +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_initialization.js b/devtools/client/scratchpad/test/browser_scratchpad_initialization.js new file mode 100644 index 000000000..387bcb08c --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_initialization.js @@ -0,0 +1,50 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled"; + +function test() +{ + waitForExplicitFinish(); + + Services.prefs.setBoolPref(DEVTOOLS_CHROME_ENABLED, false); + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); + + openScratchpad(runTests); + }, true); + + content.location = "data:text/html,initialization test for Scratchpad"; +} + +function runTests() +{ + let sp = gScratchpadWindow.Scratchpad; + ok(sp, "Scratchpad object exists in new window"); + is(typeof sp.run, "function", "Scratchpad.run() exists"); + is(typeof sp.inspect, "function", "Scratchpad.inspect() exists"); + is(typeof sp.display, "function", "Scratchpad.display() exists"); + + let environmentMenu = gScratchpadWindow.document. + getElementById("sp-environment-menu"); + ok(environmentMenu, "Environment menu element exists"); + ok(environmentMenu.hasAttribute("hidden"), + "Environment menu is not visible"); + + let errorConsoleCommand = gScratchpadWindow.document. + getElementById("sp-cmd-errorConsole"); + ok(errorConsoleCommand, "Error console command element exists"); + is(errorConsoleCommand.getAttribute("disabled"), "true", + "Error console command is disabled"); + + let chromeContextCommand = gScratchpadWindow.document. + getElementById("sp-cmd-browserContext"); + ok(chromeContextCommand, "Chrome context command element exists"); + is(chromeContextCommand.getAttribute("disabled"), "true", + "Chrome context command is disabled"); + + Services.prefs.clearUserPref(DEVTOOLS_CHROME_ENABLED); + finish(); +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_inspect.js b/devtools/client/scratchpad/test/browser_scratchpad_inspect.js new file mode 100644 index 000000000..23194f05e --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_inspect.js @@ -0,0 +1,55 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function test() +{ + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); + openScratchpad(runTests); + }, true); + + content.location = "data:text/html;charset=utf8,<p>test inspect() in Scratchpad</p>"; +} + +function runTests() +{ + let sp = gScratchpadWindow.Scratchpad; + + sp.setText("({ a: 'foobarBug636725' })"); + + sp.inspect().then(function () { + let sidebar = sp.sidebar; + ok(sidebar.visible, "sidebar is open"); + + + let found = false; + + outer: for (let scope of sidebar.variablesView) { + for (let [, obj] of scope) { + for (let [, prop] of obj) { + if (prop.name == "a" && prop.value == "foobarBug636725") { + found = true; + break outer; + } + } + } + } + + ok(found, "found the property"); + + let tabbox = sidebar._sidebar._tabbox; + is(tabbox.width, 300, "Scratchpad sidebar width is correct"); + ok(!tabbox.hasAttribute("hidden"), "Scratchpad sidebar visible"); + sidebar.hide(); + ok(tabbox.hasAttribute("hidden"), "Scratchpad sidebar hidden"); + sp.inspect().then(function () { + is(tabbox.width, 300, "Scratchpad sidebar width is still correct"); + ok(!tabbox.hasAttribute("hidden"), "Scratchpad sidebar visible again"); + finish(); + }); + }); +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_inspect_primitives.js b/devtools/client/scratchpad/test/browser_scratchpad_inspect_primitives.js new file mode 100644 index 000000000..914f0a6d8 --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_inspect_primitives.js @@ -0,0 +1,61 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that inspecting primitive values uses the object inspector, not an +// inline comment. + +var {Task} = require("devtools/shared/task"); + +function test() { + const options = { + tabContent: "test inspecting primitive values" + }; + openTabAndScratchpad(options) + .then(Task.async(runTests)) + .then(finish, console.error); +} + +function* runTests([win, sp]) { + // Inspect a number. + yield checkResults(sp, 7); + + // Inspect a string. + yield checkResults(sp, "foobar", true); + + // Inspect a boolean. + yield checkResults(sp, true); +} + +// Helper function that does the actual testing. +var checkResults = Task.async(function* (sp, value, isString = false) { + let sourceValue = value; + if (isString) { + sourceValue = '"' + value + '"'; + } + let source = "var foobar = " + sourceValue + "; foobar"; + sp.setText(source); + yield sp.inspect(); + + let sidebar = sp.sidebar; + ok(sidebar.visible, "sidebar is open"); + + let found = false; + + outer: for (let scope of sidebar.variablesView) { + for (let [, obj] of scope) { + for (let [, prop] of obj) { + if (prop.name == "value" && prop.value == value) { + found = true; + break outer; + } + } + } + } + + ok(found, "found the value of " + value); + + let tabbox = sidebar._sidebar._tabbox; + ok(!tabbox.hasAttribute("hidden"), "Scratchpad sidebar visible"); + sidebar.hide(); + ok(tabbox.hasAttribute("hidden"), "Scratchpad sidebar hidden"); +}); diff --git a/devtools/client/scratchpad/test/browser_scratchpad_long_string.js b/devtools/client/scratchpad/test/browser_scratchpad_long_string.js new file mode 100644 index 000000000..d85a7df1c --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_long_string.js @@ -0,0 +1,30 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function test() +{ + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); + openScratchpad(runTests); + }, true); + + content.location = "data:text/html;charset=utf8,<p>test long string in Scratchpad</p>"; +} + +function runTests() +{ + let sp = gScratchpadWindow.Scratchpad; + + sp.setText("'0'.repeat(10000)"); + + sp.display().then(() => { + is(sp.getText(), "'0'.repeat(10000)\n" + + "/*\n" + "0".repeat(10000) + "\n*/", + "display()ing a long string works"); + finish(); + }); +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_modeline.js b/devtools/client/scratchpad/test/browser_scratchpad_modeline.js new file mode 100644 index 000000000..20a4e8449 --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_modeline.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/ */ +/* Bug 644413 */ + +var gScratchpad; // Reference to the Scratchpad object. +var gFile; // Reference to the temporary nsIFile we will work with. +var DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled"; + +// The temporary file content. +var gFileContent = "function main() { return 0; }"; + +function test() { + waitForExplicitFinish(); + + Services.prefs.setBoolPref(DEVTOOLS_CHROME_ENABLED, false); + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); + openScratchpad(runTests); + }, true); + + content.location = "data:text/html,<p>test file open and save in Scratchpad"; +} + +function runTests() { + gScratchpad = gScratchpadWindow.Scratchpad; + function size(obj) { return Object.keys(obj).length; } + + // Test Scratchpad._scanModeLine method. + let obj = gScratchpad._scanModeLine(); + is(size(obj), 0, "Mode-line object has no properties"); + + obj = gScratchpad._scanModeLine("/* This is not a mode-line comment */"); + is(size(obj), 0, "Mode-line object has no properties"); + + obj = gScratchpad._scanModeLine("/* -sp-context:browser */"); + is(size(obj), 1, "Mode-line object has one property"); + is(obj["-sp-context"], "browser"); + + obj = gScratchpad._scanModeLine("/* -sp-context: browser */"); + is(size(obj), 1, "Mode-line object has one property"); + is(obj["-sp-context"], "browser"); + + obj = gScratchpad._scanModeLine("// -sp-context: browser"); + is(size(obj), 1, "Mode-line object has one property"); + is(obj["-sp-context"], "browser"); + + obj = gScratchpad._scanModeLine("/* -sp-context:browser, other:true */"); + is(size(obj), 2, "Mode-line object has two properties"); + is(obj["-sp-context"], "browser"); + is(obj["other"], "true"); + + // Test importing files with a mode-line in them. + let content = "/* -sp-context:browser */\n" + gFileContent; + createTempFile("fileForBug644413.tmp", content, function (aStatus, aFile) { + ok(Components.isSuccessCode(aStatus), "File was saved successfully"); + + gFile = aFile; + gScratchpad.importFromFile(gFile.QueryInterface(Ci.nsILocalFile), true, fileImported); + }); +} + +function fileImported(status, content) { + ok(Components.isSuccessCode(status), "File was imported successfully"); + + // Since devtools.chrome.enabled is off, Scratchpad should still be in + // the content context. + is(gScratchpad.executionContext, gScratchpadWindow.SCRATCHPAD_CONTEXT_CONTENT); + + // Set the pref and try again. + Services.prefs.setBoolPref(DEVTOOLS_CHROME_ENABLED, true); + + gScratchpad.importFromFile(gFile.QueryInterface(Ci.nsILocalFile), true, function (status, content) { + ok(Components.isSuccessCode(status), "File was imported successfully"); + is(gScratchpad.executionContext, gScratchpadWindow.SCRATCHPAD_CONTEXT_BROWSER); + + gFile.remove(false); + gFile = null; + gScratchpad = null; + finish(); + }); +} + +registerCleanupFunction(function () { + Services.prefs.clearUserPref(DEVTOOLS_CHROME_ENABLED); +}); diff --git a/devtools/client/scratchpad/test/browser_scratchpad_open.js b/devtools/client/scratchpad/test/browser_scratchpad_open.js new file mode 100644 index 000000000..ec55f0101 --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_open.js @@ -0,0 +1,101 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// only finish() when correct number of tests are done +const expected = 4; +var count = 0; +var lastUniqueName = null; + +function done() +{ + if (++count == expected) { + finish(); + } +} + +function test() +{ + waitForExplicitFinish(); + testOpen(); + testOpenWithState(); + testOpenInvalidState(); + testOpenTestFile(); +} + +function testUniqueName(name) +{ + ok(name, "Scratchpad has a uniqueName"); + + if (lastUniqueName === null) { + lastUniqueName = name; + return; + } + + ok(name !== lastUniqueName, + "Unique name for this instance differs from the last one."); +} + +function testOpen() +{ + openScratchpad(function (win) { + is(win.Scratchpad.filename, undefined, "Default filename is undefined"); + isnot(win.Scratchpad.getText(), null, "Default text should not be null"); + is(win.Scratchpad.executionContext, win.SCRATCHPAD_CONTEXT_CONTENT, + "Default execution context is content"); + testUniqueName(win.Scratchpad.uniqueName); + + win.close(); + done(); + }, {noFocus: true}); +} + +function testOpenWithState() +{ + let state = { + filename: "testfile", + executionContext: 2, + text: "test text" + }; + + openScratchpad(function (win) { + is(win.Scratchpad.filename, state.filename, "Filename loaded from state"); + is(win.Scratchpad.executionContext, state.executionContext, "Execution context loaded from state"); + is(win.Scratchpad.getText(), state.text, "Content loaded from state"); + testUniqueName(win.Scratchpad.uniqueName); + + win.close(); + done(); + }, {state: state, noFocus: true}); +} + +function testOpenInvalidState() +{ + let win = openScratchpad(null, {state: 7}); + ok(!win, "no scratchpad opened if state is not an object"); + done(); +} + +function testOpenTestFile() +{ + let win = openScratchpad(function (win) { + ok(win, "scratchpad opened for file open"); + try { + win.Scratchpad.importFromFile( + "http://example.com/browser/devtools/client/scratchpad/test/NS_ERROR_ILLEGAL_INPUT.txt", + "silent", + function (aStatus, content) { + let nb = win.document.querySelector("#scratchpad-notificationbox"); + is(nb.querySelectorAll("notification").length, 1, "There is just one notification"); + let cn = nb.currentNotification; + is(cn.priority, nb.PRIORITY_WARNING_HIGH, "notification priority is correct"); + is(cn.value, "file-import-convert-failed", "notification value is corrent"); + is(cn.type, "warning", "notification type is correct"); + done(); + }); + ok(true, "importFromFile does not cause exception"); + } catch (exception) { + ok(false, "importFromFile causes exception " + DevToolsUtils.safeErrorString(exception)); + } + }, {noFocus: true}); +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_open_error_console.js b/devtools/client/scratchpad/test/browser_scratchpad_open_error_console.js new file mode 100644 index 000000000..4da2a2daf --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_open_error_console.js @@ -0,0 +1,39 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const HUDService = require("devtools/client/webconsole/hudservice"); + +function test() +{ + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); + openScratchpad(runTests); + }, true); + + content.location = "data:text/html;charset=utf8,test Scratchpad." + + "openErrorConsole()"; +} + +function runTests() +{ + Services.obs.addObserver(function observer(aSubject) { + Services.obs.removeObserver(observer, "web-console-created"); + aSubject.QueryInterface(Ci.nsISupportsString); + + let hud = HUDService.getBrowserConsole(); + ok(hud, "browser console is open"); + is(aSubject.data, hud.hudId, "notification hudId is correct"); + + HUDService.toggleBrowserConsole().then(finish); + }, "web-console-created", false); + + let hud = HUDService.getBrowserConsole(); + ok(!hud, "browser console is not open"); + info("wait for the browser console to open from Scratchpad"); + + gScratchpadWindow.Scratchpad.openErrorConsole(); +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_pprint-02.js b/devtools/client/scratchpad/test/browser_scratchpad_pprint-02.js new file mode 100644 index 000000000..c7cd2927e --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_pprint-02.js @@ -0,0 +1,40 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function test() +{ + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); + openScratchpad(runTests); + }, true); + + content.location = "data:text/html;charset=utf8,test Scratchpad pretty print."; +} + +var gTabsize; + +function runTests(sw) +{ + gTabsize = Services.prefs.getIntPref("devtools.editor.tabsize"); + Services.prefs.setIntPref("devtools.editor.tabsize", 6); + const space = " ".repeat(6); + + const sp = sw.Scratchpad; + sp.setText("function main() { console.log(5); }"); + sp.prettyPrint().then(() => { + const prettyText = sp.getText(); + ok(prettyText.includes(space)); + finish(); + }).then(null, error => { + ok(false, error); + }); +} + +registerCleanupFunction(function () { + Services.prefs.setIntPref("devtools.editor.tabsize", gTabsize); + gTabsize = null; +}); diff --git a/devtools/client/scratchpad/test/browser_scratchpad_pprint.js b/devtools/client/scratchpad/test/browser_scratchpad_pprint.js new file mode 100644 index 000000000..1ba9a2cda --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_pprint.js @@ -0,0 +1,29 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function test() +{ + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); + openScratchpad(runTests); + }, true); + + content.location = "data:text/html;charset=utf8,test Scratchpad pretty print."; +} + +function runTests(sw) +{ + const sp = sw.Scratchpad; + sp.setText("function main() { console.log(5); }"); + sp.prettyPrint().then(() => { + const prettyText = sp.getText(); + ok(prettyText.includes("\n")); + finish(); + }).then(null, error => { + ok(false, error); + }); +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_pprint_error_goto_line.js b/devtools/client/scratchpad/test/browser_scratchpad_pprint_error_goto_line.js new file mode 100644 index 000000000..21f266f61 --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_pprint_error_goto_line.js @@ -0,0 +1,78 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2; fill-column: 80 -*- */ +/* 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"; + +function test() +{ + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); + openScratchpad(runTests); + }, true); + + content.location = "data:text/html;charset=utf8," + + "test Scratchpad pretty print error goto line."; +} + +function testJumpToPrettyPrintError(sp, error, remark) { + info("will test jumpToLine after prettyPrint error" + remark); + + // CodeMirror lines and columns are 0-based, Scratchpad UI and error + // stack are 1-based. + is(/Invalid regular expression flag \(3:10\)/.test(error), true, + "prettyPrint expects error in editor text:\n" + error); + + sp.editor.jumpToLine(); + + const editorDoc = sp.editor.container.contentDocument; + const lineInput = editorDoc.querySelector("input"); + const errorLocation = lineInput.value; + const [ inputLine, inputColumn ] = errorLocation.split(":"); + const errorLine = 3, errorColumn = 10; + + is(inputLine, errorLine, + "jumpToLine input field is set from editor selection (line)"); + is(inputColumn, errorColumn, + "jumpToLine input field is set from editor selection (column)"); + + EventUtils.synthesizeKey("VK_RETURN", { }, editorDoc.defaultView); + + // CodeMirror lines and columns are 0-based, Scratchpad UI and error + // stack are 1-based. + const cursor = sp.editor.getCursor(); + is(inputLine, cursor.line + 1, "jumpToLine goto error location (line)"); + is(inputColumn, cursor.ch + 1, "jumpToLine goto error location (column)"); +} + +function runTests(sw, sp) +{ + sp.setText([ + "// line 1", + "// line 2", + "var re = /a bad /regexp/; // line 3 is an obvious syntax error!", + "// line 4", + "// line 5", + "" + ].join("\n")); + + sp.prettyPrint().then(aFulfill => { + ok(false, "Expecting Invalid regexp flag (3:10)"); + finish(); + }, error => { + testJumpToPrettyPrintError(sp, error, " (Bug 1005471, first time)"); + }); + + sp.prettyPrint().then(aFulfill => { + ok(false, "Expecting Invalid regexp flag (3:10)"); + finish(); + }, error => { + // Second time verifies bug in earlier implementation fixed. + testJumpToPrettyPrintError(sp, error, " (second time)"); + finish(); + }); +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_recent_files.js b/devtools/client/scratchpad/test/browser_scratchpad_recent_files.js new file mode 100644 index 000000000..66a4e7cd1 --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_recent_files.js @@ -0,0 +1,350 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* Bug 651942 */ + +// Reference to the Scratchpad object. +var gScratchpad; + +// References to the temporary nsIFiles. +var gFile01; +var gFile02; +var gFile03; +var gFile04; + +// lists of recent files. +var lists = { + recentFiles01: null, + recentFiles02: null, + recentFiles03: null, + recentFiles04: null, +}; + +// Temporary file names. +var gFileName01 = "file01_ForBug651942.tmp"; +var gFileName02 = "☕"; // See bug 783858 for more information +var gFileName03 = "file03_ForBug651942.tmp"; +var gFileName04 = "file04_ForBug651942.tmp"; + +// Content for the temporary files. +var gFileContent; +var gFileContent01 = "hello.world.01('bug651942');"; +var gFileContent02 = "hello.world.02('bug651942');"; +var gFileContent03 = "hello.world.03('bug651942');"; +var gFileContent04 = "hello.world.04('bug651942');"; + +function startTest() +{ + gScratchpad = gScratchpadWindow.Scratchpad; + + gFile01 = createAndLoadTemporaryFile(gFile01, gFileName01, gFileContent01); + gFile02 = createAndLoadTemporaryFile(gFile02, gFileName02, gFileContent02); + gFile03 = createAndLoadTemporaryFile(gFile03, gFileName03, gFileContent03); +} + +// Test to see if the three files we created in the 'startTest()'-method have +// been added to the list of recent files. +function testAddedToRecent() +{ + lists.recentFiles01 = gScratchpad.getRecentFiles(); + + is(lists.recentFiles01.length, 3, + "Temporary files created successfully and added to list of recent files."); + + // Create a 4th file, this should clear the oldest file. + gFile04 = createAndLoadTemporaryFile(gFile04, gFileName04, gFileContent04); +} + +// We have opened a 4th file. Test to see if the oldest recent file was removed, +// and that the other files were reordered successfully. +function testOverwriteRecent() +{ + lists.recentFiles02 = gScratchpad.getRecentFiles(); + + is(lists.recentFiles02[0], lists.recentFiles01[1], + "File02 was reordered successfully in the 'recent files'-list."); + is(lists.recentFiles02[1], lists.recentFiles01[2], + "File03 was reordered successfully in the 'recent files'-list."); + isnot(lists.recentFiles02[2], lists.recentFiles01[2], + "File04: was added successfully."); + + // Open the oldest recent file. + gScratchpad.openFile(0); +} + +// We have opened the "oldest"-recent file. Test to see if it is now the most +// recent file, and that the other files were reordered successfully. +function testOpenOldestRecent() +{ + lists.recentFiles03 = gScratchpad.getRecentFiles(); + + is(lists.recentFiles02[0], lists.recentFiles03[2], + "File04 was reordered successfully in the 'recent files'-list."); + is(lists.recentFiles02[1], lists.recentFiles03[0], + "File03 was reordered successfully in the 'recent files'-list."); + is(lists.recentFiles02[2], lists.recentFiles03[1], + "File02 was reordered successfully in the 'recent files'-list."); + + Services.prefs.setIntPref("devtools.scratchpad.recentFilesMax", 0); +} + +// The "devtools.scratchpad.recentFilesMax"-preference was set to zero (0). +// This should disable the "Open Recent"-menu by hiding it (this should not +// remove any files from the list). Test to see if it's been hidden. +function testHideMenu() +{ + let menu = gScratchpadWindow.document.getElementById("sp-open_recent-menu"); + ok(menu.hasAttribute("hidden"), "The menu was hidden successfully."); + + Services.prefs.setIntPref("devtools.scratchpad.recentFilesMax", 2); +} + +// We have set the recentFilesMax-pref to one (1), this enables the feature, +// removes the two oldest files, rebuilds the menu and removes the +// "hidden"-attribute from it. Test to see if this works. +function testChangedMaxRecent() +{ + let menu = gScratchpadWindow.document.getElementById("sp-open_recent-menu"); + ok(!menu.hasAttribute("hidden"), "The menu is visible. \\o/"); + + lists.recentFiles04 = gScratchpad.getRecentFiles(); + + is(lists.recentFiles04.length, 2, + "Two recent files were successfully removed from the 'recent files'-list"); + + let doc = gScratchpadWindow.document; + let popup = doc.getElementById("sp-menu-open_recentPopup"); + + let menuitemLabel = popup.children[0].getAttribute("label"); + let correctMenuItem = false; + if (menuitemLabel === lists.recentFiles03[2] && + menuitemLabel === lists.recentFiles04[1]) { + correctMenuItem = true; + } + + is(correctMenuItem, true, + "Two recent files were successfully removed from the 'Open Recent'-menu"); + + // We now remove one file from the harddrive and use the recent-menuitem for + // it to make sure the user is notified that the file no longer exists. + // This is tested in testOpenDeletedFile(). + gFile04.remove(false); + + // Make sure the file has been deleted before continuing to avoid + // intermittent oranges. + waitForFileDeletion(); +} + +function waitForFileDeletion() { + if (gFile04.exists()) { + executeSoon(waitForFileDeletion); + return; + } + + gFile04 = null; + gScratchpad.openFile(0); +} + +// By now we should have two recent files stored in the list but one of the +// files should be missing on the harddrive. +function testOpenDeletedFile() { + let doc = gScratchpadWindow.document; + let popup = doc.getElementById("sp-menu-open_recentPopup"); + + is(gScratchpad.getRecentFiles().length, 1, + "The missing file was successfully removed from the list."); + // The number of recent files stored, plus the separator and the + // clearRecentMenuItems-item. + is(popup.children.length, 3, + "The missing file was successfully removed from the menu."); + ok(gScratchpad.notificationBox.currentNotification, + "The notification was successfully displayed."); + is(gScratchpad.notificationBox.currentNotification.label, + gScratchpad.strings.GetStringFromName("fileNoLongerExists.notification"), + "The notification label is correct."); + + gScratchpad.clearRecentFiles(); +} + +// We have cleared the last file. Test to see if the last file was removed, +// the menu is empty and was disabled successfully. +function testClearedAll() +{ + let doc = gScratchpadWindow.document; + let menu = doc.getElementById("sp-open_recent-menu"); + let popup = doc.getElementById("sp-menu-open_recentPopup"); + + is(gScratchpad.getRecentFiles().length, 0, + "All recent files removed successfully."); + is(popup.children.length, 0, "All menuitems removed successfully."); + ok(menu.hasAttribute("disabled"), + "No files in the menu, it was disabled successfully."); + + finishTest(); +} + +function createAndLoadTemporaryFile(aFile, aFileName, aFileContent) +{ + // Create a temporary file. + aFile = FileUtils.getFile("TmpD", [aFileName]); + aFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + // Write the temporary file. + let fout = Cc["@mozilla.org/network/file-output-stream;1"]. + createInstance(Ci.nsIFileOutputStream); + fout.init(aFile.QueryInterface(Ci.nsILocalFile), 0x02 | 0x08 | 0x20, + 0o644, fout.DEFER_OPEN); + + gScratchpad.setFilename(aFile.path); + gScratchpad.importFromFile(aFile.QueryInterface(Ci.nsILocalFile), true, + fileImported); + gScratchpad.saveFile(fileSaved); + + return aFile; +} + +function fileImported(aStatus) +{ + ok(Components.isSuccessCode(aStatus), + "the temporary file was imported successfully with Scratchpad"); +} + +function fileSaved(aStatus) +{ + ok(Components.isSuccessCode(aStatus), + "the temporary file was saved successfully with Scratchpad"); + + checkIfMenuIsPopulated(); +} + +function checkIfMenuIsPopulated() +{ + let doc = gScratchpadWindow.document; + let expectedMenuitemCount = doc.getElementById("sp-menu-open_recentPopup"). + children.length; + // The number of recent files stored, plus the separator and the + // clearRecentMenuItems-item. + let recentFilesPlusExtra = gScratchpad.getRecentFiles().length + 2; + + if (expectedMenuitemCount > 2) { + is(expectedMenuitemCount, recentFilesPlusExtra, + "the recent files menu was populated successfully."); + } +} + +/** + * The PreferenceObserver listens for preference changes while Scratchpad is + * running. + */ +var PreferenceObserver = { + _initialized: false, + + _timesFired: 0, + set timesFired(aNewValue) { + this._timesFired = aNewValue; + }, + get timesFired() { + return this._timesFired; + }, + + init: function PO_init() + { + if (this._initialized) { + return; + } + + this.branch = Services.prefs.getBranch("devtools.scratchpad."); + this.branch.addObserver("", this, false); + this._initialized = true; + }, + + observe: function PO_observe(aMessage, aTopic, aData) + { + if (aTopic != "nsPref:changed") { + return; + } + + switch (this.timesFired) { + case 0: + this.timesFired = 1; + break; + case 1: + this.timesFired = 2; + break; + case 2: + this.timesFired = 3; + testAddedToRecent(); + break; + case 3: + this.timesFired = 4; + testOverwriteRecent(); + break; + case 4: + this.timesFired = 5; + testOpenOldestRecent(); + break; + case 5: + this.timesFired = 6; + testHideMenu(); + break; + case 6: + this.timesFired = 7; + testChangedMaxRecent(); + break; + case 7: + this.timesFired = 8; + testOpenDeletedFile(); + break; + case 8: + this.timesFired = 9; + testClearedAll(); + break; + } + }, + + uninit: function PO_uninit() { + this.branch.removeObserver("", this); + } +}; + +function test() +{ + waitForExplicitFinish(); + + registerCleanupFunction(function () { + gFile01.remove(false); + gFile01 = null; + gFile02.remove(false); + gFile02 = null; + gFile03.remove(false); + gFile03 = null; + // gFile04 was removed earlier. + lists.recentFiles01 = null; + lists.recentFiles02 = null; + lists.recentFiles03 = null; + lists.recentFiles04 = null; + gScratchpad = null; + + PreferenceObserver.uninit(); + Services.prefs.clearUserPref("devtools.scratchpad.recentFilesMax"); + }); + + Services.prefs.setIntPref("devtools.scratchpad.recentFilesMax", 3); + + // Initiate the preference observer after we have set the temporary recent + // files max for this test. + PreferenceObserver.init(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); + openScratchpad(startTest); + }, true); + + content.location = "data:text/html,<p>test recent files in Scratchpad"; +} + +function finishTest() +{ + finish(); +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_reload_and_run.js b/devtools/client/scratchpad/test/browser_scratchpad_reload_and_run.js new file mode 100644 index 000000000..19e360b20 --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_reload_and_run.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/ */ +/* Bug 740948 */ + +var DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled"; +var EDITOR_TEXT = [ + "var evt = new CustomEvent('foo', { bubbles: true });", + "document.body.innerHTML = 'Modified text';", + "window.dispatchEvent(evt);" +].join("\n"); + +function test() +{ + requestLongerTimeout(2); + waitForExplicitFinish(); + Services.prefs.setBoolPref(DEVTOOLS_CHROME_ENABLED, true); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); + openScratchpad(runTests); + }, true); + + content.location = "data:text/html,Scratchpad test for bug 740948"; +} + +function runTests() +{ + let sp = gScratchpadWindow.Scratchpad; + ok(sp, "Scratchpad object exists in new window"); + + // Test that Reload And Run command is enabled in the content + // context and disabled in the browser context. + + let reloadAndRun = gScratchpadWindow.document. + getElementById("sp-cmd-reloadAndRun"); + ok(reloadAndRun, "Reload And Run command exists"); + ok(!reloadAndRun.hasAttribute("disabled"), + "Reload And Run command is enabled"); + + sp.setBrowserContext(); + ok(reloadAndRun.hasAttribute("disabled"), + "Reload And Run command is disabled in the browser context."); + + // Switch back to the content context and run our predefined + // code. This code modifies the body of our document and dispatches + // a custom event 'foo'. We listen to that event and check the + // body to make sure that the page has been reloaded and Scratchpad + // code has been executed. + + sp.setContentContext(); + sp.setText(EDITOR_TEXT); + + let browser = gBrowser.selectedBrowser; + + let deferred = promise.defer(); + browser.addEventListener("DOMWindowCreated", function onWindowCreated() { + browser.removeEventListener("DOMWindowCreated", onWindowCreated, true); + + browser.contentWindow.addEventListener("foo", function onFoo() { + browser.contentWindow.removeEventListener("foo", onFoo, true); + + is(browser.contentWindow.document.body.innerHTML, "Modified text", + "After reloading, HTML is different."); + + Services.prefs.clearUserPref(DEVTOOLS_CHROME_ENABLED); + deferred.resolve(); + }, true); + }, true); + + ok(browser.contentWindow.document.body.innerHTML !== "Modified text", + "Before reloading, HTML is intact."); + sp.reloadAndRun().then(deferred.promise).then(finish); +} + diff --git a/devtools/client/scratchpad/test/browser_scratchpad_remember_view_options.js b/devtools/client/scratchpad/test/browser_scratchpad_remember_view_options.js new file mode 100644 index 000000000..67073c52f --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_remember_view_options.js @@ -0,0 +1,65 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* Bug 1140839 */ + +// Test that view menu items are remembered across scratchpad invocations. +function test() +{ + waitForExplicitFinish(); + + // To test for this bug we open a Scratchpad window and change all + // view menu options. After each change we compare the correspondent + // preference value with the expected value. + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); + openScratchpad(runTests); + }, true); + + content.location = "data:text/html,<title>Bug 1140839</title>" + + "<p>test Scratchpad should remember View options"; +} + +function runTests() +{ + let sp = gScratchpadWindow.Scratchpad; + let doc = gScratchpadWindow.document; + + let testData = [ + {itemMenuId: "sp-menu-line-numbers", prefId: "devtools.scratchpad.lineNumbers", expectedVal: false}, + {itemMenuId: "sp-menu-word-wrap", prefId: "devtools.scratchpad.wrapText", expectedVal: true}, + {itemMenuId: "sp-menu-highlight-trailing-space", prefId: "devtools.scratchpad.showTrailingSpace", expectedVal: true}, + {itemMenuId: "sp-menu-larger-font", prefId: "devtools.scratchpad.editorFontSize", expectedVal: 13}, + {itemMenuId: "sp-menu-normal-size-font", prefId: "devtools.scratchpad.editorFontSize", expectedVal: 12}, + {itemMenuId: "sp-menu-smaller-font", prefId: "devtools.scratchpad.editorFontSize", expectedVal: 11}, + ]; + + testData.forEach(function (data) { + let getPref = getPrefFunction(data.prefId); + + try { + let menu = doc.getElementById(data.itemMenuId); + menu.doCommand(); + let newPreferenceValue = getPref(data.prefId); + ok(newPreferenceValue === data.expectedVal, newPreferenceValue + " !== " + data.expectedVal); + Services.prefs.clearUserPref(data.prefId); + } + catch (exception) { + ok(false, "Exception thrown while executing the command of menuitem #" + data.itemMenuId); + } + }); + + finish(); +} + +function getPrefFunction(preferenceId) +{ + let preferenceType = Services.prefs.getPrefType(preferenceId); + if (preferenceType === Services.prefs.PREF_INT) { + return Services.prefs.getIntPref; + } else if (preferenceType === Services.prefs.PREF_BOOL) { + return Services.prefs.getBoolPref; + } +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_reset_undo.js b/devtools/client/scratchpad/test/browser_scratchpad_reset_undo.js new file mode 100644 index 000000000..a1b60cd33 --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_reset_undo.js @@ -0,0 +1,155 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* Bug 684546 */ + +// Reference to the Scratchpad chrome window object. +var gScratchpadWindow; + +// Reference to the Scratchpad object. +var gScratchpad; + +// Reference to the temporary nsIFile we will work with. +var gFileA; +var gFileB; + +// The temporary file content. +var gFileAContent = "// File A ** Hello World!"; +var gFileBContent = "// File B ** Goodbye All"; + +// Help track if one or both files are saved +var gFirstFileSaved = false; + +function test() +{ + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function browserLoad() { + gBrowser.selectedBrowser.removeEventListener("load", browserLoad, true); + openScratchpad(runTests); + }, true); + + content.location = "data:text/html,<p>test that undo get's reset after file load in Scratchpad"; +} + +function runTests() +{ + gScratchpad = gScratchpadWindow.Scratchpad; + + // Create a temporary file. + gFileA = FileUtils.getFile("TmpD", ["fileAForBug684546.tmp"]); + gFileA.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + gFileB = FileUtils.getFile("TmpD", ["fileBForBug684546.tmp"]); + gFileB.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + // Write the temporary file. + let foutA = Cc["@mozilla.org/network/file-output-stream;1"]. + createInstance(Ci.nsIFileOutputStream); + foutA.init(gFileA.QueryInterface(Ci.nsILocalFile), 0x02 | 0x08 | 0x20, + 0o644, foutA.DEFER_OPEN); + + let foutB = Cc["@mozilla.org/network/file-output-stream;1"]. + createInstance(Ci.nsIFileOutputStream); + foutB.init(gFileB.QueryInterface(Ci.nsILocalFile), 0x02 | 0x08 | 0x20, + 0o644, foutB.DEFER_OPEN); + + let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]. + createInstance(Ci.nsIScriptableUnicodeConverter); + converter.charset = "UTF-8"; + let fileContentStreamA = converter.convertToInputStream(gFileAContent); + let fileContentStreamB = converter.convertToInputStream(gFileBContent); + + NetUtil.asyncCopy(fileContentStreamA, foutA, tempFileSaved); + NetUtil.asyncCopy(fileContentStreamB, foutB, tempFileSaved); +} + +function tempFileSaved(aStatus) +{ + let success = Components.isSuccessCode(aStatus); + + ok(success, "a temporary file was saved successfully"); + + if (!success) + { + finish(); + return; + } + + if (gFirstFileSaved && success) + { + ok((gFirstFileSaved && success), "Both files loaded"); + // Import the file A into Scratchpad. + gScratchpad.importFromFile(gFileA.QueryInterface(Ci.nsILocalFile), true, + fileAImported); + } + gFirstFileSaved = success; +} + +function fileAImported(aStatus, aFileContent) +{ + ok(Components.isSuccessCode(aStatus), + "the temporary file A was imported successfully with Scratchpad"); + + is(aFileContent, gFileAContent, "received data is correct"); + + is(gScratchpad.getText(), gFileAContent, "the editor content is correct"); + + gScratchpad.editor.replaceText("new text", + gScratchpad.editor.getPosition(gScratchpad.getText().length)); + + is(gScratchpad.getText(), gFileAContent + "new text", "text updated correctly"); + gScratchpad.undo(); + is(gScratchpad.getText(), gFileAContent, "undo works"); + gScratchpad.redo(); + is(gScratchpad.getText(), gFileAContent + "new text", "redo works"); + + // Import the file B into Scratchpad. + gScratchpad.importFromFile(gFileB.QueryInterface(Ci.nsILocalFile), true, + fileBImported); +} + +function fileBImported(aStatus, aFileContent) +{ + ok(Components.isSuccessCode(aStatus), + "the temporary file B was imported successfully with Scratchpad"); + + is(aFileContent, gFileBContent, "received data is correct"); + + is(gScratchpad.getText(), gFileBContent, "the editor content is correct"); + + ok(!gScratchpad.editor.canUndo(), "editor cannot undo after load"); + + gScratchpad.undo(); + is(gScratchpad.getText(), gFileBContent, + "the editor content is still correct after undo"); + + gScratchpad.editor.replaceText("new text", + gScratchpad.editor.getPosition(gScratchpad.getText().length)); + is(gScratchpad.getText(), gFileBContent + "new text", "text updated correctly"); + + gScratchpad.undo(); + is(gScratchpad.getText(), gFileBContent, "undo works"); + ok(!gScratchpad.editor.canUndo(), "editor cannot undo after load (again)"); + + gScratchpad.redo(); + is(gScratchpad.getText(), gFileBContent + "new text", "redo works"); + + // Done! + finish(); +} + +registerCleanupFunction(function () { + if (gFileA && gFileA.exists()) + { + gFileA.remove(false); + gFileA = null; + } + if (gFileB && gFileB.exists()) + { + gFileB.remove(false); + gFileB = null; + } + gScratchpad = null; +}); diff --git a/devtools/client/scratchpad/test/browser_scratchpad_restore.js b/devtools/client/scratchpad/test/browser_scratchpad_restore.js new file mode 100644 index 000000000..5890e954f --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_restore.js @@ -0,0 +1,96 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* Call the iterator for each item in the list, + calling the final callback with all the results + after every iterator call has sent its result */ +function asyncMap(items, iterator, callback) +{ + let expected = items.length; + let results = []; + + items.forEach(function (item) { + iterator(item, function (result) { + results.push(result); + if (results.length == expected) { + callback(results); + } + }); + }); +} + +function test() +{ + waitForExplicitFinish(); + testRestore(); +} + +function testRestore() +{ + let states = [ + { + filename: "testfile", + text: "test1", + executionContext: 2 + }, + { + text: "text2", + executionContext: 1 + }, + { + text: "text3", + executionContext: 1 + } + ]; + + asyncMap(states, function (state, done) { + // Open some scratchpad windows + openScratchpad(done, {state: state, noFocus: true}); + }, function (wins) { + // Then save the windows to session store + ScratchpadManager.saveOpenWindows(); + + // Then get their states + let session = ScratchpadManager.getSessionState(); + + // Then close them + wins.forEach(function (win) { + win.close(); + }); + + // Clear out session state for next tests + ScratchpadManager.saveOpenWindows(); + + // Then restore them + let restoredWins = ScratchpadManager.restoreSession(session); + + is(restoredWins.length, 3, "Three scratchad windows restored"); + + asyncMap(restoredWins, function (restoredWin, done) { + openScratchpad(function (aWin) { + let state = aWin.Scratchpad.getState(); + aWin.close(); + done(state); + }, {window: restoredWin, noFocus: true}); + }, function (restoredStates) { + // Then make sure they were restored with the right states + ok(statesMatch(restoredStates, states), + "All scratchpad window states restored correctly"); + + // Yay, we're done! + finish(); + }); + }); +} + +function statesMatch(restoredStates, states) +{ + return states.every(function (state) { + return restoredStates.some(function (restoredState) { + return state.filename == restoredState.filename + && state.text == restoredState.text + && state.executionContext == restoredState.executionContext; + }); + }); +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_revert_to_saved.js b/devtools/client/scratchpad/test/browser_scratchpad_revert_to_saved.js new file mode 100644 index 000000000..92c6c3720 --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_revert_to_saved.js @@ -0,0 +1,134 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* Bug 751744 */ + +// Reference to the Scratchpad object. +var gScratchpad; + +// Reference to the temporary nsIFiles. +var gFile; + +// Temporary file name. +var gFileName = "testFileForBug751744.tmp"; + + +// Content for the temporary file. +var gFileContent = "/* this file is already saved */\n" + + "function foo() { alert('bar') }"; +var gLength = gFileContent.length; + +// Reference to the menu entry. +var menu; + +function startTest() +{ + gScratchpad = gScratchpadWindow.Scratchpad; + menu = gScratchpadWindow.document.getElementById("sp-cmd-revert"); + createAndLoadTemporaryFile(); +} + +function testAfterSaved() { + // Check if the revert menu is disabled as the file is at saved state. + ok(menu.hasAttribute("disabled"), "The revert menu entry is disabled."); + + // chancging the text in the file + gScratchpad.setText(gScratchpad.getText() + "\nfoo();"); + // Checking the text got changed + is(gScratchpad.getText(), gFileContent + "\nfoo();", + "The text changed the first time."); + + // Revert menu now should be enabled. + ok(!menu.hasAttribute("disabled"), + "The revert menu entry is enabled after changing text first time"); + + // reverting back to last saved state. + gScratchpad.revertFile(testAfterRevert); +} + +function testAfterRevert() { + // Check if the file's text got reverted + is(gScratchpad.getText(), gFileContent, + "The text reverted back to original text."); + // The revert menu should be disabled again. + ok(menu.hasAttribute("disabled"), + "The revert menu entry is disabled after reverting."); + + // chancging the text in the file again + gScratchpad.setText(gScratchpad.getText() + "\nalert(foo.toSource());"); + // Saving the file. + gScratchpad.saveFile(testAfterSecondSave); +} + +function testAfterSecondSave() { + // revert menu entry should be disabled. + ok(menu.hasAttribute("disabled"), + "The revert menu entry is disabled after saving."); + + // changing the text. + gScratchpad.setText(gScratchpad.getText() + "\nfoo();"); + + // revert menu entry should get enabled yet again. + ok(!menu.hasAttribute("disabled"), + "The revert menu entry is enabled after changing text third time"); + + // reverting back to last saved state. + gScratchpad.revertFile(testAfterSecondRevert); +} + +function testAfterSecondRevert() { + // Check if the file's text got reverted + is(gScratchpad.getText(), gFileContent + "\nalert(foo.toSource());", + "The text reverted back to the changed saved text."); + // The revert menu should be disabled again. + ok(menu.hasAttribute("disabled"), + "Revert menu entry is disabled after reverting to changed saved state."); + gFile.remove(false); + gFile = gScratchpad = menu = null; + finish(); +} + +function createAndLoadTemporaryFile() +{ + // Create a temporary file. + gFile = FileUtils.getFile("TmpD", [gFileName]); + gFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + + // Write the temporary file. + let fout = Cc["@mozilla.org/network/file-output-stream;1"]. + createInstance(Ci.nsIFileOutputStream); + fout.init(gFile.QueryInterface(Ci.nsILocalFile), 0x02 | 0x08 | 0x20, + 0o644, fout.DEFER_OPEN); + + let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]. + createInstance(Ci.nsIScriptableUnicodeConverter); + converter.charset = "UTF-8"; + let fileContentStream = converter.convertToInputStream(gFileContent); + + NetUtil.asyncCopy(fileContentStream, fout, tempFileSaved); +} + +function tempFileSaved(aStatus) +{ + ok(Components.isSuccessCode(aStatus), + "the temporary file was saved successfully"); + + // Import the file into Scratchpad. + gScratchpad.setFilename(gFile.path); + gScratchpad.importFromFile(gFile.QueryInterface(Ci.nsILocalFile), true, + testAfterSaved); +} + +function test() +{ + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); + openScratchpad(startTest); + }, true); + + content.location = "data:text/html,<p>test reverting to last saved state of" + + " a file </p>"; +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_run_error_goto_line.js b/devtools/client/scratchpad/test/browser_scratchpad_run_error_goto_line.js new file mode 100644 index 000000000..a5d3d5163 --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_run_error_goto_line.js @@ -0,0 +1,60 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2; fill-column: 80 -*- */ +/* 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"; + +function test() +{ + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); + openScratchpad(runTests); + }, true); + + content.location = "data:text/html;charset=utf8,test Scratchpad pretty print."; +} + +function runTests(sw) +{ + const sp = sw.Scratchpad; + sp.setText([ + "// line 1", + "// line 2", + "var re = /a bad /regexp/; // line 3 is an obvious syntax error!", + "// line 4", + "// line 5", + "" + ].join("\n")); + sp.run().then(() => { + // CodeMirror lines and columns are 0-based, Scratchpad UI and error + // stack are 1-based. + let errorLine = 3; + let editorDoc = sp.editor.container.contentDocument; + sp.editor.jumpToLine(); + let lineInput = editorDoc.querySelector("input"); + let inputLine = lineInput.value; + is(inputLine, errorLine, "jumpToLine input field is set from editor selection"); + EventUtils.synthesizeKey("VK_RETURN", { }, editorDoc.defaultView); + // CodeMirror lines and columns are 0-based, Scratchpad UI and error + // stack are 1-based. + let cursor = sp.editor.getCursor(); + is(cursor.line + 1, inputLine, "jumpToLine goto error location (line)"); + is(cursor.ch + 1, 1, "jumpToLine goto error location (column)"); + }, error => { + ok(false, error); + finish(); + }).then(() => { + var statusBarField = sp.editor.container.ownerDocument.querySelector("#statusbar-line-col"); + let { line, ch } = sp.editor.getCursor(); + is(statusBarField.textContent, sp.strings.formatStringFromName( + "scratchpad.statusBarLineCol", [ line + 1, ch + 1], 2), "statusbar text is correct (" + statusBarField.textContent + ")"); + finish(); + }, error => { + ok(false, error); + finish(); + }); +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_tab.js b/devtools/client/scratchpad/test/browser_scratchpad_tab.js new file mode 100644 index 000000000..67057f206 --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_tab.js @@ -0,0 +1,75 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* Bug 660560 */ + +function test() +{ + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onTabLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onTabLoad, true); + + Services.prefs.setIntPref("devtools.editor.tabsize", 5); + + openScratchpad(runTests); + }, true); + + content.location = "data:text/html,Scratchpad test for the Tab key, bug 660560"; +} + +function runTests() +{ + let sp = gScratchpadWindow.Scratchpad; + ok(sp, "Scratchpad object exists in new window"); + + ok(sp.editor.hasFocus(), "the editor has focus"); + + sp.setText("window.foo;"); + sp.editor.setCursor({ line: 0, ch: 0 }); + + EventUtils.synthesizeKey("VK_TAB", {}, gScratchpadWindow); + + is(sp.getText(), " window.foo;", "Tab key added 5 spaces"); + + is(sp.editor.getCursor().line, 0, "line is correct"); + is(sp.editor.getCursor().ch, 5, "character is correct"); + + sp.editor.setCursor({ line: 0, ch: 6 }); + + EventUtils.synthesizeKey("VK_TAB", {}, gScratchpadWindow); + + is(sp.getText(), " w indow.foo;", + "Tab key added 4 spaces"); + + is(sp.editor.getCursor().line, 0, "line is correct"); + is(sp.editor.getCursor().ch, 10, "character is correct"); + + gScratchpadWindow.close(); + + Services.prefs.setIntPref("devtools.editor.tabsize", 6); + Services.prefs.setBoolPref("devtools.editor.expandtab", false); + + openScratchpad(runTests2); +} + +function runTests2() +{ + let sp = gScratchpadWindow.Scratchpad; + + sp.setText("window.foo;"); + sp.editor.setCursor({ line: 0, ch: 0 }); + + EventUtils.synthesizeKey("VK_TAB", {}, gScratchpadWindow); + + is(sp.getText(), "\twindow.foo;", "Tab key added the tab character"); + + is(sp.editor.getCursor().line, 0, "line is correct"); + is(sp.editor.getCursor().ch, 1, "character is correct"); + + Services.prefs.clearUserPref("devtools.editor.tabsize"); + Services.prefs.clearUserPref("devtools.editor.expandtab"); + + finish(); +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_tab_switch.js b/devtools/client/scratchpad/test/browser_scratchpad_tab_switch.js new file mode 100644 index 000000000..c2419a1e1 --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_tab_switch.js @@ -0,0 +1,103 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var tab1; +var tab2; +var sp; + +function test() +{ + waitForExplicitFinish(); + + tab1 = gBrowser.addTab(); + gBrowser.selectedTab = tab1; + gBrowser.selectedBrowser.addEventListener("load", function onLoad1() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad1, true); + + tab2 = gBrowser.addTab(); + gBrowser.selectedTab = tab2; + gBrowser.selectedBrowser.addEventListener("load", function onLoad2() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad2, true); + openScratchpad(runTests); + }, true); + content.location = "data:text/html,test context switch in Scratchpad tab 2"; + }, true); + + content.location = "data:text/html,test context switch in Scratchpad tab 1"; +} + +function runTests() +{ + sp = gScratchpadWindow.Scratchpad; + + let contentMenu = gScratchpadWindow.document.getElementById("sp-menu-content"); + let browserMenu = gScratchpadWindow.document.getElementById("sp-menu-browser"); + let notificationBox = sp.notificationBox; + + ok(contentMenu, "found #sp-menu-content"); + ok(browserMenu, "found #sp-menu-browser"); + ok(notificationBox, "found Scratchpad.notificationBox"); + + sp.setContentContext(); + + is(sp.executionContext, gScratchpadWindow.SCRATCHPAD_CONTEXT_CONTENT, + "executionContext is content"); + + is(contentMenu.getAttribute("checked"), "true", + "content menuitem is checked"); + + isnot(browserMenu.getAttribute("checked"), "true", + "chrome menuitem is not checked"); + + is(notificationBox.currentNotification, null, + "there is no notification currently shown for content context"); + + sp.setText("window.foosbug653108 = 'aloha';"); + + ok(!content.wrappedJSObject.foosbug653108, + "no content.foosbug653108"); + + sp.run().then(function () { + is(content.wrappedJSObject.foosbug653108, "aloha", + "content.foosbug653108 has been set"); + + gBrowser.tabContainer.addEventListener("TabSelect", runTests2, true); + gBrowser.selectedTab = tab1; + }); +} + +function runTests2() { + gBrowser.tabContainer.removeEventListener("TabSelect", runTests2, true); + + ok(!window.foosbug653108, "no window.foosbug653108"); + + sp.setText("window.foosbug653108"); + sp.run().then(function ([, , result]) { + isnot(result, "aloha", "window.foosbug653108 is not aloha"); + + sp.setText("window.foosbug653108 = 'ahoyhoy';"); + sp.run().then(function () { + is(content.wrappedJSObject.foosbug653108, "ahoyhoy", + "content.foosbug653108 has been set 2"); + + gBrowser.selectedBrowser.addEventListener("load", runTests3, true); + content.location = "data:text/html,test context switch in Scratchpad location 2"; + }); + }); +} + +function runTests3() { + gBrowser.selectedBrowser.removeEventListener("load", runTests3, true); + // Check that the sandbox is not cached. + + sp.setText("typeof foosbug653108;"); + sp.run().then(function ([, , result]) { + is(result, "undefined", "global variable does not exist"); + + tab1 = null; + tab2 = null; + sp = null; + finish(); + }); +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_throw_output.js b/devtools/client/scratchpad/test/browser_scratchpad_throw_output.js new file mode 100644 index 000000000..cfcd5e049 --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_throw_output.js @@ -0,0 +1,52 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + + +function test() +{ + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); + openScratchpad(testThrowOutput); + }, true); + + content.location = "data:text/html;charset=utf8,<p>Test throw outputs in Scratchpad</p>"; +} + +function testThrowOutput() +{ + let scratchpad = gScratchpadWindow.Scratchpad, tests = []; + + let falsyValues = ["false", "0", "-0", "null", "undefined", "Infinity", + "-Infinity", "NaN"]; + falsyValues.forEach(function (value) { + tests.push({ + method: "display", + code: "throw " + value + ";", + result: "throw " + value + ";\n/*\nException: " + value + "\n*/", + label: "Correct exception message for '" + value + "' is shown" + }); + }); + + let { DebuggerServer } = require("devtools/server/main"); + + let longLength = DebuggerServer.LONG_STRING_LENGTH + 1; + let longString = new Array(longLength).join("a"); + let shortedString = longString.substring(0, + DebuggerServer.LONG_STRING_INITIAL_LENGTH) + "\u2026"; + + tests.push({ + method: "display", + code: "throw (new Array(" + longLength + ").join('a'));", + result: "throw (new Array(" + longLength + ").join('a'));\n" + + "/*\nException: " + shortedString + "\n*/", + label: "Correct exception message for a longString is shown" + }); + + runAsyncTests(scratchpad, tests).then(function () { + finish(); + }); +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_ui.js b/devtools/client/scratchpad/test/browser_scratchpad_ui.js new file mode 100644 index 000000000..5d8af1068 --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_ui.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/ */ + +function test() +{ + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); + openScratchpad(runTests); + }, true); + + content.location = "data:text/html,<title>foobarBug636725</title>" + + "<p>test inspect() in Scratchpad"; +} + +function runTests() +{ + let sp = gScratchpadWindow.Scratchpad; + let doc = gScratchpadWindow.document; + + let methodsAndItems = { + "sp-menu-newscratchpad": "openScratchpad", + "sp-menu-open": "openFile", + "sp-menu-save": "saveFile", + "sp-menu-saveas": "saveFileAs", + "sp-text-run": "run", + "sp-text-inspect": "inspect", + "sp-text-display": "display", + "sp-text-reloadAndRun": "reloadAndRun", + "sp-menu-content": "setContentContext", + "sp-menu-browser": "setBrowserContext", + "sp-menu-pprint": "prettyPrint", + "sp-menu-line-numbers": "toggleEditorOption", + "sp-menu-word-wrap": "toggleEditorOption", + "sp-menu-highlight-trailing-space": "toggleEditorOption", + "sp-menu-larger-font": "increaseFontSize", + "sp-menu-smaller-font": "decreaseFontSize", + "sp-menu-normal-size-font": "normalFontSize", + }; + + let lastMethodCalled = null; + + for (let id in methodsAndItems) { + lastMethodCalled = null; + + let methodName = methodsAndItems[id]; + let oldMethod = sp[methodName]; + ok(oldMethod, "found method " + methodName + " in Scratchpad object"); + + sp[methodName] = () => { + lastMethodCalled = methodName; + }; + + let menu = doc.getElementById(id); + ok(menu, "found menuitem #" + id); + + try { + menu.doCommand(); + } + catch (ex) { + ok(false, "exception thrown while executing the command of menuitem #" + id); + } + + ok(lastMethodCalled == methodName, + "method " + methodName + " invoked by the associated menuitem"); + + sp[methodName] = oldMethod; + } + + finish(); +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_unsaved.js b/devtools/client/scratchpad/test/browser_scratchpad_unsaved.js new file mode 100644 index 000000000..54b97b75b --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_unsaved.js @@ -0,0 +1,119 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* Bug 669612 */ + +// only finish() when correct number of tests are done +const expected = 4; +var count = 0; +function done() +{ + if (++count == expected) { + finish(); + } +} + + +function test() +{ + waitForExplicitFinish(); + + testListeners(); + testRestoreNotFromFile(); + testRestoreFromFileSaved(); + testRestoreFromFileUnsaved(); + + gBrowser.selectedTab = gBrowser.addTab(); + content.location = "data:text/html,<p>test star* UI for unsaved file changes"; +} + +function testListeners() +{ + openScratchpad(function (aWin, aScratchpad) { + aScratchpad.setText("new text"); + ok(isStar(aWin), "show star if scratchpad text changes"); + + aScratchpad.dirty = false; + ok(!isStar(aWin), "no star before changing text"); + + aScratchpad.setFilename("foo.js"); + aScratchpad.setText("new text2"); + ok(isStar(aWin), "shows star if scratchpad text changes"); + + aScratchpad.dirty = false; + ok(!isStar(aWin), "no star if scratchpad was just saved"); + + aScratchpad.setText("new text3"); + ok(isStar(aWin), "shows star if scratchpad has more changes"); + + aScratchpad.undo(); + ok(!isStar(aWin), "no star if scratchpad undo to save point"); + + aScratchpad.undo(); + ok(isStar(aWin), "star if scratchpad undo past save point"); + + aWin.close(); + done(); + }, {noFocus: true}); +} + +function testRestoreNotFromFile() +{ + let session = [{ + text: "test1", + executionContext: 1 + }]; + + let [win] = ScratchpadManager.restoreSession(session); + openScratchpad(function (aWin, aScratchpad) { + aScratchpad.setText("new text"); + ok(isStar(win), "show star if restored scratchpad isn't from a file"); + + win.close(); + done(); + }, {window: win, noFocus: true}); +} + +function testRestoreFromFileSaved() +{ + let session = [{ + filename: "test.js", + text: "test1", + executionContext: 1, + saved: true + }]; + + let [win] = ScratchpadManager.restoreSession(session); + openScratchpad(function (aWin, aScratchpad) { + ok(!isStar(win), "no star before changing text in scratchpad restored from file"); + + aScratchpad.setText("new text"); + ok(isStar(win), "star when text changed from scratchpad restored from file"); + + win.close(); + done(); + }, {window: win, noFocus: true}); +} + +function testRestoreFromFileUnsaved() +{ + let session = [{ + filename: "test.js", + text: "test1", + executionContext: 1, + saved: false + }]; + + let [win] = ScratchpadManager.restoreSession(session); + openScratchpad(function () { + ok(isStar(win), "star with scratchpad restored with unsaved text"); + + win.close(); + done(); + }, {window: win, noFocus: true}); +} + +function isStar(win) +{ + return win.document.title.match(/^\*[^\*]/); +} diff --git a/devtools/client/scratchpad/test/browser_scratchpad_wrong_window_focus.js b/devtools/client/scratchpad/test/browser_scratchpad_wrong_window_focus.js new file mode 100644 index 000000000..0d094ba98 --- /dev/null +++ b/devtools/client/scratchpad/test/browser_scratchpad_wrong_window_focus.js @@ -0,0 +1,93 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* Bug 661762 */ + +// Use the old webconsole since scratchpad focus isn't working on new one (Bug 1304794) +Services.prefs.setBoolPref("devtools.webconsole.new-frontend-enabled", false); +registerCleanupFunction(function* () { + Services.prefs.clearUserPref("devtools.webconsole.new-frontend-enabled"); +}); + +function test() +{ + waitForExplicitFinish(); + + // To test for this bug we open a Scratchpad window, save its + // reference and then open another one. This way the first window + // loses its focus. + // + // Then we open a web console and execute a console.log statement + // from the first Scratch window (that's why we needed to save its + // reference). + // + // Then we wait for our message to appear in the console and click + // on the location link. After that we check which Scratchpad window + // is currently active (it should be the older one). + + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener("load", function onLoad() { + gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); + + openScratchpad(function () { + let sw = gScratchpadWindow; + let {require} = Cu.import("resource://devtools/shared/Loader.jsm", {}); + let {TargetFactory} = require("devtools/client/framework/target"); + + openScratchpad(function () { + let target = TargetFactory.forTab(gBrowser.selectedTab); + gDevTools.showToolbox(target, "webconsole").then((toolbox) => { + let hud = toolbox.getCurrentPanel().hud; + hud.jsterm.clearOutput(true); + testFocus(sw, hud); + }); + }); + }); + }, true); + + content.location = "data:text/html;charset=utf8,<p>test window focus for Scratchpad."; +} + +function testFocus(sw, hud) { + let sp = sw.Scratchpad; + + function onMessage(event, messages) { + let msg = [...messages][0]; + let node = msg.node; + + var loc = node.querySelector(".frame-link"); + ok(loc, "location element exists"); + is(loc.getAttribute("data-url"), sw.Scratchpad.uniqueName, "location value is correct"); + is(loc.getAttribute("data-line"), "1", "line value is correct"); + is(loc.getAttribute("data-column"), "1", "column value is correct"); + + sw.addEventListener("focus", function onFocus() { + sw.removeEventListener("focus", onFocus, true); + + let win = Services.wm.getMostRecentWindow("devtools:scratchpad"); + + ok(win, "there are active Scratchpad windows"); + is(win.Scratchpad.uniqueName, sw.Scratchpad.uniqueName, + "correct window is in focus"); + + // gScratchpadWindow will be closed automatically but we need to + // close the second window ourselves. + sw.close(); + finish(); + }, true); + + // Simulate a click on the "Scratchpad/N:1" link. + EventUtils.synthesizeMouse(loc, 2, 2, {}, hud.iframeWindow); + } + + // Sending messages to web console is an asynchronous operation. That's + // why we have to setup an observer here. + hud.ui.once("new-messages", onMessage); + + sp.setText("console.log('foo');"); + sp.run().then(function ([selection, error, result]) { + is(selection, "console.log('foo');", "selection is correct"); + is(error, undefined, "error is correct"); + is(result.type, "undefined", "result is correct"); + }); +} diff --git a/devtools/client/scratchpad/test/head.js b/devtools/client/scratchpad/test/head.js new file mode 100644 index 000000000..15619a169 --- /dev/null +++ b/devtools/client/scratchpad/test/head.js @@ -0,0 +1,221 @@ +/* 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"; + +const {NetUtil} = Cu.import("resource://gre/modules/NetUtil.jsm", {}); +const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", {}); +const {console} = Cu.import("resource://gre/modules/Console.jsm", {}); +const {ScratchpadManager} = Cu.import("resource://devtools/client/scratchpad/scratchpad-manager.jsm", {}); +const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {}); +const Services = require("Services"); +const DevToolsUtils = require("devtools/shared/DevToolsUtils"); +const flags = require("devtools/shared/flags"); +const promise = require("promise"); + + +var gScratchpadWindow; // Reference to the Scratchpad chrome window object + +flags.testing = true; +SimpleTest.registerCleanupFunction(() => { + flags.testing = false; +}); + +/** + * Open a Scratchpad window. + * + * @param function aReadyCallback + * Optional. The function you want invoked when the Scratchpad instance + * is ready. + * @param object aOptions + * Optional. Options for opening the scratchpad: + * - window + * Provide this if there's already a Scratchpad window you want to wait + * loading for. + * - state + * Scratchpad state object. This is used when Scratchpad is open. + * - noFocus + * Boolean that tells you do not want the opened window to receive + * focus. + * @return nsIDOMWindow + * The new window object that holds Scratchpad. Note that the + * gScratchpadWindow global is also updated to reference the new window + * object. + */ +function openScratchpad(aReadyCallback, aOptions = {}) +{ + let win = aOptions.window || + ScratchpadManager.openScratchpad(aOptions.state); + if (!win) { + return; + } + + let onLoad = function () { + win.removeEventListener("load", onLoad, false); + + win.Scratchpad.addObserver({ + onReady: function (aScratchpad) { + aScratchpad.removeObserver(this); + + if (aOptions.noFocus) { + aReadyCallback(win, aScratchpad); + } else { + waitForFocus(aReadyCallback.bind(null, win, aScratchpad), win); + } + } + }); + }; + + if (aReadyCallback) { + win.addEventListener("load", onLoad, false); + } + + gScratchpadWindow = win; + return gScratchpadWindow; +} + +/** + * Open a new tab and then open a scratchpad. + * @param object aOptions + * Optional. Options for opening the tab and scratchpad. In addition + * to the options supported by openScratchpad, the following options + * are supported: + * - tabContent + * A string providing the html content of the tab. + * @return Promise + */ +function openTabAndScratchpad(aOptions = {}) +{ + waitForExplicitFinish(); + return new promise(resolve => { + gBrowser.selectedTab = gBrowser.addTab(); + let {selectedBrowser} = gBrowser; + selectedBrowser.addEventListener("load", function onLoad() { + selectedBrowser.removeEventListener("load", onLoad, true); + openScratchpad((win, sp) => resolve([win, sp]), aOptions); + }, true); + content.location = "data:text/html;charset=utf8," + (aOptions.tabContent || ""); + }); +} + +/** + * Create a temporary file, write to it and call a callback + * when done. + * + * @param string aName + * Name of your temporary file. + * @param string aContent + * Temporary file's contents. + * @param function aCallback + * Optional callback to be called when we're done writing + * to the file. It will receive two parameters: status code + * and a file object. + */ +function createTempFile(aName, aContent, aCallback = function () {}) +{ + // Create a temporary file. + let file = FileUtils.getFile("TmpD", [aName]); + file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8)); + + // Write the temporary file. + let fout = Cc["@mozilla.org/network/file-output-stream;1"]. + createInstance(Ci.nsIFileOutputStream); + fout.init(file.QueryInterface(Ci.nsILocalFile), 0x02 | 0x08 | 0x20, + parseInt("644", 8), fout.DEFER_OPEN); + + let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]. + createInstance(Ci.nsIScriptableUnicodeConverter); + converter.charset = "UTF-8"; + let fileContentStream = converter.convertToInputStream(aContent); + + NetUtil.asyncCopy(fileContentStream, fout, function (aStatus) { + aCallback(aStatus, file); + }); +} + +/** + * Run a set of asychronous tests sequentially defined by input and output. + * + * @param Scratchpad aScratchpad + * The scratchpad to use in running the tests. + * @param array aTests + * An array of test objects, each with the following properties: + * - method + * Scratchpad method to use, one of "run", "display", or "inspect". + * - code + * Code to run in the scratchpad. + * - result + * Expected code that will be in the scratchpad upon completion. + * - label + * The tests label which will be logged in the test runner output. + * @return Promise + * The promise that will be resolved when all tests are finished. + */ +function runAsyncTests(aScratchpad, aTests) +{ + let deferred = promise.defer(); + + (function runTest() { + if (aTests.length) { + let test = aTests.shift(); + aScratchpad.setText(test.code); + aScratchpad[test.method]().then(function success() { + is(aScratchpad.getText(), test.result, test.label); + runTest(); + }, function failure(error) { + ok(false, error.stack + " " + test.label); + runTest(); + }); + } else { + deferred.resolve(); + } + })(); + + return deferred.promise; +} + +/** + * Run a set of asychronous tests sequentially with callbacks to prepare each + * test and to be called when the test result is ready. + * + * @param Scratchpad aScratchpad + * The scratchpad to use in running the tests. + * @param array aTests + * An array of test objects, each with the following properties: + * - method + * Scratchpad method to use, one of "run", "display", or "inspect". + * - prepare + * The callback to run just prior to executing the scratchpad method. + * - then + * The callback to run when the scratchpad execution promise resolves. + * @return Promise + * The promise that will be resolved when all tests are finished. + */ +var runAsyncCallbackTests = Task.async(function* (aScratchpad, aTests) { + for (let {prepare, method, then} of aTests) { + yield prepare(); + let res = yield aScratchpad[method](); + yield then(res); + } +}); + +/** + * A simple wrapper for ContentTask.spawn for more compact code. + */ +function inContent(generator) { + return ContentTask.spawn(gBrowser.selectedBrowser, {}, generator); +} + +function cleanup() +{ + if (gScratchpadWindow) { + gScratchpadWindow.close(); + gScratchpadWindow = null; + } + while (gBrowser.tabs.length > 1) { + gBrowser.removeCurrentTab(); + } +} + +registerCleanupFunction(cleanup); |