michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: michael@0: const TESTCASE_URI = TEST_BASE + "autocomplete.html"; michael@0: const MAX_SUGGESTIONS = 15; michael@0: michael@0: // Pref which decides if CSS autocompletion is enabled in Style Editor or not. michael@0: const AUTOCOMPLETION_PREF = "devtools.styleeditor.autocompletion-enabled"; michael@0: michael@0: const {CSSProperties, CSSValues} = getCSSKeywords(); michael@0: michael@0: // Test cases to test that autocompletion works correctly when enabled. michael@0: // Format: michael@0: // [ michael@0: // key, michael@0: // { michael@0: // total: Number of suggestions in the popup (-1 if popup is closed), michael@0: // current: Index of selected suggestion, michael@0: // inserted: 1 to check whether the selected suggestion is inserted into the editor or not, michael@0: // entered: 1 if the suggestion is inserted and finalized michael@0: // } michael@0: // ] michael@0: let TEST_CASES = [ michael@0: ['VK_RIGHT'], michael@0: ['VK_RIGHT'], michael@0: ['VK_RIGHT'], michael@0: ['VK_RIGHT'], michael@0: ['Ctrl+Space', {total: 1, current: 0}], michael@0: ['VK_LEFT'], michael@0: ['VK_RIGHT'], michael@0: ['VK_DOWN'], michael@0: ['VK_RIGHT'], michael@0: ['VK_RIGHT'], michael@0: ['VK_RIGHT'], michael@0: ['Ctrl+Space', { total: getSuggestionNumberFor("font"), current: 0}], michael@0: ['VK_END'], michael@0: ['VK_RETURN'], michael@0: ['b', {total: getSuggestionNumberFor("b"), current: 0}], michael@0: ['a', {total: getSuggestionNumberFor("ba"), current: 0}], michael@0: ['VK_DOWN', {total: getSuggestionNumberFor("ba"), current: 0, inserted: 1}], michael@0: ['VK_TAB', {total: getSuggestionNumberFor("ba"), current: 1, inserted: 1}], michael@0: ['VK_RETURN', {current: 1, inserted: 1, entered: 1}], michael@0: ['b', {total: getSuggestionNumberFor("background", "b"), current: 0}], michael@0: ['l', {total: getSuggestionNumberFor("background", "bl"), current: 0}], michael@0: ['VK_TAB', {total: getSuggestionNumberFor("background", "bl"), current: 0, inserted: 1}], michael@0: ['VK_DOWN', {total: getSuggestionNumberFor("background", "bl"), current: 1, inserted: 1}], michael@0: ['VK_UP', {total: getSuggestionNumberFor("background", "bl"), current: 0, inserted: 1}], michael@0: ['VK_TAB', {total: getSuggestionNumberFor("background", "bl"), current: 1, inserted: 1}], michael@0: ['VK_TAB', {total: getSuggestionNumberFor("background", "bl"), current: 2, inserted: 1}], michael@0: [';'], michael@0: ['VK_RETURN'], michael@0: ['c', {total: getSuggestionNumberFor("c"), current: 0}], michael@0: ['o', {total: getSuggestionNumberFor("co"), current: 0}], michael@0: ['VK_RETURN', {current: 0, inserted: 1}], michael@0: ['r', {total: getSuggestionNumberFor("color", "r"), current: 0}], michael@0: ['VK_RETURN', {current: 0, inserted: 1}], michael@0: [';'], michael@0: ['VK_LEFT'], michael@0: ['VK_RIGHT'], michael@0: ['VK_DOWN'], michael@0: ['VK_RETURN'], michael@0: ['b', {total: 2, current: 0}], michael@0: ['u', {total: 1, current: 0}], michael@0: ['VK_RETURN', {current: 0, inserted: 1}], michael@0: ['{'], michael@0: ['VK_HOME'], michael@0: ['VK_DOWN'], michael@0: ['VK_DOWN'], michael@0: ['VK_RIGHT'], michael@0: ['VK_RIGHT'], michael@0: ['VK_RIGHT'], michael@0: ['VK_RIGHT'], michael@0: ['VK_RIGHT'], michael@0: ['VK_RIGHT'], michael@0: ['VK_RIGHT'], michael@0: ['VK_RIGHT'], michael@0: ['VK_RIGHT'], michael@0: ['VK_RIGHT'], michael@0: ['Ctrl+Space', {total: 1, current: 0}], michael@0: ]; michael@0: michael@0: let gEditor; michael@0: let gPopup; michael@0: let index = 0; michael@0: michael@0: function test() michael@0: { michael@0: waitForExplicitFinish(); michael@0: michael@0: addTabAndOpenStyleEditors(1, testEditorAdded); michael@0: michael@0: content.location = TESTCASE_URI; michael@0: } michael@0: michael@0: function testEditorAdded(panel) { michael@0: info("Editor added, getting the source editor and starting tests"); michael@0: panel.UI.editors[0].getSourceEditor().then(editor => { michael@0: info("source editor found, starting tests."); michael@0: gEditor = editor.sourceEditor; michael@0: gPopup = gEditor.getAutocompletionPopup(); michael@0: waitForFocus(testState, gPanelWindow); michael@0: }); michael@0: } michael@0: michael@0: function testState() { michael@0: if (index == TEST_CASES.length) { michael@0: testAutocompletionDisabled(); michael@0: return; michael@0: } michael@0: michael@0: let [key, details] = TEST_CASES[index]; michael@0: let entered; michael@0: if (details) { michael@0: entered = details.entered; michael@0: } michael@0: let mods = {}; michael@0: michael@0: info("pressing key " + key + " to get result: " + michael@0: JSON.stringify(TEST_CASES[index]) + " for index " + index); michael@0: michael@0: let evt = "after-suggest"; michael@0: michael@0: if (key == 'Ctrl+Space') { michael@0: key = " "; michael@0: mods.accelKey = true; michael@0: } michael@0: else if (key == "VK_RETURN" && entered) { michael@0: evt = "popup-hidden"; michael@0: } michael@0: else if (/(left|right|return|home|end)/ig.test(key) || michael@0: (key == "VK_DOWN" && !gPopup.isOpen)) { michael@0: evt = "cursorActivity"; michael@0: } michael@0: else if (key == "VK_TAB" || key == "VK_UP" || key == "VK_DOWN") { michael@0: evt = "suggestion-entered"; michael@0: } michael@0: michael@0: gEditor.once(evt, checkState); michael@0: EventUtils.synthesizeKey(key, mods, gPanelWindow); michael@0: } michael@0: michael@0: function checkState() { michael@0: executeSoon(() => { michael@0: let [key, details] = TEST_CASES[index]; michael@0: details = details || {}; michael@0: let {total, current, inserted} = details; michael@0: michael@0: if (total != undefined) { michael@0: ok(gPopup.isOpen, "Popup is open for index " + index); michael@0: is(total, gPopup.itemCount, michael@0: "Correct total suggestions for index " + index); michael@0: is(current, gPopup.selectedIndex, michael@0: "Correct index is selected for index " + index); michael@0: if (inserted) { michael@0: let { preLabel, label, text } = gPopup.getItemAtIndex(current); michael@0: let { line, ch } = gEditor.getCursor(); michael@0: let lineText = gEditor.getText(line); michael@0: is(lineText.substring(ch - text.length, ch), text, michael@0: "Current suggestion from the popup is inserted into the editor."); michael@0: } michael@0: } michael@0: else { michael@0: ok(!gPopup.isOpen, "Popup is closed for index " + index); michael@0: if (inserted) { michael@0: let { preLabel, label, text } = gPopup.getItemAtIndex(current); michael@0: let { line, ch } = gEditor.getCursor(); michael@0: let lineText = gEditor.getText(line); michael@0: is(lineText.substring(ch - text.length, ch), text, michael@0: "Current suggestion from the popup is inserted into the editor."); michael@0: } michael@0: } michael@0: index++; michael@0: testState(); michael@0: }); michael@0: } michael@0: michael@0: function testAutocompletionDisabled() { michael@0: gBrowser.removeCurrentTab(); michael@0: michael@0: index = 0; michael@0: info("Starting test to check if autocompletion is disabled correctly.") michael@0: Services.prefs.setBoolPref(AUTOCOMPLETION_PREF, false); michael@0: michael@0: addTabAndOpenStyleEditors(1, testEditorAddedDisabled); michael@0: michael@0: content.location = TESTCASE_URI; michael@0: } michael@0: michael@0: function testEditorAddedDisabled(panel) { michael@0: info("Editor added, getting the source editor and starting tests"); michael@0: panel.UI.editors[0].getSourceEditor().then(editor => { michael@0: ok(!editor.sourceEditor.getAutocompletionPopup, michael@0: "Autocompletion popup does not exist"); michael@0: cleanup(); michael@0: }); michael@0: } michael@0: michael@0: function cleanup() { michael@0: Services.prefs.clearUserPref(AUTOCOMPLETION_PREF); michael@0: gEditor = null; michael@0: gPopup = null; michael@0: finish(); michael@0: } michael@0: michael@0: /** michael@0: * Returns a list of all property names and a map of property name vs possible michael@0: * CSS values provided by the Gecko engine. michael@0: * michael@0: * @return {Object} An object with following properties: michael@0: * - CSSProperties {Array} Array of string containing all the possible michael@0: * CSS property names. michael@0: * - CSSValues {Object|Map} A map where key is the property name and michael@0: * value is an array of string containing all the possible michael@0: * CSS values the property can have. michael@0: */ michael@0: function getCSSKeywords() { michael@0: let domUtils = Cc["@mozilla.org/inspector/dom-utils;1"] michael@0: .getService(Ci.inIDOMUtils); michael@0: let props = {}; michael@0: let propNames = domUtils.getCSSPropertyNames(domUtils.INCLUDE_ALIASES); michael@0: propNames.forEach(prop => { michael@0: props[prop] = domUtils.getCSSValuesForProperty(prop).sort(); michael@0: }); michael@0: return { michael@0: CSSValues: props, michael@0: CSSProperties: propNames.sort() michael@0: }; michael@0: } michael@0: michael@0: /** michael@0: * Returns the number of expected suggestions for the given property and value. michael@0: * If the value is not null, returns the number of values starting with `value`. michael@0: * Returns the number of properties starting with `property` otherwise. michael@0: */ michael@0: function getSuggestionNumberFor(property, value) { michael@0: if (value == null) { michael@0: return CSSProperties.filter(prop => prop.startsWith(property)) michael@0: .slice(0, MAX_SUGGESTIONS).length; michael@0: } michael@0: return CSSValues[property].filter(val => val.startsWith(value)) michael@0: .slice(0, MAX_SUGGESTIONS).length; michael@0: }