diff --git a/src/client/core/utils/content-editable.js b/src/client/core/utils/content-editable.js index 668a3171e6c..6bb63b3bf19 100644 --- a/src/client/core/utils/content-editable.js +++ b/src/client/core/utils/content-editable.js @@ -110,24 +110,25 @@ function isNodeAfterNodeBlockWithBreakLine (parent, node) { return false; } -export function getFirstVisibleTextNode (el) { +function getFirstTextNode (el, onlyVisible) { var children = el.childNodes; var childrenLength = domUtils.getChildNodesLength(children); var curNode = null; var child = null; var isNotContentEditableElement = null; + var checkTextNode = onlyVisible ? isVisibleTextNode : domUtils.isTextNode; - if (!childrenLength && isVisibleTextNode(el)) + if (!childrenLength && checkTextNode(el)) return el; for (var i = 0; i < childrenLength; i++) { curNode = children[i]; isNotContentEditableElement = domUtils.isElementNode(curNode) && !domUtils.isContentEditableElement(curNode); - if (isVisibleTextNode(curNode)) + if (checkTextNode(curNode)) return curNode; else if (domUtils.isRenderedNode(curNode) && hasVisibleChildren(curNode) && !isNotContentEditableElement) { - child = getFirstVisibleTextNode(curNode); + child = getFirstTextNode(curNode, onlyVisible); if (child) return child; @@ -137,6 +138,10 @@ export function getFirstVisibleTextNode (el) { return child; } +export function getFirstVisibleTextNode (el) { + return getFirstTextNode(el, true); +} + export function getLastTextNode (el, onlyVisible) { var children = el.childNodes; var childrenLength = domUtils.getChildNodesLength(children); @@ -533,16 +538,37 @@ export function getFirstVisiblePosition (el) { export function getLastVisiblePosition (el) { var lastVisibleTextChild = domUtils.isTextNode(el) ? el : getLastTextNode(el, true); - var curDocument = domUtils.findDocument(el); - var range = curDocument.createRange(); - if (lastVisibleTextChild) { - range.selectNodeContents(lastVisibleTextChild); + if (!lastVisibleTextChild || isResetAnchorOffsetRequired(lastVisibleTextChild, el)) + return 0; + + var curDocument = domUtils.findDocument(el); + var range = curDocument.createRange(); - return calculatePositionByNodeAndOffset(el, { node: lastVisibleTextChild, offset: range.endOffset }); + range.selectNodeContents(lastVisibleTextChild); + + return calculatePositionByNodeAndOffset(el, { node: lastVisibleTextChild, offset: range.endOffset }); +} + +function isResetAnchorOffsetRequired (lastVisibleTextChild, el) { + const firstVisibleTextChild = domUtils.isTextNode(el) ? el : getFirstTextNode(el, false); + const isSingleTextNode = lastVisibleTextChild === firstVisibleTextChild; + const isNewLineChar = lastVisibleTextChild.nodeValue === String.fromCharCode(10); + + return isSingleTextNode && isNewLineChar && hasWhiteSpacePreStyle(lastVisibleTextChild, el); +} + +function hasWhiteSpacePreStyle (el, container) { + const whiteSpacePreStyles = ['pre', 'pre-wrap', 'pre-line']; + + while (el !== container) { + el = el.parentNode; + + if (arrayUtils.indexOf(whiteSpacePreStyles, styleUtils.get(el, 'white-space')) > -1) + return true; } - return 0; + return false; } function getContentEditableNodes (target) { diff --git a/test/client/fixtures/automation/content-editable/regression-test/index-test.js b/test/client/fixtures/automation/content-editable/regression-test/index-test.js index 50fdb987639..0ac90b75f39 100644 --- a/test/client/fixtures/automation/content-editable/regression-test/index-test.js +++ b/test/client/fixtures/automation/content-editable/regression-test/index-test.js @@ -1,8 +1,8 @@ var hammerhead = window.getTestCafeModule('hammerhead'); var browserUtils = hammerhead.utils.browser; -var testCafeCore = window.getTestCafeModule('testCafeCore'); -var domUtils = testCafeCore.get('./utils/dom'); +var testCafeCore = window.getTestCafeModule('testCafeCore'); +var domUtils = testCafeCore.get('./utils/dom'); var testCafeAutomation = window.getTestCafeModule('testCafeAutomation'); var TypeOptions = testCafeAutomation.get('../../test-run/commands/options').TypeOptions; @@ -142,4 +142,48 @@ $(document).ready(function () { }); }); } + + asyncTest('selection after mousedown should ignore single new line character', function () { + + function testWithWhiteSpaceStyle (whiteSpace) { + var editor = document.createElement('div'); + var span = document.createElement('span'); + var type = new TypeAutomation(editor, 'Hello World', {}); + + editor.className = TEST_ELEMENT_CLASS; + editor.style.whiteSpace = whiteSpace; + editor.contentEditable = true; + span.innerHTML = String.fromCharCode(10); + + editor.appendChild(span); + document.body.appendChild(editor); + + var onSelectionChange = function () { + equal(document.getSelection().anchorOffset, 0); + document.removeEventListener('selectionchange', onSelectionChange, true); + }; + + document.addEventListener('selectionchange', onSelectionChange, true); + + return type + .run() + .then(function () { + equal(editor.textContent, 'Hello' + String.fromCharCode(160) + 'World\n', 'white-space: ' + whiteSpace); + removeTestElements(); + document.getSelection().removeAllRanges(); + return; + }); + } + + testWithWhiteSpaceStyle('pre') + .then(function () { + return testWithWhiteSpaceStyle('pre-wrap'); + }) + .then(function () { + return testWithWhiteSpaceStyle('pre-line'); + }) + .then(function () { + startNext(); + }); + }); });