helper_inplace_editor.js (6087B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 /* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */ 4 /* import-globals-from head.js */ 5 6 "use strict"; 7 8 /** 9 * Helper methods for the HTMLTooltip integration tests. 10 */ 11 12 const HTML_NS = "http://www.w3.org/1999/xhtml"; 13 const { 14 editableField, 15 } = require("resource://devtools/client/shared/inplace-editor.js"); 16 const { colorUtils } = require("resource://devtools/shared/css/color.js"); 17 18 /** 19 * Create an inplace editor linked to a span element and click on the span to 20 * to turn to edit mode. 21 * 22 * @param {object} options 23 * Options passed to the InplaceEditor/editableField constructor. 24 * @param {Document} doc 25 * Document where the span element will be created. 26 * @param {string} textContent 27 * (optional) String that will be used as the text content of the span. 28 */ 29 const createInplaceEditorAndClick = async function (options, doc, textContent) { 30 const span = (options.element = createSpan(doc)); 31 if (textContent) { 32 span.textContent = textContent; 33 } 34 35 info("Creating an inplace-editor field"); 36 editableField(options); 37 38 info("Clicking on the inplace-editor field to turn to edit mode"); 39 span.click(); 40 }; 41 42 /** 43 * Helper to create a span in the provided document. 44 * 45 * @param {Document} doc 46 * Document where the span element will be created. 47 * @return {Element} the created span element. 48 */ 49 function createSpan(doc) { 50 info("Creating a new span element"); 51 const div = doc.createElementNS(HTML_NS, "div"); 52 const span = doc.createElementNS(HTML_NS, "span"); 53 span.setAttribute("tabindex", "0"); 54 span.style.fontSize = "11px"; 55 span.style.display = "inline-block"; 56 span.style.width = "100px"; 57 span.style.border = "1px solid red"; 58 span.style.fontFamily = "monospace"; 59 60 div.style.height = "100%"; 61 div.style.position = "absolute"; 62 div.appendChild(span); 63 64 const parent = doc.querySelector("window") || doc.body; 65 parent.appendChild(div); 66 return span; 67 } 68 69 /** 70 * Test helper simulating a key event in an InplaceEditor and checking that the 71 * autocompletion works as expected. 72 * 73 * @param {Array} testData 74 * - {String|Object} key, the key to send. An object can be passed with a `key` property. 75 * The other properties will be the options for the event (e.g. `shiftKey`) 76 * - {String} completion, the expected value of the auto-completion 77 * - {Number} index, the index of the selected suggestion in the popup 78 * - {Number|Array} items, the number of suggestions in the popup, or, alternatively 79 * an array of the items label 80 * - {String} postLabel, the expected post label for the selected suggestion 81 * - {Boolean} colorSwatch, if there is a swatch of color expected to be visible 82 * - {Boolean} noSuggestion, true if the keypress doesn't trigger an "after-suggest" event 83 * @param {InplaceEditor} editor 84 * The InplaceEditor instance being tested 85 */ 86 async function testCompletion( 87 [key, completion, index, items, postLabel, colorSwatch, noSuggestion], 88 editor 89 ) { 90 let eventOptions = {}; 91 if (typeof key === "object") { 92 ({ key, ...eventOptions } = key); 93 } 94 95 info(`Pressing key <${key}> | options: ${JSON.stringify(eventOptions)}`); 96 info("Expecting " + completion); 97 98 let onVisibilityChange = null; 99 const total = Array.isArray(items) ? items.length : items; 100 const open = total > 0; 101 if (editor.popup.isOpen != open) { 102 onVisibilityChange = editor.popup.once( 103 open ? "popup-opened" : "popup-closed" 104 ); 105 } 106 107 let onSuggest; 108 if (/(left|right|back_space|escape)/gi.test(key) || noSuggestion) { 109 info("Waiting for next keypress event"); 110 onSuggest = once(editor.input, "keypress"); 111 } else { 112 info("Waiting for after-suggest event on the editor"); 113 onSuggest = editor.once("after-suggest"); 114 } 115 116 info("Synthesizing key " + key); 117 EventUtils.synthesizeKey(key, eventOptions, editor.input.defaultView); 118 119 await onSuggest; 120 await onVisibilityChange; 121 await waitForTime(5); 122 123 info("Checking the state"); 124 if (completion !== null) { 125 is(editor.input.value, completion, "Correct value is autocompleted"); 126 } 127 128 if (postLabel) { 129 const selectedItem = editor.popup.getItems()[index]; 130 const selectedElement = editor.popup.elements.get(selectedItem); 131 ok( 132 selectedElement.textContent.includes(postLabel), 133 "Selected popup element contains the expected post-label" 134 ); 135 136 // Determines if there is a color swatch attached to the label 137 // and if the color swatch's background color matches the post label 138 const swatchSpan = selectedElement.getElementsByClassName( 139 "autocomplete-swatch autocomplete-colorswatch" 140 ); 141 if (colorSwatch) { 142 Assert.strictEqual( 143 swatchSpan.length, 144 1, 145 "Displayed the expected color swatch" 146 ); 147 const color = new colorUtils.CssColor( 148 swatchSpan[0].style.backgroundColor 149 ); 150 const swatchColor = color.rgba; 151 const postColor = new colorUtils.CssColor(postLabel).rgba; 152 Assert.equal( 153 swatchColor, 154 postColor, 155 "Color swatch matches postLabel value" 156 ); 157 } else { 158 Assert.strictEqual( 159 swatchSpan.length, 160 0, 161 "As expected no swatches were available" 162 ); 163 } 164 } 165 166 if (total === 0) { 167 ok(!(editor.popup && editor.popup.isOpen), "Popup is closed"); 168 } else { 169 ok(editor.popup.isOpen, "Popup is open"); 170 const popupItems = editor.popup.getItems(); 171 if (Array.isArray(items)) { 172 Assert.deepEqual( 173 popupItems.map(item => item.label), 174 items, 175 "Suggestions match" 176 ); 177 } else { 178 is( 179 popupItems.length, 180 total, 181 "Number of suggestions match" + 182 (popupItems.length !== total 183 ? ` - got ${JSON.stringify(popupItems.map(item => item.label))}` 184 : "") 185 ); 186 } 187 is(editor.popup.selectedIndex, index, "Expected item is selected"); 188 } 189 }