summaryrefslogtreecommitdiffstats
path: root/devtools/client/scratchpad/test
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /devtools/client/scratchpad/test
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'devtools/client/scratchpad/test')
-rw-r--r--devtools/client/scratchpad/test/.eslintrc.js6
-rw-r--r--devtools/client/scratchpad/test/NS_ERROR_ILLEGAL_INPUT.txt2
-rw-r--r--devtools/client/scratchpad/test/browser.ini46
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_autocomplete.js66
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_browser_last_window_closing.js79
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_chrome_context_pref.js50
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_close_toolbox.js38
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_confirm_close.js230
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_contexts.js149
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_disable_view_menu_items.js66
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_display_non_error_exceptions.js110
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_display_outputs_errors.js72
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_edit_ui_updates.js206
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_eval_func.js86
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_execute_print.js116
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_falsy.js69
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_files.js119
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_goto_line_ui.js43
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_help_key.js59
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_initialization.js50
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_inspect.js55
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_inspect_primitives.js61
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_long_string.js30
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_modeline.js87
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_open.js101
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_open_error_console.js39
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_pprint-02.js40
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_pprint.js29
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_pprint_error_goto_line.js78
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_recent_files.js350
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_reload_and_run.js76
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_remember_view_options.js65
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_reset_undo.js155
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_restore.js96
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_revert_to_saved.js134
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_run_error_goto_line.js60
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_tab.js75
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_tab_switch.js103
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_throw_output.js52
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_ui.js74
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_unsaved.js119
-rw-r--r--devtools/client/scratchpad/test/browser_scratchpad_wrong_window_focus.js93
-rw-r--r--devtools/client/scratchpad/test/head.js221
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);