diff --git a/src/client/automation/playback/type/type-text.js b/src/client/automation/playback/type/type-text.js index 1f0d99fad89..55fae79be0e 100644 --- a/src/client/automation/playback/type/type-text.js +++ b/src/client/automation/playback/type/type-text.js @@ -64,6 +64,26 @@ function _typeTextInElementNode (elementNode, text, offset) { textSelection.selectByNodesAndOffsets(selectPosition, selectPosition); } +function _typeTextInChildTextNode (element, selection, text) { + let startNode = selection.startPos.node; + + // NOTE: startNode could be moved or deleted on textInput event. Need ensure startNode. + if (!domUtils.isElementContainsNode(element, startNode)) { + selection = _excludeInvisibleSymbolsFromSelection(_getSelectionInElement(element)); + startNode = selection.startPos.node; + } + + const startOffset = selection.startPos.offset; + const endOffset = selection.endPos.offset; + const nodeValue = startNode.nodeValue; + const selectPosition = { node: startNode, offset: startOffset + text.length }; + + startNode.nodeValue = nodeValue.substring(0, startOffset) + text + + nodeValue.substring(endOffset, nodeValue.length); + + textSelection.selectByNodesAndOffsets(selectPosition, selectPosition); +} + function _excludeInvisibleSymbolsFromSelection (selection) { var startNode = selection.startPos.node; var startOffset = selection.startPos.offset; @@ -84,26 +104,44 @@ function _excludeInvisibleSymbolsFromSelection (selection) { return selection; } +// NOTE: typing can be prevented in Chrome/Edge but can not be prevented in IE11 or Firefox +// Firefox does not support TextInput event +// Safari support TextInput event but adds e.data to node value. So let's ignore it +function simulateTextInput (element, text) { + const isTextInputIgnoredByBrowser = [ browserUtils.isFirefox, browserUtils.isSafari ].some(browser => browser); + const isInputEventRequired = isTextInputIgnoredByBrowser || eventSimulator.textInput(element, text); + + return isInputEventRequired || browserUtils.isIE11 || browserUtils.isFirefox; +} + function _typeTextToContentEditable (element, text) { - var currentSelection = _getSelectionInElement(element); - var startNode = currentSelection.startPos.node; - var endNode = currentSelection.endPos.node; + var currentSelection = _getSelectionInElement(element); + var startNode = currentSelection.startPos.node; + var endNode = currentSelection.endPos.node; + var needProcessInput = true; + var needRaiseInputEvent = true; // NOTE: some browsers raise the 'input' event after the element // content is changed, but in others we should do it manually. - var inputEventRaised = false; var onInput = () => { - inputEventRaised = true; + needRaiseInputEvent = false; + }; + + // NOTE: IE11 does not raise input event when type to contenteditable + + var beforeContentChanged = () => { + needProcessInput = simulateTextInput(element, text); + needRaiseInputEvent = needProcessInput && !browserUtils.isIE11; }; var afterContentChanged = () => { nextTick() .then(() => { - if (!inputEventRaised) + if (needRaiseInputEvent) eventSimulator.input(element); - listeners.removeInternalEventListener(window, 'input', onInput); + listeners.removeInternalEventListener(window, ['input'], onInput); }); }; @@ -126,27 +164,16 @@ function _typeTextToContentEditable (element, text) { if (!startNode || !domUtils.isContentEditableElement(startNode) || !domUtils.isRenderedNode(startNode)) return; - // NOTE: we can type only to the text nodes; for nodes with the 'element-node' type, we use a special behavior - if (domUtils.isElementNode(startNode)) { - _typeTextInElementNode(startNode, text, currentSelection.startPos.offset); + beforeContentChanged(); - afterContentChanged(); - return; + if (needProcessInput) { + // NOTE: we can type only to the text nodes; for nodes with the 'element-node' type, we use a special behavior + if (domUtils.isElementNode(startNode)) + _typeTextInElementNode(startNode, text); + else + _typeTextInChildTextNode(element, _excludeInvisibleSymbolsFromSelection(currentSelection), text); } - currentSelection = _excludeInvisibleSymbolsFromSelection(currentSelection); - startNode = currentSelection.startPos.node; - - var startOffset = currentSelection.startPos.offset; - var endOffset = currentSelection.endPos.offset; - var nodeValue = startNode.nodeValue; - var selectPosition = { node: startNode, offset: startOffset + text.length }; - - startNode.nodeValue = nodeValue.substring(0, startOffset) + text + - nodeValue.substring(endOffset, nodeValue.length); - - textSelection.selectByNodesAndOffsets(selectPosition, selectPosition); - afterContentChanged(); } @@ -156,6 +183,10 @@ function _typeTextToTextEditable (element, text) { var startSelection = textSelection.getSelectionStart(element); var endSelection = textSelection.getSelectionEnd(element); var isInputTypeNumber = domUtils.isInputElement(element) && element.type === 'number'; + var needProcessInput = simulateTextInput(element, text); + + if (!needProcessInput) + return; // NOTE: the 'maxlength' attribute doesn't work in all browsers. IE still doesn't support input with the 'number' type var elementMaxLength = !browserUtils.isIE && isInputTypeNumber ? null : parseInt(element.maxLength, 10); diff --git a/test/client/fixtures/automation/content-editable/api-actions-content-editable-test/index-test.js b/test/client/fixtures/automation/content-editable/api-actions-content-editable-test/index-test.js index 42a25d3ab26..6bed0f723d1 100644 --- a/test/client/fixtures/automation/content-editable/api-actions-content-editable-test/index-test.js +++ b/test/client/fixtures/automation/content-editable/api-actions-content-editable-test/index-test.js @@ -439,6 +439,9 @@ $(document).ready(function () { var fixedText = 'Test' + String.fromCharCode(160) + 'me' + String.fromCharCode(160) + 'all!'; var inputEventRaisedCount = 0; + // NOTE IE11 does not raise input event on contenteditable element + var expectedInputEventRaisedCount = !browserUtils.isIE11 ? 12 : 0; + $el = $('#2'); function onInput () { @@ -454,7 +457,7 @@ $(document).ready(function () { .then(function () { checkSelection($el, $el[0].childNodes[2], 4 + text.length, $el[0].childNodes[2], 4 + text.length); equal($.trim($el[0].childNodes[2].nodeValue), 'with' + fixedText + ' br'); - equal(inputEventRaisedCount, 12); + equal(inputEventRaisedCount, expectedInputEventRaisedCount); $el.unbind('input', onInput); startNext(); @@ -465,6 +468,9 @@ $(document).ready(function () { var text = 'Test'; var inputEventRaisedCount = 0; + // NOTE IE11 does not raise input event on contenteditable element + var expectedInputEventRaisedCount = !browserUtils.isIE11 ? 4 : 0; + $el = $('#8'); function onInput () { @@ -479,7 +485,7 @@ $(document).ready(function () { .run() .then(function () { equal($.trim($el[0].textContent), text); - equal(inputEventRaisedCount, 4); + equal(inputEventRaisedCount, expectedInputEventRaisedCount); $el.unbind('input', onInput); startNext(); diff --git a/test/functional/fixtures/regression/gh-1054/test.js b/test/functional/fixtures/regression/gh-1054/test.js index 7f65adcb5e6..88372716712 100644 --- a/test/functional/fixtures/regression/gh-1054/test.js +++ b/test/functional/fixtures/regression/gh-1054/test.js @@ -1,11 +1,12 @@ -describe('[Regression](GH-1054)', function () { +describe.only('[Regression](GH-1054)', function () { describe('Target element should contain the first symbol of text input when the input event raised at the first time', function () { it('Typing in the input element with "replace" option', function () { return runTests('testcafe-fixtures/index.test.js', 'Type text in the input'); }); + // NOTE: IE11 does not raise Input event on contenteditable element it('Typing in the content editable element with "replace" option', function () { - return runTests('testcafe-fixtures/index.test.js', 'Type text in the content editable element'); + return runTests('testcafe-fixtures/index.test.js', 'Type text in the content editable element', { skip: [ 'ie' ] }); }); }); }); diff --git a/test/functional/fixtures/regression/gh-1956/pages/index.html b/test/functional/fixtures/regression/gh-1956/pages/index.html new file mode 100644 index 00000000000..2af849c902f --- /dev/null +++ b/test/functional/fixtures/regression/gh-1956/pages/index.html @@ -0,0 +1,105 @@ + +
+ Chrome/Edge. Typing is prevented and Input event is not raised + IE11/Firefox. Typing is not prevented. Input event is raised ++ +
+ Chrome/Edge. Typing is prevented. Input event is not raised + IE11/Firefox. Typing is not prevented. + Input event is raised in firefox but is not raised in IE11 - it's a IE11 bug ++ +
+ Not for IE11 because preventDefault will not prevent typing + Not for Firefox because Firefox does not support TextInput event ++
+ Not for IE11 because is's not possible to prevent typing in IE11 + Not for Firefox because Firefox does not support TextInput event ++
A
+ Not for IE11 because this test emulates behavior from https://github.com/DevExpress/testcafe/issues/1956. + This behavior is different in IE11 + Not for Firefox because Firefox does not support TextInput event ++
B