| <!doctype html> |
| <html> |
| <head> |
| <meta charset="utf-8"> |
| <title>Don't move caret to non-editable node from a editable node</title> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="/resources/testdriver.js"></script> |
| <script src="/resources/testdriver-vendor.js"></script> |
| <script src="/resources/testdriver-actions.js"></script> |
| <script> |
| "use strict"; |
| |
| function getRangeDescription(range) { |
| function getNodeDescription(node) { |
| if (!node) { |
| return "null"; |
| } |
| switch (node.nodeType) { |
| case Node.TEXT_NODE: |
| return `${node.nodeName} "${node.data}"`; |
| case Node.ELEMENT_NODE: |
| return `<${node.nodeName.toLowerCase()}>`; |
| default: |
| return `${node.nodeName}`; |
| } |
| } |
| if (range === null) { |
| return "null"; |
| } |
| if (range === undefined) { |
| return "undefined"; |
| } |
| return range.startContainer == range.endContainer && |
| range.startOffset == range.endOffset |
| ? `(${getNodeDescription(range.startContainer)}, ${range.startOffset})` |
| : `(${getNodeDescription(range.startContainer)}, ${ |
| range.startOffset |
| }) - (${getNodeDescription(range.endContainer)}, ${range.endOffset})`; |
| } |
| |
| function sendArrowRightKey() { |
| const kArrowRight = "\uE014"; |
| return new test_driver.Actions() |
| .keyDown(kArrowRight) |
| .keyUp(kArrowRight) |
| .send(); |
| } |
| |
| function sendArrowLeftKey() { |
| const kArrowLeft = "\uE012"; |
| return new test_driver.Actions() |
| .keyDown(kArrowLeft) |
| .keyUp(kArrowLeft) |
| .send(); |
| } |
| |
| promise_test(async () => { |
| await new Promise(resolve => { |
| addEventListener("load", resolve, {once: true}); |
| }); |
| }, "Initializing tests"); |
| |
| promise_test(async t => { |
| const editingHost = document.querySelector("div[contenteditable]"); |
| editingHost.focus(); |
| const p = editingHost.querySelector("p"); |
| getSelection().collapse(p.firstChild, "abc".length); |
| await sendArrowRightKey(); |
| test(() => { |
| assert_equals( |
| getRangeDescription(getSelection().getRangeAt(0)), |
| getRangeDescription({ |
| startContainer: p.nextSibling, |
| startOffset: 0, |
| endContainer: p.nextSibling, |
| endOffset: 0, |
| }), |
| ); |
| }, `${t.name}: first arrow-right should move caret before non-editable text`); |
| await sendArrowRightKey(); |
| test(() => { |
| assert_equals( |
| getRangeDescription(getSelection().getRangeAt(0)), |
| getRangeDescription({ |
| startContainer: p.nextSibling, |
| startOffset: 1, |
| endContainer: p.nextSibling, |
| endOffset: 1, |
| }), |
| ); |
| }, `${t.name}: second arrow-right should move caret after non-editable text`); |
| }, "Move caret from end of editable text node to <br> following non-editable text in next paragraph"); |
| |
| promise_test(async t => { |
| const editingHost = document.querySelector("div[contenteditable]"); |
| editingHost.focus(); |
| const p = editingHost.querySelector("p"); |
| getSelection().collapse(p.nextSibling, 1); |
| await sendArrowLeftKey(); |
| assert_false( |
| editingHost.querySelector("[contenteditable=false]").contains(getSelection().focusNode), |
| "focus node should not be the non-editable nodes" |
| ); |
| assert_false( |
| editingHost.querySelector("[contenteditable=false]").contains(getSelection().anchorNode), |
| "anchor node should not be the non-editable nodes" |
| ); |
| test(() => { |
| assert_equals( |
| getRangeDescription(getSelection().getRangeAt(0)), |
| getRangeDescription({ |
| startContainer: p.nextSibling, |
| startOffset: 0, |
| endContainer: p.nextSibling, |
| endOffset: 0, |
| }), |
| ); |
| }, `${t.name}: first arrow-left should move caret before non-editable text`); |
| }, "Move caret from <br> following non-editable text to end of preceding editable text in next paragraph"); |
| |
| promise_test(async t => { |
| const editingHost = document.querySelector("div[contenteditable] + div[contenteditable]"); |
| editingHost.focus(); |
| const p = editingHost.querySelector("p"); |
| getSelection().collapse(p.firstChild, 0); |
| await sendArrowRightKey(); |
| test(() => { |
| assert_equals( |
| getRangeDescription(getSelection().getRangeAt(0)), |
| getRangeDescription({ |
| startContainer: p.nextSibling, |
| startOffset: 0, |
| endContainer: p.nextSibling, |
| endOffset: 0, |
| }), |
| ); |
| }, `${t.name}: first arrow-right should move caret before non-editable text`); |
| await sendArrowRightKey(); |
| test(() => { |
| assert_equals( |
| getRangeDescription(getSelection().getRangeAt(0)), |
| getRangeDescription({ |
| startContainer: editingHost.querySelector("[contenteditable=false]").nextSibling, |
| startOffset: 0, |
| endContainer: editingHost.querySelector("[contenteditable=false]").nextSibling, |
| endOffset: 0, |
| }), |
| ); |
| }, `${t.name}: second arrow-right should move caret after non-editable text`); |
| }, "Move caret from empty editable paragraph to editable text following non-editable text in next paragraph"); |
| |
| promise_test(async t => { |
| const editingHost = document.querySelector("div[contenteditable] + div[contenteditable]"); |
| editingHost.focus(); |
| const p = editingHost.querySelector("p"); |
| getSelection().collapse(editingHost.querySelector("[contenteditable=false]").nextSibling, 0); |
| await sendArrowLeftKey(); |
| assert_false( |
| editingHost.querySelector("[contenteditable=false]").contains(getSelection().focusNode), |
| "focus node should not be the non-editable nodes" |
| ); |
| assert_false( |
| editingHost.querySelector("[contenteditable=false]").contains(getSelection().anchorNode), |
| "anchor node should not be the non-editable nodes" |
| ); |
| test(() => { |
| assert_equals( |
| getRangeDescription(getSelection().getRangeAt(0)), |
| getRangeDescription({ |
| startContainer: p.nextSibling, |
| startOffset: 0, |
| endContainer: p.nextSibling, |
| endOffset: 0, |
| }), |
| ); |
| }, `${t.name}: first arrow-left should move caret before non-editable text`); |
| }, "Move caret from start of text following non-editable text to empty preceding editable paragraph"); |
| </script> |
| </head> |
| <body> |
| <div contenteditable> |
| <p>abc</p><p><span contenteditable="false">def</span><br></p> |
| </div> |
| <div contenteditable> |
| <p><br></p><p><span contenteditable="false">abc</span>def</p> |
| </div> |
| </body> |
| </html> |