test_nsIEditor_deleteNode.html (8269B)
1 <!doctype html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title>nsIEditor.insertNode</title> 6 <script src="/tests/SimpleTest/SimpleTest.js"></script> 7 <link rel="stylesheet" href="/tests/SimpleTest/test.css"/> 8 <script> 9 "use strict"; 10 11 function stringifyInputEvent(aEvent) { 12 if (!aEvent) { 13 return "null"; 14 } 15 return `${aEvent.type}: { inputType=${aEvent.inputType} }`; 16 } 17 18 function getRangeDescription(range) { 19 function getNodeDescription(node) { 20 if (!node) { 21 return "null"; 22 } 23 switch (node.nodeType) { 24 case Node.TEXT_NODE: 25 return `${node.nodeName} "${node.data}"`; 26 case Node.ELEMENT_NODE: 27 return `<${node.nodeName.toLowerCase()}>`; 28 default: 29 return `${node.nodeName}`; 30 } 31 } 32 if (range === null) { 33 return "null"; 34 } 35 if (range === undefined) { 36 return "undefined"; 37 } 38 return range.startContainer == range.endContainer && 39 range.startOffset == range.endOffset 40 ? `(${getNodeDescription(range.startContainer)}, ${range.startOffset})` 41 : `(${getNodeDescription(range.startContainer)}, ${ 42 range.startOffset 43 }) - (${getNodeDescription(range.endContainer)}, ${range.endOffset})`; 44 } 45 46 SimpleTest.waitForExplicitFinish(); 47 SimpleTest.waitForFocus(() => { 48 const editingHost = document.querySelector("div[contenteditable]"); 49 const editor = 50 SpecialPowers.wrap(window).docShell.editingSession.getEditorForWindow(window); 51 52 editingHost.focus(); 53 54 let events = []; 55 editingHost.addEventListener("input", event => events.push(event)); 56 57 (function test_delete_node_before_selection() { 58 editingHost.innerHTML = "<span>abc</span><span>def</span>"; 59 getSelection().collapse(editingHost.querySelector("span + span").firstChild, 0); 60 editor.deleteNode(editingHost.querySelector("span")); 61 is( 62 editingHost.innerHTML, 63 "<span>def</span>", 64 "test_delete_node_before_selection: deleteNode() should delete the node" 65 ); 66 is( 67 events.length, 68 1, 69 "test_delete_node_before_selection: Only one input event should be fired when deleteNode() deletes a node" 70 ); 71 is( 72 stringifyInputEvent(events[0]), 73 stringifyInputEvent({ type: "input", inputType: "" }), 74 "test_delete_node_before_selection: input event should be fired when deleting a node" 75 ); 76 is( 77 getRangeDescription(getSelection().getRangeAt(0)), 78 getRangeDescription({ 79 startContainer: editingHost.firstChild.firstChild, 80 startOffset: 0, 81 endContainer: editingHost.firstChild.firstChild, 82 endOffset: 0, 83 }), 84 "test_delete_node_before_selection: selection shouldn't be updated" 85 ); 86 })(); 87 88 (function test_delete_node_after_selection() { 89 events = []; 90 editingHost.innerHTML = "<span>abc</span><span>def</span>"; 91 getSelection().collapse(editingHost.querySelector("span").firstChild, 0); 92 editor.deleteNode(editingHost.querySelector("span + span")); 93 is( 94 editingHost.innerHTML, 95 "<span>abc</span>", 96 "test_delete_node_after_selection: deleteNode() should delete the node" 97 ); 98 is( 99 events.length, 100 1, 101 "test_delete_node_after_selection: Only one input event should be fired when deleteNode() deletes a node" 102 ); 103 is( 104 stringifyInputEvent(events[0]), 105 stringifyInputEvent({ type: "input", inputType: "" }), 106 "test_delete_node_after_selection: input event should be fired when deleting a node" 107 ); 108 is( 109 getRangeDescription(getSelection().getRangeAt(0)), 110 getRangeDescription({ 111 startContainer: editingHost.firstChild.firstChild, 112 startOffset: 0, 113 endContainer: editingHost.firstChild.firstChild, 114 endOffset: 0, 115 }), 116 "test_delete_node_after_selection: selection shouldn't be updated" 117 ); 118 })(); 119 120 (function test_delete_node_containing_selection() { 121 events = []; 122 editingHost.innerHTML = "<span>abc</span><span>def</span>"; 123 getSelection().collapse(editingHost.querySelector("span").firstChild, 0); 124 editor.deleteNode(editingHost.querySelector("span")); 125 is( 126 editingHost.innerHTML, 127 "<span>def</span>", 128 "test_delete_node_containing_selection: deleteNode() should delete the node" 129 ); 130 is( 131 events.length, 132 1, 133 "test_delete_node_containing_selection: Only one input event should be fired when deleteNode() deletes a node" 134 ); 135 is( 136 stringifyInputEvent(events[0]), 137 stringifyInputEvent({ type: "input", inputType: "" }), 138 "test_delete_node_containing_selection: input event should be fired when deleting a node" 139 ); 140 is( 141 getRangeDescription(getSelection().getRangeAt(0)), 142 getRangeDescription({ 143 startContainer: editingHost, 144 startOffset: 0, 145 endContainer: editingHost, 146 endOffset: 0, 147 }), 148 "test_delete_node_containing_selection: selection should be updated whether node was" 149 ); 150 })(); 151 152 (function test_delete_node_containing_selection_with_preserving_selection() { 153 events = []; 154 editingHost.innerHTML = "<span>abc</span><span>def</span>"; 155 getSelection().collapse(editingHost.querySelector("span").firstChild, 0); 156 editor.deleteNode(editingHost.querySelector("span"), true); 157 is( 158 editingHost.innerHTML, 159 "<span>def</span>", 160 "test_delete_node_containing_selection_with_preserving_selection: deleteNode() should delete the node" 161 ); 162 is( 163 events.length, 164 1, 165 "test_delete_node_containing_selection_with_preserving_selection: Only one input event should be fired when deleteNode() deletes a node" 166 ); 167 is( 168 stringifyInputEvent(events[0]), 169 stringifyInputEvent({ type: "input", inputType: "" }), 170 "test_delete_node_containing_selection_with_preserving_selection: input event should be fired when deleting a node" 171 ); 172 is( 173 getRangeDescription(getSelection().getRangeAt(0)), 174 getRangeDescription({ 175 startContainer: editingHost, 176 startOffset: 0, 177 endContainer: editingHost, 178 endOffset: 0, 179 }), 180 "test_delete_node_containing_selection_with_preserving_selection: selection should be updated whether node was" 181 ); 182 })(); 183 184 (function test_not_preserve_selection_nested_by_beforeinput() { 185 editingHost.innerHTML = "<span>abc</span><span>ghi</span>"; 186 const span = document.createElement("span"); 187 span.textContent = "def"; 188 getSelection().collapse(editingHost, 0); 189 editingHost.addEventListener("beforeinput", () => { 190 editor.insertNode(span, editingHost, 1); 191 }, {once: true}); 192 editor.deleteNode(editingHost.querySelector("span + span"), true); 193 is( 194 editingHost.innerHTML, 195 "<span>abc</span><span>def</span>", 196 "test_not_preserve_selection_nested_by_beforeinput: both insertNode() and deleteNode() should work" 197 ); 198 is( 199 getRangeDescription(getSelection().getRangeAt(0)), 200 getRangeDescription({ 201 startContainer: editingHost, 202 startOffset: 2, 203 endContainer: editingHost, 204 endOffset: 2, 205 }), 206 "test_not_preserve_selection_nested_by_beforeinput: only insertNode() called in beforeinput listener should update selection" 207 ); 208 })(); 209 210 (function test_not_preserve_selection_nested_by_input() { 211 editingHost.innerHTML = "<span>abc</span><span>ghi</span>"; 212 const span = document.createElement("span"); 213 span.textContent = "def"; 214 getSelection().collapse(editingHost, 0); 215 editingHost.addEventListener("input", () => { 216 editor.insertNode(span, editingHost, 1); 217 }, {once: true}); 218 editor.deleteNode(editingHost.querySelector("span + span"), true); 219 is( 220 editingHost.innerHTML, 221 "<span>abc</span><span>def</span>", 222 "test_not_preserve_selection_nested_by_input: both insertNode() and deleteNode() should work" 223 ); 224 is( 225 getRangeDescription(getSelection().getRangeAt(0)), 226 getRangeDescription({ 227 startContainer: editingHost, 228 startOffset: 2, 229 endContainer: editingHost, 230 endOffset: 2, 231 }), 232 "test_not_preserve_selection_nested_by_input: only insertNode() called in input listener should update selection" 233 ); 234 })(); 235 236 SimpleTest.finish(); 237 }); 238 239 </script> 240 </head> 241 <body><div contenteditable><br></div></body> 242 </html>