diff --git a/src/component/contents/DraftEditorLeaf.react.js b/src/component/contents/DraftEditorLeaf.react.js index c41ea9f72d..4e3da3b99a 100644 --- a/src/component/contents/DraftEditorLeaf.react.js +++ b/src/component/contents/DraftEditorLeaf.react.js @@ -20,7 +20,8 @@ const React = require('React'); const invariant = require('invariant'); const isHTMLBRElement = require('isHTMLBRElement'); -const setDraftEditorSelection = require('setDraftEditorSelection'); +const setDraftEditorSelection = require('setDraftEditorSelection') + .setDraftEditorSelection; type Props = { // The block that contains this leaf. diff --git a/src/component/selection/__tests__/__snapshots__/setDraftEditorSelection-test.js.snap b/src/component/selection/__tests__/__snapshots__/setDraftEditorSelection-test.js.snap new file mode 100644 index 0000000000..1652ab3fe3 --- /dev/null +++ b/src/component/selection/__tests__/__snapshots__/setDraftEditorSelection-test.js.snap @@ -0,0 +1,23 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`addFocusToSelection sets a new focus on the selection if selection.extend is unsupported 1`] = ` +Selection { + "focusNode": Washington, + "focusOffset": 0, + "range": Range { + "endOffset": 3, + "node": Washington, + "startOffset": 0, + }, + "rangeCount": 1, +} +`; + +exports[`addFocusToSelection the range is not updated if rangeCount is 0 1`] = ` +Selection { + "focusNode": null, + "focusOffset": 0, + "range": null, + "rangeCount": 0, +} +`; diff --git a/src/component/selection/__tests__/setDraftEditorSelection-test.js b/src/component/selection/__tests__/setDraftEditorSelection-test.js new file mode 100644 index 0000000000..a5759fe2c3 --- /dev/null +++ b/src/component/selection/__tests__/setDraftEditorSelection-test.js @@ -0,0 +1,106 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails oncall+draft_js + * @format + */ + +'use strict'; + +jest.disableAutomock(); + +const addFocusToSelection = require('setDraftEditorSelection') + .addFocusToSelection; +const getSampleSelectionMocksForTesting = require('getSampleSelectionMocksForTesting'); + +// Based on https://w3c.github.io/selection-api/#selection-interface +class Selection { + constructor({range}) { + this.rangeCount = range ? 1 : 0; + this.focusNode = (range && range.node) || null; + this.focusOffset = (range && range.startOffset) || 0; + this.range = range || null; + } + + getRangeAt(idx) { + if (idx !== 0 || this.rangeCount <= 0) { + throw new Error('IndexSizeError'); + } + return this.range; + } + + addRange(range) { + this.range = range; + this.rangeCount = 1; + } +} + +// Based on https://dom.spec.whatwg.org/#concept-range +class Range { + constructor({startOffset, endOffset, node}) { + this.startOffset = startOffset; + this.endOffset = endOffset; + this.node = node; + } + + setEnd(node, offset) { + this.endOffset = offset; + this.node = node; + } + + cloneRange() { + return new Range({ + startOffset: this.startOffset, + endOffset: this.endOffset, + node: this.node, + }); + } +} + +let editorState = null; +let textNodes = null; + +const resetRootNodeMocks = () => { + ({editorState, textNodes} = getSampleSelectionMocksForTesting()); +}; + +beforeEach(() => { + resetRootNodeMocks(); +}); + +describe('addFocusToSelection', () => { + test('sets a new focus on the selection if selection.extend is unsupported', () => { + const range = new Range({ + startOffset: 0, + endOffset: 0, + node: textNodes[0], + }); + const selection = new Selection({range}); + const storedFocusNode = selection.focusNode; + const storedFocusOffset = 3; + addFocusToSelection( + selection, + storedFocusNode, + storedFocusOffset, + editorState.getSelection(), + ); + expect(selection).toMatchSnapshot(); + }); + + // If rangeCount is 0, selection.getRangeAt() will throw on various browsers + test('the range is not updated if rangeCount is 0', () => { + const selection = new Selection({}); + const storedFocusNode = selection.focusNode; + const storedFocusOffset = 3; + addFocusToSelection( + selection, + storedFocusNode, + storedFocusOffset, + editorState.getSelection(), + ); + expect(selection).toMatchSnapshot(); + }); +}); diff --git a/src/component/selection/setDraftEditorSelection.js b/src/component/selection/setDraftEditorSelection.js index 0b8f4fc94f..88d53f8e18 100644 --- a/src/component/selection/setDraftEditorSelection.js +++ b/src/component/selection/setDraftEditorSelection.js @@ -306,9 +306,11 @@ function addFocusToSelection( // Additionally, clone the selection range. IE11 throws an // InvalidStateError when attempting to access selection properties // after the range is detached. - const range = selection.getRangeAt(0); - range.setEnd(node, offset); - selection.addRange(range.cloneRange()); + if (selection.rangeCount > 0) { + const range = selection.getRangeAt(0); + range.setEnd(node, offset); + selection.addRange(range.cloneRange()); + } } } @@ -333,4 +335,7 @@ function addPointToSelection( selection.addRange(range); } -module.exports = setDraftEditorSelection; +module.exports = { + setDraftEditorSelection, + addFocusToSelection, +};