text: /* eslint-disable no-undef, react/no-multi-comp, no-console,
  react/no-unused-state, react/prop-types, no-return-assign, import/no-named-as-default-member */
  import React from 'react';
  import { render, mount } from 'enzyme';
  import { renderToJson } from 'enzyme-to-json';
  import { resetWarned } from 'rc-util/lib/warning';
  import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
  import VirtualList from 'rc-virtual-list';
  import Tree, { TreeNode } from '../src';
  import { InternalTreeNode } from '../src/TreeNode';
  import { objectMatcher, spyConsole, spyError } from './util';
  import { convertNodePropsToEventData } from '../src/utils/treeUtil';
  
  const OPEN_CLASSNAME = '.rc-tree-switcher_open';
  const CHECKED_CLASSNAME = '.rc-tree-checkbox-checked';
  const SELECTED_CLASSNAME = '.rc-tree-node-selected';
  
  const delay = timeout =>
    new Promise(resolve => {
      setTimeout(resolve, timeout);
    });
  
  describe('Tree Basic', () => {
    spyConsole();
  
    it('TreeNode is in Tree', () => {
      expect(TreeNode).toBe(Tree.TreeNode);
    });
  
    it('renders correctly', () => {
      const wrapper = render(
        <Tree
          className="forTest"
          selectable
          checkable
          defaultExpandAll
          showIcon
          showLine
          multiple
          focusable
        >
          <TreeNode title="parent 1" key="0-0" className="spe">
            <TreeNode title="leaf 1" key="0-0-0" disabled>
              <TreeNode title="leaf" key="random" />
              <TreeNode title="leaf" />
              {null /* Supports conditional rendering */}
            </TreeNode>
            <TreeNode title="leaf 2" key="0-0-1" disableCheckbox />
          </TreeNode>
        </Tree>,
      );
      expect(renderToJson(wrapper)).toMatchSnapshot();
    });
  
    it('switcherIcon = null, no render rc-tree-switcher null', () => {
      const wrapper = mount(
        <Tree className="forTest" defaultExpandAll switcherIcon={() => false}>
          <TreeNode title="parent 1" key="0-0" className="spe">
            <TreeNode title="leaf 1" key="0-0-0" disabled>
              <TreeNode title="leaf" key="random" />
              <TreeNode title="leaf" />
            </TreeNode>
            <TreeNode title="leaf 2" key="0-0-1" disableCheckbox />
          </TreeNode>
        </Tree>,
      );
      wrapper.update();
      expect(wrapper.find('.rc-tree-switcher').exists()).toBeFalsy();
    });
  
    describe('expanded', () => {
      it('expands all keys', () => {
        const wrapper = mount(
          <Tree defaultExpandAll>
            <TreeNode title="parent 1" key="0-0">
              <TreeNode title="leaf 1" key="0-0-0" />
            </TreeNode>
          </Tree>,
        );
        const switcher = wrapper.find('.rc-tree-switcher').first();
        expect(switcher.is(OPEN_CLASSNAME)).toBe(true);
      });
  
      it('expands default expanded keys', () => {
        const wrapper = mount(
          <Tree defaultExpandedKeys={['0-0']}>
            <TreeNode title="parent 1" key="0-0">
              <TreeNode title="leaf 1" key="0-0-0" />
            </TreeNode>
          </Tree>,
        );
        const switcher = wrapper.find('.rc-tree-switcher').first();
        expect(switcher.is(OPEN_CLASSNAME)).toBe(true);
      });
  
      it('controlled by expanded keys', () => {
        const wrapper = mount(
          <Tree expandedKeys={[]}>
            <TreeNode title="parent 1" key="0-0">
              <TreeNode title="leaf 1" key="0-0-0" />
            </TreeNode>
          </Tree>,
        );
        const getSwitcher = () => wrapper.find('.rc-tree-switcher').first();
        expect(getSwitcher().is(OPEN_CLASSNAME)).toBe(false);
        wrapper.setProps({ expandedKeys: ['0-0'] });
        expect(getSwitcher().is(OPEN_CLASSNAME)).toBe(true);
      });
  
      it('expands parent node when child node is expanded', () => {
        const wrapper = mount(
          <Tree expandedKeys={['0-0-0']}>
            <TreeNode title="parent 1" key="0-0">
              <TreeNode title="leaf 1" key="0-0-0">
                <TreeNode title="leaf" key="0-0-0-0" />
              </TreeNode>
            </TreeNode>
          </Tree>,
        );
  
        const switcher = wrapper.find('.rc-tree-switcher').first();
        expect(switcher.is(OPEN_CLASSNAME)).toBe(true);
      });
  
      it('use treeData to expand the parent node when the parent node key type is numeric', () => {
        const Demo = () => (
          <Tree
            defaultExpandParent
            defaultExpandedKeys={[22]}
            treeData={[{ key: 11, title: 11, children: [{ key: 22, title: 22 }] }]}
          />
        );
  
        const wrapper = mount(<Demo />);
  
        const switcher = wrapper.find('.rc-tree-switcher').first();
        expect(switcher.is(OPEN_CLASSNAME)).toBe(true);
      });
  
      it('does not expand parent node when autoExpandParent is false', () => {
        const wrapper = mount(
          <Tree expandedKeys={['0-0-0']} defaultExpandParent={false}>
            <TreeNode title="parent 1" key="0-0">
              <TreeNode title="leaf 1" key="0-0-0">
                <TreeNode title="leaf" key="0-0-0-0" />
              </TreeNode>
            </TreeNode>
          </Tree>,
        );
        const switcher = wrapper.find('.rc-tree-switcher').first();
        expect(switcher.is(OPEN_CLASSNAME)).toBe(false);
      });
  
      it('update to expand parent node with autoExpandParent', () => {
        const wrapper = mount(
          <Tree expandedKeys={['0-0-0']} defaultExpandParent={false}>
            <TreeNode title="parent 1" checkable={false} key="0-0">
              <TreeNode title="leaf 1" key="0-0-0">
                <TreeNode title="leaf" key="0-0-0-0" />
              </TreeNode>
            </TreeNode>
          </Tree>,
        );
        let parentSwitcher = wrapper.find('.rc-tree-switcher').first();
        expect(parentSwitcher.is(OPEN_CLASSNAME)).toBe(false);
  
        wrapper.setProps({ autoExpandParent: true });
  
        parentSwitcher = wrapper.find('.rc-tree-switcher').first();
        const childSwitcher = wrapper.find('.rc-tree-switcher').at(1);
        expect(parentSwitcher.is(OPEN_CLASSNAME)).toBe(true);
        expect(childSwitcher.is(OPEN_CLASSNAME)).toBe(true);
      });
  
      it('skip only if disabled with autoExpandParent', () => {
        const wrapper = mount(
          <Tree expandedKeys={['0-0-0-0']} defaultExpandParent>
            <TreeNode title="parent 1" key="0-0">
              <TreeNode title="leaf 1" disabled key="0-0-0">
                <TreeNode title="leaf" key="0-0-0-0" />
              </TreeNode>
            </TreeNode>
          </Tree>,
        );
  
        expect(wrapper.state().expandedKeys.sort()).toEqual(['0-0-0', '0-0-0-0']);
      });
  
      it('fires expand event', () => {
        const handleExpand = jest.fn();
        const wrapper = mount(
          <Tree onExpand={handleExpand}>
            <TreeNode title="parent 1" key="0-0">
              <TreeNode title="leaf 1" key="0-0-0" />
            </TreeNode>
          </Tree>,
        );
        const switcher = wrapper.find('.rc-tree-switcher');
  
        let node = convertNodePropsToEventData(
          wrapper
            .find(InternalTreeNode)
            .first()
            .props(),
        );
        switcher.simulate('click');
        expect(handleExpand).toHaveBeenCalledWith(['0-0'], {
          expanded: true,
          node,
          nativeEvent: expect.anything(),
        });
  
        node = convertNodePropsToEventData(
          wrapper
            .find(InternalTreeNode)
            .first()
            .props(),
        );
        switcher.simulate('click');
        expect(handleExpand).toHaveBeenCalledWith([], {
          expanded: false,
          node,
          nativeEvent: expect.anything(),
        });
      });
    });
  
    describe('check', () => {
      it('basic render', () => {
        const wrapper = render(
          <Tree checkable defaultExpandAll>
            <TreeNode key="0-0">
              <TreeNode key="0-0-0" disabled />
              <TreeNode key="0-0-1" />
            </TreeNode>
          </Tree>,
        );
  
        expect(wrapper).toMatchSnapshot();
      });
  
      it('checks default checked keys', () => {
        const wrapper = mount(
          <Tree checkable defaultCheckedKeys={['0-0']}>
            <TreeNode title="parent 1" key="0-0">
              <TreeNode title="leaf 1" key="0-0-0" />
            </TreeNode>
          </Tree>,
        );
        wrapper.find('.rc-tree-switcher').simulate('click');
        wrapper.find('.rc-tree-checkbox').forEach(checkbox => {
          expect(checkbox.is(CHECKED_CLASSNAME)).toBe(true);
        });
      });
  
      it("ignore disabled children when calculate parent's checked status", () => {
        const wrapper = mount(
          <Tree checkable defaultCheckedKeys={['0-0-0']}>
            <TreeNode title="parent 1" key="0-0">
              <TreeNode title="leaf 1" key="0-0-0" disableCheckbox />
              <TreeNode title="leaf 1" key="0-0-1" />
            </TreeNode>
          </Tree>,
        );
        const firstCheckboxClassNames = wrapper.find('.rc-tree-checkbox').instance().classList;
        expect([].slice.call(firstCheckboxClassNames).includes(CHECKED_CLASSNAME.slice(1))).toBe(
          false,
        );
      });
  
      it('controlled by checkedKeys', () => {
        const wrapper = mount(
          <Tree checkable checkedKeys={[]}>
            <TreeNode title="parent 1" key="0-0">
              <TreeNode title="leaf 1" key="0-0-0" />
            </TreeNode>
          </Tree>,
        );
        const getCheckbox = () => wrapper.find('.rc-tree-checkbox');
        expect(getCheckbox().is(CHECKED_CLASSNAME)).toBe(false);
        wrapper.setProps({ checkedKeys: ['0-0'] });
        expect(getCheckbox().is(CHECKED_CLASSNAME)).toBe(true);
      });
  
      it('trurns parent node to checked when all children are checked', () => {
        const wrapper = mount(
          <Tree checkable>
            <TreeNode title="parent 1" key="0-0">
              <TreeNode title="leaf 1" key="0-0-0" />
              <TreeNode title="leaf 2" key="0-0-1" />
            </TreeNode>
          </Tree>,
        );
        wrapper.find('.rc-tree-switcher').simulate('click');
        wrapper
          .find('.rc-tree-checkbox')
          .at(1)
          .simulate('click');
        wrapper
          .find('.rc-tree-checkbox')
          .at(2)
          .simulate('click');
        expect(
          wrapper
            .find('.rc-tree-checkbox')
            .first()
            .is(CHECKED_CLASSNAME),
        ).toBe(true);
      });
  
      it('turns parent node to half checked when child is checked', () => {
        const wrapper = mount(
          <Tree checkable>
            <TreeNode title="parent 1" key="0-0">
              <TreeNode title="leaf 1" key="0-0-0" />
              <TreeNode title="leaf 2" key="0-0-1" />
            </TreeNode>
          </Tree>,
        );
        wrapper.find('.rc-tree-switcher').simulate('click');
        wrapper
          .find('.rc-tree-checkbox')
          .last()
          .simulate('click');
        expect(
          wrapper
            .find('.rc-tree-checkbox')
            .first()
            .is('.rc-tree-checkbox-indeterminate'),
        ).toBe(true);
      });
  
      it('turns parent node to checked if it is half checked', () => {
        const wrapper = mount(
          <Tree checkable>
            <TreeNode title="parent 1" key="0-0">
              <TreeNode title="leaf 1" key="0-0-0" />
              <TreeNode title="leaf 2" key="0-0-1" />
            </TreeNode>
          </Tree>,
        );
        wrapper.find('.rc-tree-switcher').simulate('click');
        wrapper
          .find('.rc-tree-checkbox')
          .last()
          .simulate('click');
        wrapper
          .find('.rc-tree-checkbox')
          .first()
          .simulate('click');
        wrapper.find('.rc-tree-checkbox').forEach(checkbox => {
          expect(checkbox.is(CHECKED_CLASSNAME)).toBe(true);
        });
      });
  
      it('fires check event', () => {
        const handleCheck = jest.fn();
        const wrapper = mount(
          <Tree checkable onCheck={handleCheck}>
            <TreeNode title="parent 1" key="0-0">
              <TreeNode title="leaf 1" key="0-0-0" />
            </TreeNode>
          </Tree>,
        );
        wrapper.find('.rc-tree-switcher').simulate('click');
  
        const nodeData00 = { title: 'parent 1', key: '0-0' };
        const nodeData000 = { title: 'leaf 1', key: '0-0-0' };
  
        let node = convertNodePropsToEventData(
          wrapper
            .find(InternalTreeNode)
            .first()
            .props(),
        );
        wrapper
          .find('.rc-tree-checkbox')
          .first()
          .simulate('click');
        expect(handleCheck).toHaveBeenCalledWith(
          ['0-0', '0-0-0'],
          objectMatcher({
            checked: true,
            checkedNodes: [nodeData00, nodeData000],
            checkedNodesPositions: [
              { node: nodeData00, pos: '0-0' },
              { node: nodeData000, pos: '0-0-0' },
            ],
            event: 'check',
            halfCheckedKeys: [],
            node,
            nativeEvent: {},
          }),
        );
  
        node = convertNodePropsToEventData(
          wrapper
            .find(InternalTreeNode)
            .last()
            .props(),
        );
        wrapper
          .find('.rc-tree-checkbox')
          .last()
          .simulate('click');
        expect(handleCheck).toHaveBeenCalledWith(
          [],
          objectMatcher({
            checked: false,
            checkedNodes: [],
            checkedNodesPositions: [],
            event: 'check',
            halfCheckedKeys: [],
            node,
            nativeEvent: {},
          }),
        );
      });
  
      // https://github.com/react-component/tree/issues/117
      it('check works correctly after dragging children under another node', () => {
        const wrapper = mount(
          <Tree defaultExpandAll checkable>
            <TreeNode title="parent 1" key="0-0">
              <TreeNode title="leaf 1" key="0-0-0" />
              <TreeNode title="leaf 2" key="0-0-1" />
            </TreeNode>
          </Tree>,
        );
        wrapper
          .find('.rc-tree-checkbox')
          .at(1)
          .simulate('click');
        wrapper.setProps({
          children: (
            <TreeNode title="parent 1" key="0-0">
              <TreeNode title="leaf 2" key="0-0-1">
                <TreeNode title="leaf 1" key="0-0-0" />
              </TreeNode>
            </TreeNode>
          ),
        });
        expect(() =>
          wrapper
            .find('.rc-tree-checkbox')
            .at(2)
            .simulate('click'),
        ).not.toThrow();
      });
      // https://github.com/react-component/tree/issues/90
      it('check works correctly after adding children dynamically', () => {
        const wrapper = mount(
          <Tree defaultExpandAll checkable>
            <TreeNode title="parent 1" key="0-0">
              <TreeNode title="leaf 1" key="0-0-0" />
            </TreeNode>
          </Tree>,
        );
        wrapper
          .find('.rc-tree-checkbox')
          .at(1)
          .simulate('click');
        wrapper.setProps({
          children: (
            <TreeNode title="parent 1" key="0-0">
              <TreeNode title="leaf 1" key="0-0-0" />
              <TreeNode title="leaf 2" key="0-0-1" />
            </TreeNode>
          ),
        });
        expect(() =>
          wrapper
            .find('.rc-tree-checkbox')
            .at(2)
            .simulate('click'),
        ).not.toThrow();
      });
  
      // https://github.com/ant-design/ant-design/issues/7353
      it('check children after changing from children[disableCheckbox] from true to false', () => {
        let checkedKeys = null;
        const mockHandleCheck = keys => (checkedKeys = keys);
        function Test({ disableCheckbox }) {
          return (
            <Tree checkable onCheck={mockHandleCheck}>
              <TreeNode title="parent 1" key="0-0">
                <TreeNode title="leaf 1" key="0-0-1" disableCheckbox={disableCheckbox} />
                <TreeNode title="leaf 2" key="0-0-2" disableCheckbox={disableCheckbox} />
                <TreeNode title="leaf 3" key="0-0-3" disableCheckbox={disableCheckbox} />
              </TreeNode>
            </Tree>
          );
        }
        const wrapper = mount(<Test disableCheckbox />);
        wrapper
          .find('.rc-tree-checkbox')
          .first()
          .simulate('click');
        expect(checkedKeys).toEqual(['0-0']);
        wrapper.setProps({ disableCheckbox: false });
        wrapper
          .find('.rc-tree-checkbox')
          .first()
          .simulate('click')
          .simulate('click');
        expect(checkedKeys).toEqual(['0-0', '0-0-1', '0-0-2', '0-0-3']);
      });
  
      it('check dynamic children when their parent is checked', () => {
        function mockLoadData() {}
        const wrapper = mount(
          <Tree checkable defaultCheckedKeys={['0-0']} loadData={mockLoadData}>
            <TreeNode title="parent 1" key="0-0" />
          </Tree>,
        );
        wrapper.setProps({
          children: (
            <TreeNode title="parent 1" key="0-0">
              <TreeNode title="leaf 1" key="0-0-0" />
              <TreeNode title="leaf 2" key="0-0-1" />
            </TreeNode>
          ),
        });
        expect(wrapper.state('checkedKeys')).toEqual(['0-0', '0-0-0', '0-0-1']);
      });
  
      // https://github.com/ant-design/ant-design/issues/10132
      it('check update when Tree trigger componentWillReceiveProps', () => {
        class Test extends React.Component {
          state = {};
  
          onCheck = () => {
            this.setState({ whatever: 1 });
          };
  
          render() {
            return (
              <Tree checkable>
                <TreeNode title="parent 1" key="0-0" />
              </Tree>
            );
          }
        }
        const wrapper = mount(<Test />);
        wrapper
          .find('.rc-tree-checkbox')
          .first()
          .simulate('click');
  
        expect(renderToJson(wrapper.render())).toMatchSnapshot();
      });
  
      describe('check after data ready', () => {
        const errorSpy = spyError();
  
        it('works', () => {
          const checkedKeys = ['0-0-0'];
          const wrapper = mount(<Tree checkable checkedKeys={checkedKeys} />);
          expect(errorSpy()).toHaveBeenCalledWith("Warning: Tree missing follow keys: '0-0-0'");
  
          wrapper.setProps({
            expandedKeys: ['0-0'],
            children: (
              <TreeNode key="0-0" title="Light">
                <TreeNode key="0-0-0" title="Bamboo" />
              </TreeNode>
            ),
          });
  
          expect(wrapper.render()).toMatchSnapshot();
        });
      });
  
      it('should ignore !checkable node', () => {
        const onCheck = jest.fn();
        const wrapper = mount(
          <Tree checkable defaultExpandAll onCheck={onCheck}>
            <TreeNode key="0-0">
              <TreeNode key="0-0-0" checkable={false} />
              <TreeNode key="0-0-1" />
            </TreeNode>
          </Tree>,
        );
  
        wrapper
          .find('.rc-tree-checkbox')
          .last()
          .simulate('click');
  
        expect(onCheck.mock.calls[0][0].sort()).toEqual(['0-0', '0-0-1']);
      });
  
      describe('strictly', () => {
        it('checks strictly', () => {
          const wrapper = mount(
            <Tree checkable checkStrictly>
              <TreeNode title="parent 1" key="0-0">
                <TreeNode title="leaf 1" key="0-0-0" />
              </TreeNode>
            </Tree>,
          );
          wrapper.find('.rc-tree-switcher').simulate('click');
          wrapper
            .find('.rc-tree-checkbox')
            .first()
            .simulate('click');
          expect(
            wrapper
              .find('.rc-tree-checkbox')
              .first()
              .is(CHECKED_CLASSNAME),
          ).toBe(true);
          expect(
            wrapper
              .find('.rc-tree-checkbox')
              .last()
              .is(CHECKED_CLASSNAME),
          ).toBe(false);
        });
  
        describe('controlled mode', () => {
          class App extends React.Component {
            state = {
              checkedKeys: {
                checked: [],
                halfChecked: [],
              },
            };
  
            handleCheck = checkedKeys => {
              this.setState({ checkedKeys });
            };
  
            render() {
              return (
                <Tree
                  checkable
                  checkStrictly
                  checkedKeys={this.state.checkedKeys}
                  onCheck={this.handleCheck}
                >
                  <TreeNode title="parent 1" key="0-0">
                    <TreeNode title="leaf 1" key="0-0-0" />
                  </TreeNode>
                </Tree>
              );
            }
          }
  
          it('do not check children', () => {
            const wrapper = mount(<App />);
            wrapper.find('.rc-tree-switcher').simulate('click');
            wrapper
              .find('.rc-tree-checkbox')
              .first()
              .simulate('click');
            expect(
              wrapper
                .find('.rc-tree-checkbox')
                .first()
                .is(CHECKED_CLASSNAME),
            ).toBe(true);
            expect(
              wrapper
                .find('.rc-tree-checkbox')
                .last()
                .is(CHECKED_CLASSNAME),
            ).toBe(false);
          });
  
          it('do not uncheck parent', () => {
            const wrapper = mount(<App />);
            wrapper.setState({
              checkedKeys: { checked: ['0-0', '0-0-0'], halfChecked: [] },
            });
            wrapper.find('.rc-tree-switcher').simulate('click');
            wrapper
              .find('.rc-tree-checkbox')
              .last()
              .simulate('click');
            expect(
              wrapper
                .find('.rc-tree-checkbox')
                .first()
                .is(CHECKED_CLASSNAME),
            ).toBe(true);
            expect(
              wrapper
                .find('.rc-tree-checkbox')
                .last()
                .is(CHECKED_CLASSNAME),
            ).toBe(false);
          });
        });
      });
  
      // https://github.com/ant-design/ant-design/issues/6891
      it('should ignore disableCheckbox children items when check parent', () => {
        const wrapper = mount(
          <Tree checkable defaultExpandAll>
            <TreeNode title="parent 1" key="0-0">
              <TreeNode title="node" key="0-0-0" disableCheckbox>
                <TreeNode title="node" key="0-0-0-0" disableCheckbox />
                <TreeNode title="node" key="0-0-0-1" disableCheckbox />
              </TreeNode>
              <TreeNode title="node" key="0-0-1" />
              <TreeNode title="node" key="0-0-2" />
            </TreeNode>
          </Tree>,
        );
  
        const firstNode = () => wrapper.find('.rc-tree-checkbox').first();
        firstNode().simulate('click');
  
        expect(firstNode().hasClass('rc-tree-checkbox-checked')).toBe(true);
        expect(firstNode().hasClass('rc-tree-checkbox-indeterminate')).toBe(false);
        expect(
          wrapper
            .state()
            .checkedKeys.slice()
            .sort(),
        ).toEqual(['0-0-1', '0-0-2', '0-0'].sort());
  
        firstNode().simulate('click');
        expect(firstNode().hasClass('rc-tree-checkbox-checked')).toBe(false);
        expect(firstNode().hasClass('rc-tree-checkbox-indeterminate')).toBe(false);
        expect(wrapper.state().checkedKeys).toEqual([]);
      });
  
      it('should ignore disabled children items when check parent', () => {
        const wrapper = mount(
          <Tree checkable defaultExpandAll>
            <TreeNode title="parent 1" key="0-0">
              <TreeNode title="node" key="0-0-0" disabled>
                <TreeNode title="node" key="0-0-0-0" disabled />
                <TreeNode title="node" key="0-0-0-1" disabled />
              </TreeNode>
              <TreeNode title="node" key="0-0-1" />
              <TreeNode title="node" key="0-0-2" />
            </TreeNode>
          </Tree>,
        );
  
        const firstNode = () =>
          wrapper
            .find(TreeNode)
            .first()
            .find('.rc-tree-checkbox')
            .first();
  
        firstNode().simulate('click');
        expect(firstNode().hasClass('rc-tree-checkbox-checked')).toBe(true);
        expect(firstNode().hasClass('rc-tree-checkbox-indeterminate')).toBe(false);
        expect(
          wrapper
            .state()
            .checkedKeys.slice()
            .sort(),
        ).toEqual(['0-0-1', '0-0-2', '0-0'].sort());
  
        firstNode().simulate('click');
        expect(firstNode().hasClass('rc-tree-checkbox-checked')).toBe(false);
        expect(firstNode().hasClass('rc-tree-checkbox-indeterminate')).toBe(false);
        expect(wrapper.state().checkedKeys).toEqual([]);
      });
  
      // https://github.com/react-component/tree/pull/106#issuecomment-316779889
      it('should render parent check state correctly', () => {
        const wrapper = mount(
          <Tree checkable defaultExpandAll>
            <TreeNode title="parent 1" key="0-0">
              <TreeNode title="node" key="0-0-0">
                <TreeNode title="node" key="0-0-0-0" />
                <TreeNode title="node" key="0-0-0-1" disableCheckbox />
              </TreeNode>
              <TreeNode title="node" key="0-0-1" />
              <TreeNode title="node" key="0-0-2" />
            </TreeNode>
          </Tree>,
        );
        const getParent = () =>
          wrapper
            .find(TreeNode)
            .at(1)
            .find('.rc-tree-checkbox')
            .first();
        const getNode = () =>
          wrapper
            .find(TreeNode)
            .at(2)
            .find('.rc-tree-checkbox')
            .first();
  
        getNode().simulate('click');
        expect(getNode().hasClass('rc-tree-checkbox-checked')).toBe(true);
        expect(getParent().hasClass('rc-tree-checkbox-indeterminate')).toBe(false);
        expect(getParent().hasClass('rc-tree-checkbox-checked')).toBe(true);
        getNode().simulate('click');
        expect(getNode().hasClass('rc-tree-checkbox-checked')).toBe(false);
        expect(getParent().hasClass('rc-tree-checkbox-indeterminate')).toBe(false);
        expect(getParent().hasClass('rc-tree-checkbox-checked')).toBe(false);
      });
    });
  
    describe('select', () => {
      it('selects default selected keys', () => {
        const wrapper = mount(
          <Tree selectable defaultSelectedKeys={['0-0']}>
            <TreeNode title="parent 1" key="0-0">
              <TreeNode title="leaf 1" key="0-0-0" />
            </TreeNode>
          </Tree>,
        );
        expect(wrapper.find('.rc-tree-node-content-wrapper').is(SELECTED_CLASSNAME)).toBe(true);
      });
  
      it('controlled by selectedKeys', () => {
        const wrapper = mount(
          <Tree selectable selectedKeys={[]}>
            <TreeNode title="parent 1" key="0-0">
              <TreeNode title="leaf 1" key="0-0-0" />
            </TreeNode>
          </Tree>,
        );
        expect(wrapper.find('.rc-tree-node-content-wrapper').is(SELECTED_CLASSNAME)).toBe(false);
        wrapper.setProps({ selectedKeys: ['0-0'] });
        expect(wrapper.find('.rc-tree-node-content-wrapper').is(SELECTED_CLASSNAME)).toBe(true);
      });
  
      it('fires select event', () => {
        const handleSelect = jest.fn();
        const wrapper = mount(
          <Tree selectable onSelect={handleSelect}>
            <TreeNode title="parent 1" key="0-0">
              <TreeNode title="leaf 1" key="0-0-0" />
            </TreeNode>
          </Tree>,
        );
        const nodeContent = wrapper.find('.rc-tree-node-content-wrapper');
  
        let node = convertNodePropsToEventData(
          wrapper
            .find(InternalTreeNode)
            .first()
            .props(),
        );
        nodeContent.simulate('click');
        expect(handleSelect).toHaveBeenCalledWith(
          ['0-0'],
          objectMatcher({
            event: 'select',
            node,
            selected: true,
            selectedNodes: [{ title: 'parent 1', key: '0-0' }],
            nativeEvent: {},
          }),
        );
  
        node = convertNodePropsToEventData(
          wrapper
            .find(InternalTreeNode)
            .first()
            .props(),
        );
        nodeContent.simulate('click');
        expect(handleSelect).toHaveBeenCalledWith(
          [],
          objectMatcher({
            event: 'select',
            node,
            selected: false,
            selectedNodes: [],
            nativeEvent: {},
          }),
        );
      });
    });
  
    describe('checkable but not selectable', () => {
      it('fires check event when click on TreeNode', () => {
        const handleCheck = jest.fn();
        const wrapper = mount(
          <Tree checkable selectable={false} onCheck={handleCheck}>
            <TreeNode title="parent 1" key="0-0">
              <TreeNode title="leaf 1" key="0-0-0" />
            </TreeNode>
          </Tree>,
        );
        wrapper.find('.rc-tree-switcher').simulate('click');
  
        const dataNode1 = { title: 'parent 1', key: '0-0' };
        const dataNode2 = { title: 'leaf 1', key: '0-0-0' };
  
        let node = convertNodePropsToEventData(
          wrapper
            .find(InternalTreeNode)
            .first()
            .props(),
        );
        wrapper
          .find('.rc-tree-node-content-wrapper')
          .first()
          .simulate('click');
        expect(handleCheck).toHaveBeenCalledWith(
          ['0-0', '0-0-0'],
          objectMatcher({
            checked: true,
            checkedNodes: [dataNode1, dataNode2],
            checkedNodesPositions: [
              { node: dataNode1, pos: '0-0' },
              { node: dataNode2, pos: '0-0-0' },
            ],
            event: 'check',
            halfCheckedKeys: [],
            node,
            nativeEvent: {},
          }),
        );
  
        node = convertNodePropsToEventData(
          wrapper
            .find(InternalTreeNode)
            .last()
            .props(),
        );
        wrapper
          .find('.rc-tree-node-content-wrapper')
          .last()
          .simulate('click');
        expect(handleCheck).toHaveBeenCalledWith(
          [],
          objectMatcher({
            checked: false,
            checkedNodes: [],
            checkedNodesPositions: [],
            event: 'check',
            halfCheckedKeys: [],
            node,
            nativeEvent: {},
          }),
        );
      });
    });
  
    it('fires rightClick event', () => {
      const handleRightClick = jest.fn();
      const wrapper = mount(
        <Tree onRightClick={handleRightClick}>
          <TreeNode title="parent 1" key="0-0">
            <TreeNode title="leaf 1" key="0-0-0" />
          </TreeNode>
        </Tree>,
      );
      const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).props());
      wrapper.find('.rc-tree-node-content-wrapper').simulate('contextMenu');
      expect(handleRightClick.mock.calls[0][0].node).toEqual(node);
    });
  
    it('fires rightClick should not change selected item', () => {
      const handleRightClick = jest.fn();
      const wrapper = mount(
        <Tree onRightClick={handleRightClick}>
          <TreeNode title="parent 1" key="0-0">
            <TreeNode title="leaf 1" key="0-0-0" />
          </TreeNode>
        </Tree>,
      );
      wrapper.find('.rc-tree-node-content-wrapper').simulate('contextMenu');
      expect(wrapper.state().selectedKeys.length).toBe(0);
    });
  
    it('fires mouseEnter events', () => {
      const handleMouseEnter = jest.fn();
      const wrapper = mount(
        <Tree onMouseEnter={handleMouseEnter}>
          <TreeNode title="parent 1" key="0-0">
            <TreeNode title="leaf 1" key="0-0-0" />
          </TreeNode>
        </Tree>,
      );
      const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).props());
      wrapper.find('.rc-tree-node-content-wrapper').simulate('mouseEnter');
      expect(handleMouseEnter.mock.calls[0][0].node).toEqual(node);
    });
  
    it('fires mouseLeave events', () => {
      const handleMouseLeave = jest.fn();
      const wrapper = mount(
        <Tree onMouseLeave={handleMouseLeave}>
          <TreeNode title="parent 1" key="0-0">
            <TreeNode title="leaf 1" key="0-0-0" />
          </TreeNode>
        </Tree>,
      );
      const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).props());
      wrapper.find('.rc-tree-node-content-wrapper').simulate('mouseLeave');
      expect(handleMouseLeave.mock.calls[0][0].node).toEqual(node);
    });
  
    it('filters nodes', () => {
      function filterTreeNode(treeNode) {
        return treeNode.title.indexOf('parent') > -1;
      }
      const wrapper = mount(
        <Tree filterTreeNode={filterTreeNode}>
          <TreeNode title="parent 1" key="0-0">
            <TreeNode title="leaf 1" key="0-0-0">
              <TreeNode title="meck" key="0-0-0-0" />
              <TreeNode title="leaf" key="0-0-0-1" />
            </TreeNode>
            <TreeNode title="leaf 2" key="0-0-1" />
          </TreeNode>
        </Tree>,
      );
  
      expect(
        wrapper
          .find(InternalTreeNode)
          .find('div')
          .is('.filter-node'),
      ).toBe(true);
    });
  
    it('loads nodes asynchronously', () => {
      const then = jest.fn(() => Promise.resolve());
      const loadData = jest.fn(() => ({ then }));
      const wrapper = mount(
        <Tree loadData={loadData}>
          <TreeNode title="parent 1" key="0-0" />
        </Tree>,
      );
      const node = convertNodePropsToEventData(wrapper.find(InternalTreeNode).props());
      wrapper.find('.rc-tree-switcher').simulate('click');
      expect(loadData).toHaveBeenCalledWith(node);
      expect(then).toHaveBeenCalled();
    });
  
    describe('drag and drop', () => {
      function createTree(props) {
        return (
          <Tree draggable defaultExpandAll {...props}>
            <TreeNode title="parent 1" key="0-0">
              <TreeNode className="dragTarget" title="leaf" key="0-0-0-0" />
              <TreeNode className="dropTarget" title="leaf" key="0-0-0-1" />
            </TreeNode>
          </Tree>
        );
      }
  
      it('fires dragStart event', () => {
        const onDragStart = jest.fn();
        const wrapper = mount(createTree({ onDragStart }));
        const treeNode = wrapper.find('.dragTarget > .rc-tree-node-content-wrapper');
        const node = convertNodePropsToEventData(
          wrapper
            .find(InternalTreeNode)
            .at(1)
            .props(),
        );
        treeNode.simulate('dragStart');
        const event = onDragStart.mock.calls[0][0];
        expect(event.node).toEqual(node);
      });
  
      it('fires dragEnter event', async () => {
        const onDragEnter = jest.fn();
        const wrapper = mount(createTree({ onDragEnter }));
  
        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
  
        // Not trigger self
        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragEnter');
        await delay(900);
        expect(onDragEnter).not.toHaveBeenCalled();
  
        wrapper
          .find('.dropTarget')
          .at(0)
          .simulate('dragEnter');
        expect(onDragEnter).toHaveBeenCalled();
  
        await delay(900);
        wrapper.update();
        const node = convertNodePropsToEventData(
          wrapper
            .find(InternalTreeNode)
            .at(2)
            .props(),
        );
        const event = onDragEnter.mock.calls[0][0];
        expect(event.node.key).toEqual(node.key);
        expect(event.expandedKeys).toEqual(['0-0', '0-0-0-1']);
        expect(onDragEnter).toHaveBeenCalledTimes(1);
      });
  
      it('fires dragOver event', () => {
        const onDragOver = jest.fn();
        const wrapper = mount(createTree({ onDragOver }));
        const node = convertNodePropsToEventData(
          wrapper
            .find(InternalTreeNode)
            .at(2)
            .props(),
        );
        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
        wrapper
          .find('.dropTarget')
          .at(0)
          .simulate('dragOver');
        const event = onDragOver.mock.calls[0][0];
        expect(event.node).toEqual(node);
      });
  
      it('fires dragLeave event', () => {
        const onDragLeave = jest.fn();
        const wrapper = mount(createTree({ onDragLeave }));
        const node = convertNodePropsToEventData(
          wrapper
            .find(InternalTreeNode)
            .at(2)
            .props(),
        );
        wrapper
          .find('.dropTarget')
          .at(0)
          .simulate('dragLeave');
        const event = onDragLeave.mock.calls[0][0];
        expect(event.node).toEqual(node);
      });
  
      it('fires drop event', () => {
        const onDrop = jest.fn();
        const wrapper = mount(createTree({ onDrop }));
        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter');
        wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver');
        const dropNode = convertNodePropsToEventData(
          wrapper
            .find(InternalTreeNode)
            .at(2)
            .props(),
        );
        const dragNode = convertNodePropsToEventData(
          wrapper
            .find(InternalTreeNode)
            .at(1)
            .props(),
        );
        wrapper
          .find('.dropTarget')
          .at(0)
          .simulate('drop');
        const event = onDrop.mock.calls[0][0];
        expect(event.node).toEqual(dropNode);
        expect(event.dragNode).toEqual(dragNode);
        expect(event.dragNodesKeys).toEqual(['0-0-0-0']);
      });
  
      it('fires dropEnd event', () => {
        const onDragEnd = jest.fn();
        const wrapper = mount(createTree({ onDragEnd }));
        const node = convertNodePropsToEventData(
          wrapper
            .find(InternalTreeNode)
            .at(1)
            .props(),
        );
        wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragEnd');
        const event = onDragEnd.mock.calls[0][0];
        expect(event.node).toEqual(node);
      });
  
      it('do not throw error when drag into another non-drag-able tree', () => {
        const wrapper = mount(
          <div>
            {createTree()}
            {createTree({ draggable: false })}
          </div>,
        );
  
        const dragTree = wrapper.find(Tree).at(0);
        const normalTree = wrapper.find(Tree).at(1);
  
        dragTree.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
        normalTree
          .find('.dropTarget')
          .at(0)
          .simulate('dragEnter');
        normalTree
          .find('.dropTarget')
          .at(0)
          .simulate('dragOver');
        normalTree
          .find('.dropTarget')
          .at(0)
          .simulate('drop');
      });
  
      describe('full steps', () => {
        function dropTarget(targetSelector) {
          return new Promise(resolve => {
            const wrapper = mount(
              <Tree draggable={() => true} defaultExpandAll onExpand={() => {}}>
                <TreeNode key="0-0" className="dragTarget">
                  <TreeNode key="0-0-0" className="dragTargetChild" />
                </TreeNode>
                <TreeNode key="0-1">
                  <TreeNode key="0-1-0" />
                </TreeNode>
                <TreeNode key="0-2" className="dropTarget">
                  <TreeNode key="0-2-0" />
                </TreeNode>
              </Tree>,
            );
  
            wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart');
  
            // 1. Move into target (first in the middle of the node)
            wrapper.find(targetSelector).simulate('dragEnter', { clientY: 10 });
            setTimeout(() => {
              wrapper.find(targetSelector).simulate('dragOver', { clientY: 999 });
  
              // 2. Move out of target
              wrapper.find(targetSelector).simulate('dragLeave');
  
              // 3. Move in again
              wrapper.find(targetSelector).simulate('dragEnter', { clientY: 0 });
              setTimeout(() => {
                wrapper.find(targetSelector).simulate('dragOver', { clientY: 999 });
                // 4. Drop
                wrapper.find(targetSelector).simulate('drop');
                wrapper.find('div.dragTarget').simulate('dragEnd');
  
                resolve();
              }, 1000);
            }, 10);
          });
        }
        let domSpy;
        beforeEach(() => {
          domSpy = spyElementPrototypes(HTMLElement, {
            offsetWidth: {
              get() {
                return 24;
              },
            },
            getBoundingClientRect: jest.fn(() => ({
              width: 100,
              height: 20,
              top: 0,
              left: 0,
              bottom: 20,
              right: 100,
            })),
          });
          // Object.defineProperties(window.HTMLElement.prototype, {
          //   // mock indent as 24
          //   // no need for clearing it, since jest make each file a independent env
          //   offsetWidth: {
          //     get() {
          //       return 24;
          //     },
          //   },
          // });
        });
  
        afterEach(() => {
          domSpy.mockRestore();
        });
  
        it('self', () => dropTarget('div.dragTarget'));
  
        it('target', () => dropTarget('div.dropTarget'));
      });
  
      describe('new drop logic', () => {
        let domSpy;
        beforeEach(() => {
          domSpy = spyElementPrototypes(HTMLElement, {
            getBoundingClientRect: () => ({
              width: 100,
              height: 20,
              top: 0,
              left: 0,
              bottom: 20,
              right: 100,
            }),
            offsetWidth: {
              get() {
                return 24;
              },
            },
          });
        });
        afterEach(() => {
          domSpy.mockRestore();
        });
        ['ltr', 'rtl'].forEach(dir => {
          const base = dir === 'ltr' ? 1 : -1;
          it('not allow cross level dnd on expanded nodes', () => {
            const onDrop = jest.fn();
            const wrapper = mount(
              <Tree draggable onDrop={onDrop} direction={dir} expandedKeys={['0-0', '0-1', '0-1-0']}>
                <TreeNode key="0-0">
                  <TreeNode key="0-0-0" className="dropTarget1">
                    <TreeNode key="0-0-0-0" />
                  </TreeNode>
                </TreeNode>
                <TreeNode key="0-1">
                  <TreeNode key="0-1-0" className="dropTarget2">
                    <TreeNode key="0-1-0-0" />
                  </TreeNode>
                </TreeNode>
                <TreeNode key="0-2" className="dragTarget" />
              </Tree>,
            );
            wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
              clientX: base * 500,
              clientY: 500,
            });
            wrapper.find('.dropTarget1 > .rc-tree-node-content-wrapper').simulate('dragEnter', {
              clientX: base * 400,
              clientY: 600,
            });
            wrapper.find('.dropTarget1 > .rc-tree-node-content-wrapper').simulate('dragOver', {
              clientX: base * 400,
              clientY: 600,
            });
            wrapper.find('.dropTarget1 > .rc-tree-node-content-wrapper').simulate('drop');
            // insert after 0-0, since 0-0-0 is not expanded
            expect(onDrop.mock.calls[0][0].node.key).toEqual('0-0');
            expect(onDrop.mock.calls[0][0].dropPosition).toEqual(1);
            wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
              clientX: base * 500,
              clientY: 500,
            });
            wrapper.find('.dropTarget2 > .rc-tree-node-content-wrapper').simulate('dragEnter', {
              clientX: base * 400,
              clientY: 600,
            });
            wrapper.find('.dropTarget2 > .rc-tree-node-content-wrapper').simulate('dragOver', {
              clientX: base * 400,
              clientY: 600,
            });
            wrapper.find('.dropTarget2 > .rc-tree-node-content-wrapper').simulate('drop');
            // insert into 0-1-0, since it is expanded, do not allow cross level dnd
            expect(onDrop.mock.calls[1][0].node.key).toEqual('0-1-0');
            expect(onDrop.mock.calls[1][0].dropPosition).toEqual(0);
          });
          it('allowDrop all nodes', () => {
            const onDrop = jest.fn();
            const wrapper = mount(
              <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
                <TreeNode key="0-0" className="dragTargetParent">
                  <TreeNode key="0-0-0" className="dragTarget">
                    <TreeNode key="0-0-0-0" className="dragTargetChild" />
                  </TreeNode>
                </TreeNode>
                <TreeNode key="0-1">
                  <TreeNode key="0-1-0">
                    <TreeNode key="0-1-0-0" className="dropTarget" />
                  </TreeNode>
                </TreeNode>
                <TreeNode key="0-2" />
              </Tree>,
            );
            wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
              clientX: base * 500,
              clientY: 500,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
              clientX: base * 400,
              clientY: 600,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
              clientX: base * 400,
              clientY: 600,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
            expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1-0-0');
            // (in ltr) drag from right to left, should be insert after, so drop position is 1
            expect(onDrop.mock.calls[0][0].dropPosition).toEqual(1);
            wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
              clientX: base * 500,
              clientY: 500,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
              clientX: base * 500,
              clientY: 600,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
              clientX: base * 500,
              clientY: 600,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
            expect(onDrop.mock.calls[1][0].node.key).toEqual('0-1-0-0');
            expect(onDrop.mock.calls[1][0].dropPosition).toEqual(1);
            wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
              clientX: base * 500,
              clientY: 500,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
              clientX: base * 550,
              clientY: 600,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
              clientX: base * 550,
              clientY: 600,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
            expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
            expect(onDrop.mock.calls[2][0].dropPosition).toEqual(0);
            wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
              clientX: base * 500,
              clientY: 500,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
              clientX: base * 600,
              clientY: 600,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
              clientX: base * 600,
              clientY: 600,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
            expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
            expect(onDrop.mock.calls[2][0].dropPosition).toEqual(0);
          });
          it('allowDrop no node', () => {
            const onDrop = jest.fn();
            const wrapper = mount(
              <Tree
                draggable
                defaultExpandAll
                onDrop={onDrop}
                allowDrop={() => false}
                direction={dir}
              >
                <TreeNode key="0-0" className="dragTargetParent">
                  <TreeNode key="0-0-0" className="dragTarget">
                    <TreeNode key="0-0-0-0" className="dragTargetChild" />
                  </TreeNode>
                </TreeNode>
                <TreeNode key="0-1">
                  <TreeNode key="0-1-0" className="dropTargetParent">
                    <TreeNode key="0-1-0-0" className="dropTarget" />
                  </TreeNode>
                </TreeNode>
                <TreeNode key="0-2" />
              </Tree>,
            );
            wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
              clientX: base * 500,
              clientY: 500,
            });
            wrapper.find('.dropTargetParent > .rc-tree-node-content-wrapper').simulate('dragEnter', {
              clientX: base * 400,
              clientY: 600,
            });
            wrapper.find('.dropTargetParent > .rc-tree-node-content-wrapper').simulate('dragOver', {
              clientX: base * 400,
              clientY: 600,
            });
            wrapper.find('.dropTargetParent > .rc-tree-node-content-wrapper').simulate('drop');
            // not allow any dropPosition except 0 on expanded node
            expect(onDrop).not.toHaveBeenCalled();
            wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
              clientX: base * 500,
              clientY: 500,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
              clientX: base * 400,
              clientY: 600,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
              clientX: base * 400,
              clientY: 600,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
            expect(onDrop).not.toHaveBeenCalled();
            wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
              clientX: base * 500,
              clientY: 500,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
              clientX: base * 500,
              clientY: 600,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
              clientX: base * 500,
              clientY: 600,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
            expect(onDrop).not.toHaveBeenCalled();
            wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
              clientX: base * 500,
              clientY: 500,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
              clientX: base * 550,
              clientY: 600,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
              clientX: base * 550,
              clientY: 600,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
            expect(onDrop).not.toHaveBeenCalled();
            wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
              clientX: base * 500,
              clientY: 500,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
              clientX: base * 600,
              clientY: 600,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
              clientX: base * 600,
              clientY: 600,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
            expect(onDrop).not.toHaveBeenCalled();
          });
          it('drop to top half of first node', () => {
            const onDrop = jest.fn();
            const wrapper = mount(
              <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
                <TreeNode key="0-1" className="dropTarget">
                  <TreeNode key="0-1-0">
                    <TreeNode key="0-1-0-0" />
                  </TreeNode>
                </TreeNode>
                <TreeNode key="0-0" className="dragTargetParent">
                  <TreeNode key="0-0-0" className="dragTarget">
                    <TreeNode key="0-0-0-0" className="dragTargetChild" />
                  </TreeNode>
                </TreeNode>
              </Tree>,
            );
            wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
              clientX: base * 500,
              clientY: 500,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
              clientX: base * 400,
              clientY: 0,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
              clientX: base * 400,
              clientY: -1000,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
            expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1');
            expect(onDrop.mock.calls[0][0].dropPosition).toEqual(-1);
          });
          it('can drop on its direct parent', () => {
            const onDrop = jest.fn();
            const wrapper = mount(
              <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
                <TreeNode key="0-1" className="dropTarget">
                  <TreeNode key="0-1-0">
                    <TreeNode key="0-1-0-0" />
                  </TreeNode>
                </TreeNode>
                <TreeNode key="0-0" className="dragTargetParent">
                  <TreeNode key="0-0-0" className="dragTarget">
                    <TreeNode key="0-0-0-0" className="dragTargetChild" />
                  </TreeNode>
                </TreeNode>
              </Tree>,
            );
            wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
              clientX: base * 500,
              clientY: 500,
            });
            wrapper.find('.dragTargetParent > .rc-tree-node-content-wrapper').simulate('dragEnter', {
              clientX: base * 500,
              clientY: 500,
            });
            wrapper.find('.dragTargetParent > .rc-tree-node-content-wrapper').simulate('dragOver', {
              clientX: base * 500,
              clientY: 500,
            });
            wrapper.find('.dragTargetParent > .rc-tree-node-content-wrapper').simulate('drop');
            expect(onDrop).toHaveBeenCalled();
          });
          it('cover window dragend & componentWillUnmount', () => {
            const wrapper = mount(
              <Tree draggable defaultExpandAll direction={dir}>
                <TreeNode key="0-1" className="dropTarget">
                  <TreeNode key="0-1-0">
                    <TreeNode key="0-1-0-0" />
                  </TreeNode>
                </TreeNode>
                <TreeNode key="0-0" className="dragTargetParent">
                  <TreeNode key="0-0-0" className="dragTarget">
                    <TreeNode key="0-0-0-0" className="dragTargetChild" />
                  </TreeNode>
                </TreeNode>
              </Tree>,
            );
            wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
              clientX: base * 500,
              clientY: 500,
            });
            window.dispatchEvent(new Event('dragend'));
            expect(wrapper.instance().state.dragging).toEqual(false);
            wrapper.unmount();
          });
          it('dragover first half of non-first child', () => {
            const onDrop = jest.fn();
            const wrapper = mount(
              <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
                <TreeNode key="0-0" className="dragTargetParent">
                  <TreeNode key="0-0-0" className="dragTarget">
                    <TreeNode key="0-0-0-0" className="dragTargetChild" />
                  </TreeNode>
                  <TreeNode key="0-0-1" />
                  <TreeNode key="0-0-2" className="dropTarget" />
                </TreeNode>
              </Tree>,
            );
            wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
              clientX: base * 500,
              clientY: 500,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
              clientX: base * 500,
              clientY: 1,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
              clientX: base * 500,
              clientY: 1,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
            expect(onDrop.mock.calls[0][0].node.key).toEqual('0-0-1');
            expect(onDrop.mock.calls[0][0].dropPosition).toEqual(2);
          });
          it('dragover self', () => {
            const onDrop = jest.fn();
            const wrapper = mount(
              <Tree draggable defaultExpandAll onDrop={onDrop} direction={dir}>
                <TreeNode key="0-1" className="dropTarget">
                  <TreeNode key="0-1-0">
                    <TreeNode key="0-1-0-0" />
                  </TreeNode>
                </TreeNode>
                <TreeNode key="0-0" className="dragTargetParent">
                  <TreeNode key="0-0-0" className="dragTarget">
                    <TreeNode key="0-0-0-0" className="dragTargetChild" />
                  </TreeNode>
                </TreeNode>
              </Tree>,
            );
            wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
              clientX: base * 500,
              clientY: 500,
            });
            wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
              clientX: base * 400,
              clientY: 500,
            });
            wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
              clientX: base * 600,
              clientY: 500,
            });
            wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
              clientX: base * 600,
              clientY: 500,
            });
            wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
              clientX: base * 600,
              clientY: 500,
            });
            wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('drop');
            expect(onDrop).not.toHaveBeenCalled();
          });
          it('not allowDrop on node which has children', () => {
            const onDrop = jest.fn();
            const allowDrop = ({ dropNode, dropPosition }) => {
              if (!dropNode.children) {
                if (dropPosition === 0) return false;
              }
              return true;
            };
            const wrapper = mount(
              <Tree draggable defaultExpandAll allowDrop={allowDrop} onDrop={onDrop} direction={dir}>
                <TreeNode key="0-0" className="dragTarget">
                  <TreeNode key="0-0-0" className="dragTargetChild" />
                </TreeNode>
                <TreeNode key="0-1">
                  <TreeNode key="0-1-0">
                    <TreeNode key="0-1-0-0" className="dropTarget" />
                  </TreeNode>
                </TreeNode>
                <TreeNode key="0-2" />
              </Tree>,
            );
            wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
              clientX: base * 500,
              clientY: 500,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
              clientX: base * 400,
              clientY: 600,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
              clientX: base * 400,
              clientY: 600,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
            expect(onDrop.mock.calls[0][0].node.key).toEqual('0-1-0-0');
            // (in ltr) drag from right to left, should be insert after, so drop position is 1
            expect(onDrop.mock.calls[0][0].dropPosition).toEqual(1);
            wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
              clientX: base * 500,
              clientY: 500,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
              clientX: base * 500,
              clientY: 600,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
              clientX: base * 500,
              clientY: 600,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
            expect(onDrop.mock.calls[1][0].node.key).toEqual('0-1-0-0');
            expect(onDrop.mock.calls[1][0].dropPosition).toEqual(1);
            wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
              clientX: base * 500,
              clientY: 500,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
              clientX: base * 550,
              clientY: 600,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
              clientX: base * 550,
              clientY: 600,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
            expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
            expect(onDrop.mock.calls[2][0].dropPosition).toEqual(1);
            wrapper.find('.dragTarget > .rc-tree-node-content-wrapper').simulate('dragStart', {
              clientX: base * 500,
              clientY: 500,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragEnter', {
              clientX: base * 600,
              clientY: 600,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('dragOver', {
              clientX: base * 600,
              clientY: 600,
            });
            wrapper.find('.dropTarget > .rc-tree-node-content-wrapper').simulate('drop');
            expect(onDrop.mock.calls[2][0].node.key).toEqual('0-1-0-0');
            expect(onDrop.mock.calls[2][0].dropPosition).toEqual(1);
          });
        });
      });
    });
  
    it('renders without errors', () => {
      expect(() => {
        mount(
          <Tree>
            {[0, 1].map(i => (
              <TreeNode title={i} key={i}>
                {[2, 3].map(j => (
                  <TreeNode title={j} key={`${i}_${j}`} />
                ))}
                <TreeNode title="4" key={`${i}_4`} />
              </TreeNode>
            ))}
          </Tree>,
        );
      }).not.toThrow();
    });
  
    it('renders opaque children correctly', () => {
      const wrapper = render(
        <Tree>
          <TreeNode title="0" key="0">
            {[1, 2].map(i => (
              <TreeNode title={i} key={i} />
            ))}
            <TreeNode title="3" key="3" />
          </TreeNode>
        </Tree>,
      );
  
      expect(renderToJson(wrapper)).toMatchSnapshot();
    });
  
    describe('ignore illegal node as Tree children', () => {
      const errorSpy = spyError();
  
      it('Direct TreeNode', () => {
        const wrapper = mount(
          <Tree defaultExpandAll>
            <TreeNode key="00" title="00" />
            <span>Hide Me</span>
            <TreeNode key="02" title="02" />
          </Tree>,
        );
        expect(wrapper.render()).toMatchSnapshot();
        expect(errorSpy()).toHaveBeenCalledWith(
          'Warning: Tree/TreeNode can only accept TreeNode as children.',
        );
      });
  
      it('Sub TreeNode', () => {
        resetWarned();
        const wrapper = mount(
          <Tree defaultExpandAll>
            <TreeNode key="00" title="00" />
            <TreeNode key="01" title="01">
              <TreeNode key="010" title="010" />
              <span>I AM INVISIBLE</span>
              <TreeNode key="012" title="012" />
            </TreeNode>
          </Tree>,
        );
        expect(wrapper.render()).toMatchSnapshot();
        expect(errorSpy()).toHaveBeenCalledWith(
          'Warning: Tree/TreeNode can only accept TreeNode as children.',
        );
      });
    });
  
    it('Number key', () => {
      const onCheck = jest.fn();
  
      const Demo = () => {
        const [checkedKeys, setCheckedKeys] = React.useState([]);
  
        return (
          <Tree
            checkable
            defaultExpandAll
            checkedKeys={checkedKeys}
            onCheck={keys => {
              setCheckedKeys(keys);
              onCheck(keys);
            }}
            treeData={[{ key: 11, title: 11, children: [{ key: 22, title: 22 }] }]}
          />
        );
      };
  
      const wrapper = mount(<Demo />);
      wrapper.update();
  
      wrapper
        .find('.rc-tree-checkbox')
        .first()
        .simulate('click');
      expect(onCheck).toHaveBeenCalledWith([11, 22]);
  
      onCheck.mockReset();
      wrapper
        .find('.rc-tree-checkbox')
        .last()
        .simulate('click');
      expect(onCheck).toHaveBeenCalledWith([]);
    });
  
    describe('scrollTo should work', () => {
      let domSpy;
      let called = false;
  
      beforeAll(() => {
        domSpy = spyElementPrototypes(HTMLDivElement, {
          scrollTop: {
            get: () => 233,
            set: () => {
              called = true;
            },
          },
          clientHeight: {
            get: () => 100,
          },
        });
      });
  
      afterAll(() => {
        domSpy.mockRestore();
      });
  
      it('work', () => {
        jest.useFakeTimers();
        const wrapper = mount(<Tree />);
  
        wrapper.instance().scrollTo({ key: 'light', align: 'top' });
        jest.runAllTimers();
  
        expect(called).toBeTruthy();
        jest.useRealTimers();
      });
    });
  
    it('not crash if expandedKeys is null', () => {
      mount(
        <Tree expandedKeys={null}>
          <TreeNode key="test" title="alive" />
        </Tree>,
      );
    });
  
    it('not crash if expandedKeys change to undefined', () => {
      const wrapper = mount(
        <Tree expandedKeys={['light']}>
          <TreeNode key="light" title="light">
            <TreeNode key="bamboo" title="bamboo" />
          </TreeNode>
        </Tree>,
      );
      const { data } = wrapper.find(VirtualList).props();
      expect(data).toHaveLength(2);
  
      wrapper.setProps({ expandedKeys: undefined });
  
      // Click should not crash
      wrapper
        .find('.rc-tree-switcher')
        .first()
        .simulate('click');
    });
  
    it('controlled should block expanded', () => {
      const wrapper = mount(
        <Tree
          expandedKeys={['1']}
          treeData={[{ key: '1', title: 1, children: [{ key: '2', title: 2 }] }]}
        />,
      );
  
      wrapper
        .find('.rc-tree-switcher')
        .first()
        .simulate('click');
      expect(wrapper.find('List').props().data).toHaveLength(2);
    });
  
    it('support virtual', () => {
      const data = [];
      for (let i = 0; i < 99; i += 1) {
        data.push({
          key: i,
          title: i,
        });
      }
  
      const wrapper = mount(<Tree itemHeight={10} height={100} treeData={data} virtual={false} />);
  
      expect(wrapper.find('List').props().virtual).toBe(false);
    });
  
    // https://github.com/ant-design/ant-design/issues/28349
    it('should not trigger expend when loading data', () => {
      const then = jest.fn(() => Promise.resolve());
      const loadData = jest.fn(() => ({ then }));
      const onExpand = jest.fn();
      const wrapper = mount(
        <Tree loadData={loadData} onExpand={onExpand}>
          <TreeNode title="parent 1" key="0-0" />
        </Tree>,
      );
  
      // trigger click to expand node
      wrapper.find('.rc-tree-switcher').simulate('click');
      expect(
        wrapper
          .find('.rc-tree-switcher')
          .first()
          .is(OPEN_CLASSNAME),
      ).toBe(true);
      expect(onExpand).toBeCalled();
  
      // click again
      onExpand.mockReset();
      wrapper.find('.rc-tree-switcher').simulate('click');
      expect(
        wrapper
          .find('.rc-tree-switcher')
          .first()
          .is(OPEN_CLASSNAME),
      ).toBe(true);
      expect(onExpand).not.toHaveBeenCalled();
    });
  });
  