--- a/devtools/client/shared/inplace-editor.js
+++ b/devtools/client/shared/inplace-editor.js
@@ -295,21 +295,21 @@ function InplaceEditor(options, event) {
this.validate = options.validate;
if (this.validate) {
this.input.addEventListener("keyup", this._onKeyup, false);
}
this._updateSize();
+ EventEmitter.decorate(this);
+
if (options.start) {
options.start(this, event);
}
-
- EventEmitter.decorate(this);
}
exports.InplaceEditor = InplaceEditor;
InplaceEditor.CONTENT_TYPES = CONTENT_TYPES;
InplaceEditor.prototype = {
@@ -1303,29 +1303,29 @@ InplaceEditor.prototype = {
input.selectionStart < input.value.length &&
input.value.slice(input.selectionStart)[0] != " ") {
// This emit is mainly to make the test flow simpler.
this.emit("after-suggest", "nothing to autocomplete");
return;
}
let list = [];
if (this.contentType == CONTENT_TYPES.CSS_PROPERTY) {
- list = CSSPropertyList;
+ list = this._getCSSPropertyList();
} else if (this.contentType == CONTENT_TYPES.CSS_VALUE) {
// Get the last query to be completed before the caret.
let match = /([^\s,.\/]+$)/.exec(query);
if (match) {
startCheckQuery = match[0];
} else {
startCheckQuery = "";
}
list =
["!important",
- ...domUtils.getCSSValuesForProperty(this.property.name)];
+ ...this._getCSSValuesForPropertyName(this.property.name)];
if (query == "") {
// Do not suggest '!important' without any manually typed character.
list.splice(0, 1);
}
} else if (this.contentType == CONTENT_TYPES.CSS_MIXED &&
/^\s*style\s*=/.test(query)) {
// Check if the style attribute is closed before the selection.
@@ -1341,30 +1341,30 @@ InplaceEditor.prototype = {
let match = query.match(/([:;"'=]?)\s*([^"';:=]+)?$/);
if (match && match.length >= 2) {
if (match[1] == ":") {
// We are in CSS value completion
let propertyName =
query.match(/[;"'=]\s*([^"';:= ]+)\s*:\s*[^"';:=]*$/)[1];
list =
["!important;",
- ...domUtils.getCSSValuesForProperty(propertyName)];
+ ...this._getCSSValuesForPropertyName(propertyName)];
let matchLastQuery = /([^\s,.\/]+$)/.exec(match[2] || "");
if (matchLastQuery) {
startCheckQuery = matchLastQuery[0];
} else {
startCheckQuery = "";
}
if (!match[2]) {
// Don't suggest '!important' without any manually typed character
list.splice(0, 1);
}
} else if (match[1]) {
// We are in CSS property name completion
- list = CSSPropertyList;
+ list = this._getCSSPropertyList();
startCheckQuery = match[2];
}
if (startCheckQuery == null) {
// This emit is mainly to make the test flow simpler.
this.emit("after-suggest", "nothing to autocomplete");
return;
}
}
@@ -1450,16 +1450,38 @@ InplaceEditor.prototype = {
* Check if the current input is displaying more than one line of text.
*
* @return {Boolean} true if the input has a single line of text
*/
_isSingleLine: function () {
let inputRect = this.input.getBoundingClientRect();
return inputRect.height < 2 * this.inputCharDimensions.height;
},
+
+ /**
+ * Returns the list of CSS properties to use for the autocompletion. This
+ * method is overridden by tests in order to use mocked suggestion lists.
+ *
+ * @return {Array} array of CSS property names (Strings)
+ */
+ _getCSSPropertyList: function () {
+ return CSSPropertyList;
+ },
+
+ /**
+ * Returns a list of CSS values valid for a provided property name to use for
+ * the autocompletion. This method is overridden by tests in order to use
+ * mocked suggestion lists.
+ *
+ * @param {String} propertyName
+ * @return {Array} array of CSS property values (Strings)
+ */
+ _getCSSValuesForPropertyName: function (propertyName) {
+ return domUtils.getCSSValuesForProperty(propertyName);
+ },
};
/**
* Copy text-related styles from one element to another.
*/
function copyTextStyles(from, to) {
let win = from.ownerDocument.defaultView;
let style = win.getComputedStyle(from);
--- a/devtools/client/shared/test/browser.ini
+++ b/devtools/client/shared/test/browser.ini
@@ -6,16 +6,17 @@ support-files =
browser_layoutHelpers-getBoxQuads.html
browser_templater_basic.html
browser_toolbar_basic.html
browser_toolbar_webconsole_errors_count.html
browser_devices.json
doc_options-view.xul
head.js
helper_html_tooltip.js
+ helper_inplace_editor.js
html-mdn-css-basic-testing.html
html-mdn-css-no-summary.html
html-mdn-css-no-summary-or-syntax.html
html-mdn-css-no-syntax.html
html-mdn-css-syntax-old-style.html
leakhunt.js
test-actor.js
test-actor-registry.js
@@ -116,16 +117,18 @@ skip-if = e10s # Bug 1221911, bug 122228
[browser_html_tooltip-02.js]
[browser_html_tooltip-03.js]
[browser_html_tooltip-04.js]
[browser_html_tooltip-05.js]
[browser_html_tooltip_arrow-01.js]
[browser_html_tooltip_arrow-02.js]
[browser_inplace-editor-01.js]
[browser_inplace-editor-02.js]
+[browser_inplace-editor_autocomplete_01.js]
+[browser_inplace-editor_autocomplete_02.js]
[browser_inplace-editor_maxwidth.js]
[browser_key_shortcuts.js]
[browser_layoutHelpers.js]
skip-if = e10s # Layouthelpers test should not run in a content page.
[browser_layoutHelpers-getBoxQuads.js]
skip-if = e10s # Layouthelpers test should not run in a content page.
[browser_mdn-docs-01.js]
[browser_mdn-docs-02.js]
--- a/devtools/client/shared/test/browser_inplace-editor-01.js
+++ b/devtools/client/shared/test/browser_inplace-editor-01.js
@@ -1,27 +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/ */
+/* import-globals-from helper_inplace_editor.js */
"use strict";
-var {editableField, getInplaceEditorForSpan: inplaceEditor} = require("devtools/client/shared/inplace-editor");
+loadHelperScript("helper_inplace_editor.js");
// Test the inplace-editor behavior.
add_task(function* () {
yield addTab("data:text/html;charset=utf-8,inline editor tests");
- let [host, win, doc] = yield createHost();
+ let [host, , doc] = yield createHost();
yield testMultipleInitialization(doc);
yield testReturnCommit(doc);
yield testBlurCommit(doc);
yield testAdvanceCharCommit(doc);
yield testAdvanceCharsFunction(doc);
+ yield testEscapeCancel(doc);
host.destroy();
gBrowser.removeCurrentTab();
});
function testMultipleInitialization(doc) {
doc.body.innerHTML = "";
let options = {};
@@ -31,28 +33,31 @@ function testMultipleInitialization(doc)
editableField(options);
editableField(options);
info("Clicking on the inplace-editor field to turn to edit mode");
span.click();
is(span.style.display, "none", "The original <span> is hidden");
is(doc.querySelectorAll("input").length, 1, "Only one <input>");
- is(doc.querySelectorAll("span").length, 2, "Correct number of <span> elements");
- is(doc.querySelectorAll("span.autosizer").length, 1, "There is an autosizer element");
+ is(doc.querySelectorAll("span").length, 2,
+ "Correct number of <span> elements");
+ is(doc.querySelectorAll("span.autosizer").length, 1,
+ "There is an autosizer element");
}
function testReturnCommit(doc) {
info("Testing that pressing return commits the new value");
let def = promise.defer();
createInplaceEditorAndClick({
initial: "explicit initial",
start: function (editor) {
- is(editor.input.value, "explicit initial", "Explicit initial value should be used.");
+ is(editor.input.value, "explicit initial",
+ "Explicit initial value should be used.");
editor.input.value = "Test Value";
EventUtils.sendKey("return");
},
done: onDone("Test Value", true, def)
}, doc);
return def.promise;
}
@@ -63,56 +68,55 @@ function testBlurCommit(doc) {
createInplaceEditorAndClick({
start: function (editor) {
is(editor.input.value, "Edit Me!", "textContent of the span used.");
editor.input.value = "Test Value";
editor.input.blur();
},
done: onDone("Test Value", true, def)
- }, doc);
+ }, doc, "Edit Me!");
return def.promise;
}
function testAdvanceCharCommit(doc) {
info("Testing that configured advanceChars commit the new value");
let def = promise.defer();
createInplaceEditorAndClick({
advanceChars: ":",
start: function (editor) {
- let input = editor.input;
EventUtils.sendString("Test:");
},
done: onDone("Test", true, def)
}, doc);
return def.promise;
}
function testAdvanceCharsFunction(doc) {
info("Testing advanceChars as a function");
let def = promise.defer();
let firstTime = true;
createInplaceEditorAndClick({
initial: "",
- advanceChars: function (aCharCode, aText, aInsertionPoint) {
- if (aCharCode !== Components.interfaces.nsIDOMKeyEvent.DOM_VK_COLON) {
+ advanceChars: function (charCode, text, insertionPoint) {
+ if (charCode !== Components.interfaces.nsIDOMKeyEvent.DOM_VK_COLON) {
return false;
}
if (firstTime) {
firstTime = false;
return false;
}
// Just to make sure we check it somehow.
- return aText.length > 0;
+ return text.length > 0;
},
start: function (editor) {
for (let ch of ":Test:") {
EventUtils.sendChar(ch);
}
},
done: onDone(":Test", true, def)
}, doc);
@@ -139,30 +143,8 @@ function testEscapeCancel(doc) {
function onDone(value, isCommit, def) {
return function (actualValue, actualCommit) {
info("Inplace-editor's done callback executed, checking its state");
is(actualValue, value, "The value is correct");
is(actualCommit, isCommit, "The commit boolean is correct");
def.resolve();
};
}
-
-function createInplaceEditorAndClick(options, doc) {
- doc.body.innerHTML = "";
- let span = options.element = createSpan(doc);
-
- info("Creating an inplace-editor field");
- editableField(options);
- is(span.getAttribute("role"), "button",
- "Editable element should have button semantics");
-
- info("Clicking on the inplace-editor field to turn to edit mode");
- span.click();
-}
-
-function createSpan(doc) {
- info("Creating a new span element");
- let span = doc.createElement("span");
- span.setAttribute("tabindex", "0");
- span.textContent = "Edit Me!";
- doc.body.appendChild(span);
- return span;
-}
--- a/devtools/client/shared/test/browser_inplace-editor-02.js
+++ b/devtools/client/shared/test/browser_inplace-editor-02.js
@@ -1,21 +1,22 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
+/* import-globals-from helper_inplace_editor.js */
"use strict";
-var {editableField, getInplaceEditorForSpan: inplaceEditor} = require("devtools/client/shared/inplace-editor");
+loadHelperScript("helper_inplace_editor.js");
// Test that the trimOutput option for the inplace editor works correctly.
add_task(function* () {
yield addTab("data:text/html;charset=utf-8,inline editor tests");
- let [host, win, doc] = yield createHost();
+ let [host, , doc] = yield createHost();
yield testNonTrimmed(doc);
yield testTrimmed(doc);
host.destroy();
gBrowser.removeCurrentTab();
});
@@ -63,28 +64,8 @@ function testTrimmed(doc) {
function onDone(value, isCommit, def) {
return function (actualValue, actualCommit) {
info("Inplace-editor's done callback executed, checking its state");
is(actualValue, value, "The value is correct");
is(actualCommit, isCommit, "The commit boolean is correct");
def.resolve();
};
}
-
-function createInplaceEditorAndClick(options, doc) {
- doc.body.innerHTML = "";
- let span = options.element = createSpan(doc);
-
- info("Creating an inplace-editor field");
- editableField(options);
-
- info("Clicking on the inplace-editor field to turn to edit mode");
- span.click();
-}
-
-function createSpan(doc) {
- info("Creating a new span element");
- let span = doc.createElement("span");
- span.setAttribute("tabindex", "0");
- span.textContent = "Edit Me!";
- doc.body.appendChild(span);
- return span;
-}
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/test/browser_inplace-editor_autocomplete_01.js
@@ -0,0 +1,67 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+/* import-globals-from helper_inplace_editor.js */
+
+"use strict";
+
+const { InplaceEditor } = require("devtools/client/shared/inplace-editor");
+const { AutocompletePopup } = require("devtools/client/shared/autocomplete-popup");
+loadHelperScript("helper_inplace_editor.js");
+
+// Test the inplace-editor autocomplete popup for CSS properties suggestions.
+// Using a mocked list of CSS properties to avoid test failures linked to
+// engine changes (new property, removed property, ...).
+
+const testData = [
+ ["b", "border", 1, 3],
+ ["VK_DOWN", "box-sizing", 2, 3],
+ ["VK_DOWN", "background", 0, 3],
+ ["VK_DOWN", "border", 1, 3],
+ ["VK_BACK_SPACE", "b", -1, 0],
+ ["VK_BACK_SPACE", "", -1, 0],
+ ["VK_DOWN", "background", 0, 6],
+ ["VK_LEFT", "background", -1, 0],
+];
+
+const mockGetCSSPropertyList = function () {
+ return [
+ "background",
+ "border",
+ "box-sizing",
+ "color",
+ "display",
+ "visibility",
+ ];
+};
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," +
+ "inplace editor CSS property autocomplete");
+ let [host, win, doc] = yield createHost();
+
+ let xulDocument = win.top.document;
+ let popup = new AutocompletePopup(xulDocument, { autoSelect: true });
+ yield new Promise(resolve => {
+ createInplaceEditorAndClick({
+ start: runPropertyAutocompletionTest,
+ contentType: InplaceEditor.CONTENT_TYPES.CSS_PROPERTY,
+ done: resolve,
+ popup: popup
+ }, doc);
+ });
+
+ host.destroy();
+ gBrowser.removeCurrentTab();
+});
+
+let runPropertyAutocompletionTest = Task.async(function* (editor) {
+ info("Starting to test for css property completion");
+ editor._getCSSPropertyList = mockGetCSSPropertyList;
+
+ for (let data of testData) {
+ yield testCompletion(data, editor);
+ }
+
+ EventUtils.synthesizeKey("VK_RETURN", {}, editor.input.defaultView);
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/test/browser_inplace-editor_autocomplete_02.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/ */
+/* import-globals-from helper_inplace_editor.js */
+
+"use strict";
+
+const { InplaceEditor } = require("devtools/client/shared/inplace-editor");
+const { AutocompletePopup } = require("devtools/client/shared/autocomplete-popup");
+loadHelperScript("helper_inplace_editor.js");
+
+// Test the inplace-editor autocomplete popup for CSS values suggestions.
+// Using a mocked list of CSS properties to avoid test failures linked to
+// engine changes (new property, removed property, ...).
+
+const testData = [
+ ["b", "block", -1, 0],
+ ["VK_BACK_SPACE", "b", -1, 0],
+ ["VK_BACK_SPACE", "", -1, 0],
+ ["i", "inline", 0, 2],
+ ["VK_DOWN", "inline-block", 1, 2],
+ ["VK_DOWN", "inline", 0, 2],
+ ["VK_LEFT", "inline", -1, 0],
+];
+
+const mockGetCSSValuesForPropertyName = function (propertyName) {
+ let values = {
+ "display": [
+ "block",
+ "flex",
+ "inline",
+ "inline-block",
+ "none",
+ ]
+ };
+ return values[propertyName] || [];
+};
+
+add_task(function* () {
+ yield addTab("data:text/html;charset=utf-8," +
+ "inplace editor CSS value autocomplete");
+ let [host, win, doc] = yield createHost();
+
+ let xulDocument = win.top.document;
+ let popup = new AutocompletePopup(xulDocument, { autoSelect: true });
+
+ yield new Promise(resolve => {
+ createInplaceEditorAndClick({
+ start: runAutocompletionTest,
+ contentType: InplaceEditor.CONTENT_TYPES.CSS_VALUE,
+ property: {
+ name: "display"
+ },
+ done: resolve,
+ popup: popup
+ }, doc);
+ });
+
+ host.destroy();
+ gBrowser.removeCurrentTab();
+});
+
+let runAutocompletionTest = Task.async(function* (editor) {
+ info("Starting to test for css property completion");
+ editor._getCSSValuesForPropertyName = mockGetCSSValuesForPropertyName;
+
+ for (let data of testData) {
+ yield testCompletion(data, editor);
+ }
+
+ EventUtils.synthesizeKey("VK_RETURN", {}, editor.input.defaultView);
+});
--- a/devtools/client/shared/test/browser_inplace-editor_maxwidth.js
+++ b/devtools/client/shared/test/browser_inplace-editor_maxwidth.js
@@ -1,15 +1,16 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
+/* import-globals-from helper_inplace_editor.js */
"use strict";
-var { editableField } = require("devtools/client/shared/inplace-editor");
+loadHelperScript("helper_inplace_editor.js");
const MAX_WIDTH = 300;
const START_TEXT = "Start text";
const LONG_TEXT = "I am a long text and I will not fit in a 300px container. " +
"I expect the inplace editor to wrap.";
// Test the inplace-editor behavior with a maxWidth configuration option
// defined.
@@ -20,17 +21,17 @@ add_task(function* () {
info("Testing the maxWidth option in pixels, to precisely check the size");
yield new Promise(resolve => {
createInplaceEditorAndClick({
multiline: true,
maxWidth: MAX_WIDTH,
start: testMaxWidth,
done: resolve
- }, doc);
+ }, doc, START_TEXT);
});
host.destroy();
gBrowser.removeCurrentTab();
});
let testMaxWidth = Task.async(function* (editor) {
is(editor.input.value, START_TEXT, "Span text content should be used");
@@ -106,30 +107,8 @@ function getLines(textarea) {
* @param {DOMNode} textarea
*/
function checkScrollbars(textarea) {
is(textarea.scrollHeight, textarea.clientHeight,
"Textarea should never have vertical scrollbars");
is(textarea.scrollWidth, textarea.clientWidth,
"Textarea should never have horizontal scrollbars");
}
-
-function createInplaceEditorAndClick(options, doc) {
- doc.body.innerHTML = "";
- let span = options.element = createSpan(doc);
-
- info("Creating an inplace-editor field");
- editableField(options);
-
- info("Clicking on the inplace-editor field to turn to edit mode");
- span.click();
-}
-
-function createSpan(doc) {
- info("Creating a new span element");
- let span = doc.createElement("span");
- span.setAttribute("tabindex", "0");
- span.style.fontSize = "11px";
- span.style.fontFamily = "monospace";
- span.textContent = START_TEXT;
- doc.body.appendChild(span);
- return span;
-}
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/test/helper_inplace_editor.js
@@ -0,0 +1,99 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+/* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */
+
+"use strict";
+
+/**
+ * Helper methods for the HTMLTooltip integration tests.
+ */
+
+const { editableField } = require("devtools/client/shared/inplace-editor");
+
+/**
+ * Create an inplace editor linked to a span element and click on the span to
+ * to turn to edit mode.
+ *
+ * @param {Object} options
+ * Options passed to the InplaceEditor/editableField constructor.
+ * @param {Document} doc
+ * Document where the span element will be created.
+ * @param {String} textContent
+ * (optional) String that will be used as the text content of the span.
+ */
+function createInplaceEditorAndClick(options, doc, textContent) {
+ doc.body.innerHTML = "";
+ let span = options.element = createSpan(doc);
+ if (textContent) {
+ span.textContent = textContent;
+ }
+
+ info("Creating an inplace-editor field");
+ editableField(options);
+
+ info("Clicking on the inplace-editor field to turn to edit mode");
+ span.click();
+}
+
+/**
+ * Helper to create a span in the provided document.
+ *
+ * @param {Document} doc
+ * Document where the span element will be created.
+ * @return {Element} the created span element.
+ */
+function createSpan(doc) {
+ info("Creating a new span element");
+ let span = doc.createElement("span");
+ span.setAttribute("tabindex", "0");
+ span.style.fontSize = "11px";
+ span.style.fontFamily = "monospace";
+ doc.body.appendChild(span);
+ return span;
+}
+
+/**
+ * Test helper simulating a key event in an InplaceEditor and checking that the
+ * autocompletion works as expected.
+ *
+ * @param {Array} testData
+ * - {String} key, the key to send
+ * - {String} completion, the expected value of the auto-completion
+ * - {Number} index, the index of the selected suggestion in the popup
+ * - {Number} total, the total number of suggestions in the popup
+ * @param {InplaceEditor} editor
+ * The InplaceEditor instance being tested
+ */
+function* testCompletion([key, completion, index, total], editor) {
+ info("Pressing key " + key);
+ info("Expecting " + completion);
+
+ let onSuggest;
+
+ if (/(left|right|back_space|escape)/ig.test(key)) {
+ info("Adding event listener for right|back_space|escape keys");
+ onSuggest = once(editor.input, "keypress");
+ } else {
+ info("Waiting for after-suggest event on the editor");
+ onSuggest = editor.once("after-suggest");
+ }
+
+ info("Synthesizing key " + key);
+ EventUtils.synthesizeKey(key, {}, editor.input.defaultView);
+
+ yield onSuggest;
+ yield waitForTick();
+
+ info("Checking the state");
+ if (completion != null) {
+ is(editor.input.value, completion, "Correct value is autocompleted");
+ }
+ if (total === 0) {
+ ok(!(editor.popup && editor.popup.isOpen), "Popup is closed");
+ } else {
+ ok(editor.popup._panel.state == "open" ||
+ editor.popup._panel.state == "showing", "Popup is open");
+ is(editor.popup.getItems().length, total, "Number of suggestions match");
+ is(editor.popup.selectedIndex, index, "Expected item is selected");
+ }
+}