diff --git a/src/react-sortable-tree.js b/src/react-sortable-tree.js index 76df00de..a0295c82 100644 --- a/src/react-sortable-tree.js +++ b/src/react-sortable-tree.js @@ -116,10 +116,9 @@ class ReactSortableTree extends Component { this.handleDndMonitorChange = this.handleDndMonitorChange.bind(this); } - componentWillMount() { + componentDidMount() { this.loadLazyChildren(); - this.search(this.props, false, false); - this.ignoreOneTreeUpdate = false; + this.search(this.props); // Hook into react-dnd state changes to detect when the drag ends // TODO: This is very brittle, so it needs to be replaced if react-dnd @@ -130,14 +129,17 @@ class ReactSortableTree extends Component { } componentWillReceiveProps(nextProps) { - this.setState({ searchFocusTreeIndex: null }); if (this.props.treeData !== nextProps.treeData) { // Ignore updates caused by search, in order to avoid infinite looping if (this.ignoreOneTreeUpdate) { this.ignoreOneTreeUpdate = false; } else { - this.loadLazyChildren(nextProps); + // Reset the focused index if the tree has changed + this.setState({ searchFocusTreeIndex: null }); + // Load any children defined by a function + this.loadLazyChildren(nextProps); + this.search(nextProps, false, false); } @@ -353,6 +355,9 @@ class ReactSortableTree extends Component { newNode: ({ node }) => ({ ...node, expanded: true }), getNodeKey: this.props.getNodeKey, }), + // reset the scroll focus so it doesn't jump back + // to a search result while dragging + searchFocusTreeIndex: null, }); } diff --git a/src/react-sortable-tree.test.js b/src/react-sortable-tree.test.js index cc65e911..2604b670 100644 --- a/src/react-sortable-tree.test.js +++ b/src/react-sortable-tree.test.js @@ -3,7 +3,9 @@ import PropTypes from 'prop-types'; import renderer from 'react-test-renderer'; import { mount } from 'enzyme'; import { List } from 'react-virtualized'; -import SortableTree from './react-sortable-tree'; +import SortableTree, { + SortableTreeWithoutDndContext, +} from './react-sortable-tree'; import sortableTreeStyles from './react-sortable-tree.scss'; import TreeNode from './tree-node'; import treeNodeStyles from './tree-node.scss'; @@ -268,4 +270,53 @@ describe('', () => { expect(wrapper.find(FakeNode).length).toEqual(1); }); + + it('search should call searchFinishCallback', () => { + const searchFinishCallback = jest.fn(); + mount( + {}} + /> + ); + + expect(searchFinishCallback).toHaveBeenCalledWith([ + // Node should be found expanded + { node: { title: 'b' }, path: [0, 1], treeIndex: 1 }, + ]); + }); + + it('search should expand all matches and seek out the focus offset', () => { + const wrapper = mount( + {}} + /> + ); + + const tree = wrapper.find(SortableTreeWithoutDndContext).instance(); + expect(tree.state.searchMatches).toEqual([ + { node: { title: 'b' }, path: [0, 1], treeIndex: 1 }, + { node: { title: 'be' }, path: [2, 3], treeIndex: 3 }, + ]); + expect(tree.state.searchFocusTreeIndex).toEqual(null); + + wrapper.setProps({ searchFocusOffset: 0 }); + expect(tree.state.searchFocusTreeIndex).toEqual(1); + + wrapper.setProps({ searchFocusOffset: 1 }); + // As the empty `onChange` we use here doesn't actually change + // the tree, the expansion of all nodes doesn't get preserved + // after the first mount, and this change in searchFocusOffset + // only triggers the opening of a single path. + // Therefore it's 2 instead of 3. + expect(tree.state.searchFocusTreeIndex).toEqual(2); + }); });