import { mount } from '@vue/test-utils';
import { asyncExpect } from '@/tests/utils';
import Table from '..';

describe('Table.rowSelection', () => {
  const columns = [
    {
      title: 'Name',
      dataIndex: 'name',
    },
  ];

  const data = [
    { key: 0, name: 'Jack' },
    { key: 1, name: 'Lucy' },
    { key: 2, name: 'Tom' },
    { key: 3, name: 'Jerry' },
  ];
  function getTableOptions(props = {}, listeners = {}) {
    return {
      propsData: {
        columns,
        dataSource: data,
        rowSelection: {},
        ...props,
      },
      listeners: {
        ...listeners,
      },
      sync: false,
      attachedToDocument: true,
    };
  }
  function renderedNames(wrapper) {
    return wrapper.findAll({ name: 'TableRow' }).wrappers.map(row => {
      return row.props().record.name;
    });
  }

  function getStore(wrapper) {
    return wrapper.vm._vnode.componentInstance.store;
  }

  it('select by checkbox', async () => {
    const wrapper = mount(Table, getTableOptions());
    const checkboxes = wrapper.findAll('input');
    const checkboxAll = checkboxes.at(0);
    checkboxAll.element.checked = true;
    checkboxAll.trigger('change');
    await asyncExpect(() => {
      expect(getStore(wrapper).getState()).toEqual({
        selectedRowKeys: [0, 1, 2, 3],
        selectionDirty: true,
      });
    });
    checkboxes.at(1).element.checked = false;
    checkboxes.at(1).trigger('change');
    await asyncExpect(() => {
      expect(getStore(wrapper).getState()).toEqual({
        selectedRowKeys: [1, 2, 3],
        selectionDirty: true,
      });
    });
    checkboxes.at(1).element.checked = true;
    checkboxes.at(1).trigger('change');
    await asyncExpect(() => {
      expect(getStore(wrapper).getState()).toEqual({
        selectedRowKeys: [1, 2, 3, 0],
        selectionDirty: true,
      });
    });
  });

  it('select by radio', async () => {
    const wrapper = mount(Table, getTableOptions({ rowSelection: { type: 'radio' } }));
    const radios = wrapper.findAll('input');

    expect(radios.length).toBe(4);
    radios.at(0).element.checked = true;
    radios.at(0).trigger('change');
    await asyncExpect(() => {
      expect(getStore(wrapper).getState()).toEqual({
        selectedRowKeys: [0],
        selectionDirty: true,
      });
    });
    radios.at(radios.length - 1).element.checked = true;
    radios.at(radios.length - 1).trigger('change');
    await asyncExpect(() => {
      expect(getStore(wrapper).getState()).toEqual({
        selectedRowKeys: [3],
        selectionDirty: true,
      });
    });
  });

  it('pass getCheckboxProps to checkbox', () => {
    const rowSelection = {
      getCheckboxProps: record => ({
        props: {
          disabled: record.name === 'Lucy',
          name: record.name,
        },
      }),
    };

    const wrapper = mount(Table, getTableOptions({ rowSelection }));
    const checkboxes = wrapper.findAll('input').wrappers;
    expect(checkboxes[1].vnode.data.attrs.disabled).toBe(false);
    expect(checkboxes[1].vnode.data.attrs.name).toEqual(data[0].name);
    expect(checkboxes[2].vnode.data.attrs.disabled).toBe(true);
    expect(checkboxes[2].vnode.data.attrs.name).toEqual(data[1].name);
  });

  it('works with pagination', async () => {
    const wrapper = mount(Table, getTableOptions({ pagination: { pageSize: 2 } }));

    const checkboxAll = wrapper.find({ name: 'SelectionCheckboxAll' });
    const pagers = wrapper.findAll({ name: 'Pager' });
    checkboxAll.find('input').element.checked = true;
    checkboxAll.find('input').trigger('change');
    await asyncExpect(() => {
      expect(checkboxAll.vm.$data).toEqual({ checked: true, indeterminate: false });
    });
    pagers.at(1).trigger('click');
    await asyncExpect(() => {
      expect(checkboxAll.vm.$data).toEqual({ checked: false, indeterminate: false });
    });
    pagers.at(0).trigger('click');
    await asyncExpect(() => {
      expect(checkboxAll.vm.$data).toEqual({ checked: true, indeterminate: false });
    });
  });

  // https://github.com/ant-design/ant-design/issues/4020
  it('handles defaultChecked', async () => {
    const rowSelection = {
      getCheckboxProps: record => {
        return {
          props: {
            defaultChecked: record.key === 0,
          },
        };
      },
    };

    const wrapper = mount(Table, getTableOptions({ rowSelection }));

    await asyncExpect(() => {
      const checkboxs = wrapper.findAll('input');
      expect(checkboxs.at(1).vnode.data.domProps.checked).toBe(true);
      expect(checkboxs.at(2).vnode.data.domProps.checked).toBe(false);
      checkboxs.at(2).element.checked = true;
      checkboxs.at(2).trigger('change');
    }, 0);

    await asyncExpect(() => {
      const checkboxs = wrapper.findAll('input');
      expect(checkboxs.at(1).vnode.data.domProps.checked).toBe(true);
      expect(checkboxs.at(2).vnode.data.domProps.checked).toBe(true);
    }, 1000);
  });

  it('can be controlled', async () => {
    const wrapper = mount(Table, getTableOptions({ rowSelection: { selectedRowKeys: [0] } }));

    expect(getStore(wrapper).getState()).toEqual({
      selectedRowKeys: [0],
      selectionDirty: false,
    });

    wrapper.setProps({ rowSelection: { selectedRowKeys: [1] } });
    await asyncExpect(() => {
      expect(getStore(wrapper).getState()).toEqual({
        selectedRowKeys: [1],
        selectionDirty: false,
      });
    });
  });

  it('fires change & select events', async () => {
    const handleChange = jest.fn();
    const handleSelect = jest.fn();
    const rowSelection = {
      onChange: handleChange,
      onSelect: handleSelect,
    };
    const wrapper = mount(Table, getTableOptions({ rowSelection }));
    const checkboxs = wrapper.findAll('input');
    checkboxs.at(checkboxs.length - 1).element.checked = true;
    checkboxs.at(checkboxs.length - 1).trigger('change');
    await asyncExpect(() => {
      expect(handleChange).toBeCalledWith([3], [{ key: 3, name: 'Jerry' }]);
      expect(handleSelect.mock.calls.length).toBe(1);
      expect(handleSelect.mock.calls[0][0]).toEqual({ key: 3, name: 'Jerry' });
      expect(handleSelect.mock.calls[0][1]).toEqual(true);
      expect(handleSelect.mock.calls[0][2]).toEqual([{ key: 3, name: 'Jerry' }]);
    });
  });

  it('fires selectAll event', async () => {
    const handleSelectAll = jest.fn();
    const rowSelection = {
      onSelectAll: handleSelectAll,
    };
    const wrapper = mount(Table, getTableOptions({ rowSelection }));
    const checkboxs = wrapper.findAll('input');
    checkboxs.at(0).element.checked = true;
    checkboxs.at(0).trigger('change');
    await asyncExpect(() => {
      expect(handleSelectAll).toBeCalledWith(true, data, data);
    });
    checkboxs.at(0).element.checked = false;
    checkboxs.at(0).trigger('change');
    await asyncExpect(() => {
      expect(handleSelectAll).toBeCalledWith(false, [], data);
    });
  });

  it('render with default selection correctly', async () => {
    const rowSelection = {
      selections: true,
    };
    const wrapper = mount(Table, getTableOptions({ rowSelection }));
    const dropdownWrapper = mount(
      {
        render() {
          return wrapper.find({ name: 'Trigger' }).vm.getComponent();
        },
      },
      { sync: false },
    );
    await asyncExpect(() => {
      expect(dropdownWrapper.html()).toMatchSnapshot();
    });

    await asyncExpect(() => {});
  });

  it('click select all selection', () => {
    const handleSelectAll = jest.fn();
    const rowSelection = {
      onSelectAll: handleSelectAll,
      selections: true,
    };
    const wrapper = mount(Table, getTableOptions({ rowSelection }));

    const dropdownWrapper = mount(
      {
        render() {
          return wrapper.find({ name: 'Trigger' }).vm.getComponent();
        },
      },
      { sync: false },
    );
    dropdownWrapper
      .findAll('.ant-dropdown-menu-item > div')
      .at(0)
      .trigger('click');

    expect(handleSelectAll).toBeCalledWith(true, data, data);
  });

  it('fires selectInvert event', () => {
    const handleSelectInvert = jest.fn();
    const rowSelection = {
      onSelectInvert: handleSelectInvert,
      selections: true,
    };
    const wrapper = mount(Table, getTableOptions({ rowSelection }));
    const checkboxes = wrapper.findAll('input');
    checkboxes.at(1).element.checked = true;
    checkboxes.at(1).trigger('change');
    const dropdownWrapper = mount(
      {
        render() {
          return wrapper.find({ name: 'Trigger' }).vm.getComponent();
        },
      },
      { sync: false },
    );
    const div = dropdownWrapper.findAll('.ant-dropdown-menu-item > div');
    div.at(div.length - 1).trigger('click');

    expect(handleSelectInvert).toBeCalledWith([1, 2, 3]);
  });

  it('fires selection event', () => {
    const handleSelectOdd = jest.fn();
    const handleSelectEven = jest.fn();
    const rowSelection = {
      selections: [
        {
          key: 'odd',
          text: '奇数项',
          onSelect: handleSelectOdd,
        },
        {
          key: 'even',
          text: '偶数项',
          onSelect: handleSelectEven,
        },
      ],
    };
    const wrapper = mount(Table, getTableOptions({ rowSelection }));

    const dropdownWrapper = mount(
      {
        render() {
          return wrapper.find({ name: 'Trigger' }).vm.getComponent();
        },
      },
      { sync: false },
    );
    expect(dropdownWrapper.findAll('.ant-dropdown-menu-item').length).toBe(4);

    dropdownWrapper
      .findAll('.ant-dropdown-menu-item > div')
      .at(2)
      .trigger('click');
    expect(handleSelectOdd).toBeCalledWith([0, 1, 2, 3]);

    dropdownWrapper
      .findAll('.ant-dropdown-menu-item > div')
      .at(3)
      .trigger('click');
    expect(handleSelectEven).toBeCalledWith([0, 1, 2, 3]);
  });

  it('could hide default selection options', () => {
    const rowSelection = {
      hideDefaultSelections: true,
      selections: [
        {
          key: 'odd',
          text: '奇数项',
        },
        {
          key: 'even',
          text: '偶数项',
        },
      ],
    };
    const wrapper = mount(Table, getTableOptions({ rowSelection }));
    const dropdownWrapper = mount(
      {
        render() {
          return wrapper.find({ name: 'Trigger' }).vm.getComponent();
        },
      },
      { sync: false },
    );
    expect(dropdownWrapper.findAll('.ant-dropdown-menu-item').length).toBe(2);
  });

  it('handle custom selection onSelect correctly when hide default selection options', () => {
    const handleSelectOdd = jest.fn();
    const handleSelectEven = jest.fn();
    const rowSelection = {
      hideDefaultSelections: true,
      selections: [
        {
          key: 'odd',
          text: '奇数项',
          onSelect: handleSelectOdd,
        },
        {
          key: 'even',
          text: '偶数项',
          onSelect: handleSelectEven,
        },
      ],
    };
    const wrapper = mount(Table, getTableOptions({ rowSelection }));

    const dropdownWrapper = mount(
      {
        render() {
          return wrapper.find({ name: 'Trigger' }).vm.getComponent();
        },
      },
      { sync: false },
    );
    expect(dropdownWrapper.findAll('.ant-dropdown-menu-item').length).toBe(2);

    dropdownWrapper
      .findAll('.ant-dropdown-menu-item > div')
      .at(0)
      .trigger('click');
    expect(handleSelectOdd).toBeCalledWith([0, 1, 2, 3]);

    dropdownWrapper
      .findAll('.ant-dropdown-menu-item > div')
      .at(1)
      .trigger('click');
    expect(handleSelectEven).toBeCalledWith([0, 1, 2, 3]);
  });

  // https:// github.com/ant-design/ant-design/issues/4245
  it('handles disabled checkbox correctly when dataSource changes', async () => {
    const rowSelection = {
      getCheckboxProps: record => {
        return { props: { disabled: record.disabled } };
      },
    };
    const wrapper = mount(Table, getTableOptions({ rowSelection }));
    const newData = [
      { key: 7, name: 'Jack', disabled: true },
      { key: 8, name: 'Lucy', disabled: true },
    ];
    await asyncExpect(() => {
      wrapper.setProps({ dataSource: newData });
    });
    await asyncExpect(() => {
      wrapper.findAll('input').wrappers.forEach(checkbox => {
        expect(checkbox.vnode.data.attrs.disabled).toBe(true);
      });
    });
  });

  // https://github.com/ant-design/ant-design/issues/4779
  it('should not switch pagination when select record', async () => {
    const newData = [];
    for (let i = 0; i < 20; i += 1) {
      newData.push({
        key: i.toString(),
        name: i.toString(),
      });
    }
    const wrapper = mount(
      Table,
      getTableOptions({
        rowSelection: {},
        dataSource: newData,
      }),
    );
    const pager = wrapper.findAll({ name: 'Pager' });
    pager.at(pager.length - 1).trigger('click'); // switch to second page
    wrapper.findAll('input').at(0).element.checked = true;
    wrapper
      .findAll('input')
      .at(0)
      .trigger('change');
    await asyncExpect(() => {
      expect(renderedNames(wrapper)).toEqual([
        '10',
        '11',
        '12',
        '13',
        '14',
        '15',
        '16',
        '17',
        '18',
        '19',
      ]);
    });
  });

  it('highlight selected row', async () => {
    const wrapper = mount(Table, getTableOptions());
    wrapper.findAll('input').at(1).element.checked = true;
    wrapper
      .findAll('input')
      .at(1)
      .trigger('change');
    await asyncExpect(() => {
      expect(
        wrapper
          .findAll('tbody tr')
          .at(0)
          .classes(),
      ).toContain('ant-table-row-selected');
    });
  });

  it('fix selection column on the left', async () => {
    const wrapper = mount(
      Table,
      getTableOptions({
        rowSelection: { fixed: true },
      }),
    );
    await asyncExpect(() => {
      expect(wrapper.html()).toMatchSnapshot();
    });
  });

  // https://github.com/ant-design/ant-design/issues/10629
  it('should keep all checked state when remove item from dataSource', async () => {
    const wrapper = mount(Table, {
      propsData: {
        columns,
        dataSource: data,
        rowSelection: {
          selectedRowKeys: [0, 1, 2, 3],
        },
      },
      sync: false,
    });
    await asyncExpect(() => {
      expect(wrapper.findAll({ name: 'ACheckbox' }).length).toBe(5);
      const allCheckbox = wrapper.findAll({ name: 'ACheckbox' });
      Array(allCheckbox.length).forEach((_, index) => {
        const checkbox = allCheckbox.at(index);
        expect(checkbox.vm.checked).toBe(true);
        expect(checkbox.vm.indeterminate).toBe(false);
      });
      wrapper.setProps({
        dataSource: data.slice(1),
        rowSelection: {
          selectedRowKeys: [1, 2, 3],
        },
      });
    });
    await asyncExpect(() => {
      expect(wrapper.findAll({ name: 'ACheckbox' }).length).toBe(4);
      const allCheckbox = wrapper.findAll({ name: 'ACheckbox' });
      Array(allCheckbox.length).forEach((_, index) => {
        const checkbox = allCheckbox.at(index);
        expect(checkbox.vm.checked).toBe(true);
        expect(checkbox.vm.indeterminate).toBe(false);
      });
    });
  });

  // https://github.com/ant-design/ant-design/issues/11042
  it('add columnTitle for rowSelection', async () => {
    const wrapper = mount(Table, {
      propsData: {
        columns,
        dataSource: data,
        rowSelection: {
          columnTitle: '多选',
        },
      },
      sync: false,
    });
    await asyncExpect(() => {
      expect(
        wrapper
          .findAll('thead tr div')
          .at(0)
          .text(),
      ).toBe('多选');
    });
    await asyncExpect(() => {
      wrapper.setProps({
        rowSelection: {
          type: 'radio',
          columnTitle: '单选',
        },
      });
    });
    await asyncExpect(() => {
      expect(
        wrapper
          .findAll('thead tr div')
          .at(0)
          .text(),
      ).toBe('单选');
    });
  });

  // https://github.com/ant-design/ant-design/issues/11384
  it('should keep item even if in filter', async () => {
    const filterColumns = [
      {
        title: 'Name',
        dataIndex: 'name',
        filters: [
          {
            text: 'Jack',
            value: 'Jack',
          },
          {
            text: 'Lucy',
            value: 'Lucy',
          },
        ],
        filterDropdownVisible: true,
        onFilter: (value, record) => record.name.indexOf(value) === 0,
      },
    ];

    const onChange = jest.fn();
    const rowSelection = {
      onChange,
    };

    const wrapper = mount(Table, {
      propsData: {
        columns: filterColumns,
        dataSource: data,
        rowSelection,
      },
      sync: false,
    });

    const dropdownWrapper = mount(
      {
        render() {
          return wrapper.find({ name: 'Trigger' }).vm.getComponent();
        },
      },
      { sync: false },
    );

    function clickItem() {
      wrapper
        .findAll('tbody .ant-table-selection-column .ant-checkbox-input')
        .at(0).element.checked = true;
      wrapper
        .findAll('tbody .ant-table-selection-column .ant-checkbox-input')
        .at(0)
        .trigger('change');
    }

    // Check Jack
    dropdownWrapper
      .findAll('.ant-dropdown-menu-item .ant-checkbox-wrapper')
      .at(0)
      .trigger('click');
    dropdownWrapper
      .find('.ant-table-filter-dropdown-btns .ant-table-filter-dropdown-link.confirm')
      .trigger('click');
    await asyncExpect(() => {
      expect(wrapper.findAll('tbody tr').length).toBe(1);
    });
    await asyncExpect(() => {
      clickItem();
    });
    await asyncExpect(() => {
      expect(onChange.mock.calls[0][0].length).toBe(1);
      expect(onChange.mock.calls[0][1].length).toBe(1);
    });

    await asyncExpect(() => {
      dropdownWrapper
        .findAll('.ant-dropdown-menu-item .ant-checkbox-wrapper')
        .at(0)
        .trigger('click');
    });

    await asyncExpect(() => {
      // Check Lucy
      dropdownWrapper
        .findAll('.ant-dropdown-menu-item .ant-checkbox-wrapper')
        .at(1)
        .trigger('click');
    });
    await asyncExpect(() => {
      dropdownWrapper
        .find('.ant-table-filter-dropdown-btns .ant-table-filter-dropdown-link.confirm')
        .trigger('click');
    });
    await asyncExpect(() => {
      expect(wrapper.findAll('tbody tr').length).toBe(1);
    });
    await asyncExpect(() => {
      clickItem();
    });
    await asyncExpect(() => {
      expect(onChange.mock.calls[1][0].length).toBe(2);
      expect(onChange.mock.calls[1][1].length).toBe(2);
    });
  });

  it('render correctly when set childrenColumnName', async () => {
    const newDatas = [
      {
        key: 1,
        name: 'Jack',
        children: [
          {
            key: 11,
            name: 'John Brown',
          },
        ],
      },
      {
        key: 2,
        name: 'Lucy',
        children: [
          {
            key: 21,
            name: 'Lucy Brown',
          },
        ],
      },
    ];

    const wrapper = mount(Table, {
      propsData: {
        columns,
        dataSource: newDatas,
        rowSelection: {},
        childrenColumnName: 'test',
      },
      sync: false,
    });

    const checkboxes = wrapper.findAll('input');
    const checkboxAll = wrapper.find({ name: 'SelectionCheckboxAll' });

    checkboxes.at(1).element.checked = true;
    checkboxes.at(1).trigger('change');
    expect(checkboxAll.vm.$data).toEqual({ indeterminate: true, checked: false });

    checkboxes.at(2).element.checked = true;
    checkboxes.at(2).trigger('change');
    await asyncExpect(() => {
      expect(checkboxAll.vm.$data).toEqual({ indeterminate: false, checked: true });
    });
  });
});