ant-design/ant-design

View on GitHub
components/table/__tests__/Table.filter.test.tsx

Summary

Maintainability
F
1 wk
Test Coverage
/* eslint-disable no-unsafe-optional-chaining */
/* eslint-disable react/no-multi-comp */
import React, { useEffect, useState } from 'react';

import type { ColumnGroupType, ColumnType, TableProps } from '..';
import Table from '..';
import { resetWarned } from '../../_util/warning';
import { act, fireEvent, render, waitFor } from '../../../tests/utils';
import Button from '../../button';
import ConfigProvider from '../../config-provider';
import Input from '../../input';
import Menu from '../../menu';
import type { SelectProps } from '../../select';
import Select from '../../select';
import Tooltip from '../../tooltip';
import type {
  ColumnFilterItem,
  ColumnsType,
  FilterDropdownProps,
  FilterValue,
  SorterResult,
} from '../interface';

// https://github.com/Semantic-Org/Semantic-UI-React/blob/72c45080e4f20b531fda2e3e430e384083d6766b/test/specs/modules/Dropdown/Dropdown-test.js#L73
const nativeEvent = { nativeEvent: { stopImmediatePropagation: () => {} } };

describe('Table.filter', () => {
  window.requestAnimationFrame = (callback) => window.setTimeout(callback, 16);
  window.cancelAnimationFrame = window.clearTimeout;

  const filterFn = (value: any, record: any) => record.name.includes(value);
  const column: ColumnGroupType<any> | ColumnType<any> = {
    title: 'Name',
    dataIndex: 'name',
    filters: [
      { text: 'Boy', value: 'boy' },
      { text: 'Girl', value: 'girl' },
      {
        text: 'Title',
        value: 'title',
        children: [
          { text: 'Designer', value: 'designer' },
          { text: 'Coder', value: 'coder' },
        ],
      },
    ],
    onFilter: filterFn,
  };

  const data = [
    { key: 0, name: 'Jack' },
    { key: 1, name: 'Lucy' },
    { key: 2, name: 'Tom' },
    { key: 3, name: 'Jerry' },
  ];

  const longData: Record<'key' | 'name', string>[] = [];
  for (let i = 0; i < 100; i += 1) {
    longData.push({ key: i.toString(), name: 'name' });
  }

  function createTable(props?: TableProps<any>) {
    return <Table columns={[column]} dataSource={data} pagination={false} {...props} />;
  }

  function renderedNames(container: ReturnType<typeof render>['container']) {
    const namesList: (Node['textContent'] | undefined)[] = [];
    container
      ?.querySelector('.ant-table-tbody')
      ?.querySelectorAll('tr')
      ?.forEach((tr) => {
        namesList.push(tr.querySelector('td')?.textContent);
      });
    return namesList;
  }

  // Seems raf not trigger when in useEffect for async update
  // Need trigger multiple times
  function refreshTimer() {
    for (let i = 0; i < 3; i += 1) {
      act(() => {
        jest.runAllTimers();
      });
    }
  }

  beforeEach(() => {
    jest.useFakeTimers();
  });

  afterEach(() => {
    jest.clearAllTimers();
    jest.useRealTimers();
  });

  it('not show filter icon when undefined', () => {
    const noFilterColumn = { ...column, filters: undefined };
    delete noFilterColumn.onFilter;
    const { container } = render(
      createTable({
        columns: [noFilterColumn],
      }),
    );

    expect(container.querySelectorAll('.ant-table-filter-column')).toHaveLength(0);
  });

  // https://github.com/ant-design/ant-design/issues/26988
  it('not show filter icon when filter and filterDropdown is undefined', () => {
    const noFilterColumn = { ...column, filters: undefined, filterDropdown: undefined };
    delete noFilterColumn.onFilter;
    const { container } = render(
      createTable({
        columns: [noFilterColumn],
      }),
    );

    expect(container.querySelectorAll('.ant-table-filter-column')).toHaveLength(0);
  });

  it('renders filter correctly', () => {
    const { asFragment } = render(createTable());

    expect(asFragment().firstChild).toMatchSnapshot();
  });

  // async await 解决 Warning: An update to Item ran an effect, but was not wrapped in act(...).
  it('renders menu correctly', async () => {
    const { container } = render(createTable());
    fireEvent.click(container.querySelector('span.ant-dropdown-trigger')!, nativeEvent);
    await waitFor(() =>
      expect(container.querySelector('.ant-table-filter-dropdown')).toMatchSnapshot(),
    );
  });

  it('renders empty menu correctly', () => {
    resetWarned();

    const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
    const { container } = render(
      createTable({
        columns: [
          {
            ...column,
            filters: [],
          },
        ],
      }),
    );

    fireEvent.click(container.querySelector('span.ant-dropdown-trigger')!, nativeEvent);

    act(() => {
      jest.runAllTimers();
    });

    expect(container.querySelector('.ant-empty')).toBeTruthy();
    expect(errorSpy).not.toHaveBeenCalled();
    errorSpy.mockRestore();
  });

  it('renders radio filter correctly', async () => {
    const { container } = render(
      createTable({
        columns: [
          {
            ...column,
            filterMultiple: false,
          },
        ],
      }),
    );
    fireEvent.click(container.querySelector('span.ant-dropdown-trigger')!, nativeEvent);
    await waitFor(() =>
      expect(container.querySelector('.ant-table-filter-dropdown')).toMatchSnapshot(),
    );
  });

  it('renders custom content correctly', async () => {
    const filter = <div className="custom-filter-dropdown">custom filter</div>;
    const { container } = render(
      createTable({
        columns: [
          {
            ...column,
            filterDropdown: filter,
          },
        ],
      }),
    );

    fireEvent.click(container.querySelector('span.ant-dropdown-trigger')!, nativeEvent);
    await waitFor(() =>
      expect(container.querySelector('.ant-table-filter-dropdown')).toMatchSnapshot(),
    );
  });

  it('override custom filter correctly', () => {
    let renderSelectedKeys: React.Key[] | null = null;
    const filter = ({
      prefixCls,
      setSelectedKeys,
      selectedKeys,
      confirm,
      clearFilters,
    }: FilterDropdownProps): React.ReactNode => {
      renderSelectedKeys = selectedKeys;

      return (
        <div className={`${prefixCls}-view`} id="customFilter">
          <span onClick={() => setSelectedKeys([42])} id="setSelectedKeys">
            setSelectedKeys
          </span>
          <span onClick={() => confirm?.()} id="confirm">
            Confirm
          </span>
          <span onClick={() => clearFilters?.()} id="reset">
            Reset
          </span>
          <span
            onClick={() => {
              setSelectedKeys([43]);
              confirm();
            }}
            id="simulateOnSelect"
          >
            SimulateOnSelect
          </span>
        </div>
      );
    };

    const { container } = render(
      createTable({
        columns: [
          {
            ...column,
            filterDropdown: filter,
          },
        ],
      }),
    );

    // check if renderer well
    fireEvent.click(container.querySelector('span.ant-dropdown-trigger')!);
    expect(container.querySelector('#customFilter')).toMatchSnapshot();

    // try to use reset btn
    expect(renderSelectedKeys).toHaveLength(0);
    fireEvent.click(container.querySelector('#setSelectedKeys')!);
    fireEvent.click(container.querySelector('#confirm')!);
    expect(renderSelectedKeys).toEqual([42]);

    // Reset
    fireEvent.click(container.querySelector('#reset')!);
    fireEvent.click(container.querySelector('#confirm')!);
    expect(renderSelectedKeys).toHaveLength(0);

    // try to use confirm btn
    fireEvent.click(container.querySelector('span.ant-dropdown-trigger')!);
    fireEvent.click(container.querySelector('#setSelectedKeys')!);
    expect(container.querySelector('.ant-dropdown-open')).toBeTruthy();
    fireEvent.click(container.querySelector('#confirm')!);
    expect(renderSelectedKeys).toEqual([42]);
    expect(container.querySelector('.ant-dropdown-open')).toBeFalsy();

    // Simulate onSelect, setSelectedKeys & confirm
    fireEvent.click(container.querySelector('span.ant-dropdown-trigger')!);
    fireEvent.click(container.querySelector('#simulateOnSelect')!);
    expect(renderSelectedKeys).toEqual([43]);
  });

  describe('filterDropdownOpen & filterDropdownVisible', () => {
    function test(propName: string) {
      it(`can be controlled by ${propName}`, () => {
        const { container, rerender } = render(
          createTable({
            columns: [
              {
                ...column,
                filterDropdownOpen: true,
              },
            ],
          }),
        );

        expect(container.querySelector('.ant-dropdown-open')).toBeTruthy();

        rerender(
          createTable({
            columns: [
              {
                ...column,
                filterDropdownOpen: false,
              },
            ],
          }),
        );

        expect(container.querySelector('.ant-dropdown-open')).toBeFalsy();
      });
    }

    test('filterDropdownOpen');
    test('filterDropdownVisible');
  });

  it('if the filter is visible it should ignore the selectedKeys changes', () => {
    const myColumn = {
      title: 'Name',
      dataIndex: 'name',
      filters: [{ text: 'J', value: 'J' }],
      onFilter: (value: any, record: any) => record.name.includes(value),
    };

    const tableProps = {
      columns: [
        {
          ...myColumn,
          filterDropdownOpen: true,
        },
      ],
    };

    const { container, rerender } = render(createTable(tableProps));

    const checkboxList = container
      ?.querySelector('.ant-table-filter-dropdown')
      ?.querySelectorAll<HTMLInputElement>('input[type="checkbox"]');
    expect(checkboxList?.length).toBeTruthy();
    checkboxList?.forEach((checkbox) => {
      expect((checkbox as any)?.checkbox).toBeFalsy();
    });

    fireEvent.click(
      container
        .querySelector('.ant-table-filter-dropdown')
        ?.querySelector('input[type="checkbox"]')!,
    );
    fireEvent.click(
      container
        ?.querySelector('.ant-table-filter-dropdown')
        ?.querySelector('.ant-table-filter-dropdown-btns .ant-btn-primary')!,
    );

    expect(container.querySelectorAll('tbody tr')).toHaveLength(2);

    rerender(
      createTable({
        ...tableProps,
        dataSource: [...data, { key: 999, name: 'Jason' }],
      }),
    );
    expect(container.querySelectorAll('tbody tr')).toHaveLength(3);
  });

  it('fires change event when visible change', () => {
    resetWarned();
    const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});

    const onFilterDropdownOpenChange = jest.fn();
    const onFilterDropdownVisibleChange = jest.fn();
    const { container } = render(
      createTable({
        columns: [
          {
            ...column,
            onFilterDropdownOpenChange,
            onFilterDropdownVisibleChange,
          },
        ],
      }),
    );
    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    expect(onFilterDropdownOpenChange).toHaveBeenCalledWith(true);
    expect(onFilterDropdownVisibleChange).toHaveBeenCalledWith(true);

    expect(errSpy).toHaveBeenCalledWith(
      'Warning: [antd: Table] `onFilterDropdownVisibleChange` is deprecated. Please use `onFilterDropdownOpenChange` instead.',
    );

    errSpy.mockRestore();
  });

  it('can be controlled by filteredValue', () => {
    const { container, rerender } = render(
      createTable({
        columns: [
          {
            ...column,
            filteredValue: ['Lucy'],
          },
        ],
      }),
    );

    expect(container.querySelectorAll('tbody tr').length).toBe(1);

    rerender(
      createTable({
        columns: [
          {
            ...column,
            filteredValue: [],
          },
        ],
      }),
    );

    expect(container.querySelectorAll('tbody tr').length).toBe(4);
  });

  it('should handle filteredValue and non-array filterValue as expected', () => {
    let filterKeys = new Set();

    const { rerender } = render(
      createTable({
        columns: [
          {
            ...column,
            filteredValue: ['Lucy', 12, true],
            onFilter: (value) => {
              filterKeys.add(value);
              return false;
            },
          },
        ],
      }),
    );

    expect(Array.from(filterKeys)).toEqual(['Lucy', '12', 'true']);

    filterKeys = new Set();
    rerender(
      createTable({
        columns: [
          {
            ...column,
            filteredValue: null,
            onFilter: (value) => {
              filterKeys.add(value);
              return true;
            },
          },
        ],
      }),
    );
    expect(Array.from(filterKeys)).toHaveLength(0);
  });

  it('can be controlled by filteredValue null', () => {
    const { container, rerender } = render(
      createTable({
        columns: [
          {
            ...column,
            filteredValue: ['Lucy'],
          },
        ],
      }),
    );

    expect(container.querySelectorAll('tbody tr').length).toBe(1);

    rerender(
      createTable({
        columns: [
          {
            ...column,
            filteredValue: null,
          },
        ],
      }),
    );

    expect(container.querySelectorAll('tbody tr').length).toBe(4);
  });

  // Warning: An update to Item ran an effect, but was not wrapped in act(...).
  it('render checked of checkbox correctly controlled by filteredValue', () => {
    ['Lucy', 23, false].forEach((val) => {
      const { container } = render(
        createTable({
          columns: [
            {
              ...column,
              filters: [{ text: val, value: val }],
              filteredValue: [val],
            },
          ],
        }),
      );

      fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
      expect(
        container
          ?.querySelector('.ant-table-filter-dropdown')
          ?.querySelectorAll<HTMLInputElement>('.ant-checkbox-input')[0].checked,
      ).toBe(true);
    });

    const { container } = render(
      createTable({
        columns: [
          {
            ...column,
            filters: [{ text: 'ant', value: 'ant' }],
            filteredValue: ['any-value-not-exists-in-filters'],
          },
        ],
      }),
    );
    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);

    expect(
      container
        ?.querySelector('.ant-table-filter-dropdown')
        ?.querySelectorAll<HTMLInputElement>('.ant-checkbox-input')[0]?.checked,
    ).toBe(false);
  });

  it('can read defaults from defaultFilteredValue', () => {
    const { container, rerender } = render(
      createTable({
        columns: [
          {
            ...column,
            defaultFilteredValue: ['Lucy'],
          },
        ],
      }),
    );
    expect(container.querySelectorAll('tbody tr').length).toBe(1);
    expect(container.querySelector('tbody tr')?.textContent).toBe('Lucy');

    // Should properly ignore further defaultFilteredValue changes
    rerender(
      createTable({
        columns: [
          {
            ...column,
            defaultFilteredValue: [],
          },
        ],
      }),
    );

    expect(container.querySelectorAll('tbody tr').length).toBe(1);
    expect(container.querySelector('tbody tr')?.textContent).toBe('Lucy');

    // Should properly be overridden by non-null filteredValue
    rerender(
      createTable({
        columns: [
          {
            ...column,
            defaultFilteredValue: ['Lucy'],
            filteredValue: ['Tom'],
          },
        ],
      }),
    );
    expect(container.querySelectorAll('tbody tr').length).toBe(1);
    expect(container.querySelector('tbody tr')?.textContent).toBe('Tom');

    // Should properly be overridden by a null filteredValue
    rerender(
      createTable({
        columns: [
          {
            ...column,
            defaultFilteredValue: ['Lucy'],
            filteredValue: null,
          },
        ],
      }),
    );
    expect(container.querySelectorAll('tbody tr').length).toBe(4);
  });

  it('can filter children by defaultFilteredValue', () => {
    const { container } = render(
      createTable({
        columns: [
          {
            ...column,
            defaultFilteredValue: ['Jim', 'Tom'],
            onFilter: (value, record) => {
              if (record.children && record.children.length) {
                return true;
              }
              return record.name.includes(value);
            },
          },
        ],
        dataSource: [
          {
            key: '0',
            name: 'Jack',
            children: [
              { key: '0-1', name: 'Jim' },
              { key: '0-2', name: 'Tony' },
            ],
          },
          { key: '1', name: 'Lucy' },
          { key: '2', name: 'Tom' },
          { key: '3', name: 'Jerry' },
        ],
        expandable: {
          defaultExpandAllRows: true,
        },
      }),
    );

    expect([...container.querySelectorAll('tbody tr')].map((item) => item.textContent)).toEqual([
      'Jack',
      'Jim',
      'Tom',
    ]);
  });

  //  Warning: An update to Item ran an effect, but was not wrapped in act(...).
  it('fires change event', () => {
    const handleChange = jest.fn();
    const { container } = render(createTable({ onChange: handleChange }));
    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    fireEvent.click(container.querySelectorAll('.ant-dropdown-menu-item')[0]);
    fireEvent.click(container.querySelector('.ant-table-filter-dropdown-btns .ant-btn-primary')!);
    expect(handleChange).toHaveBeenCalledWith(
      {},
      { name: ['boy'] },
      {},
      {
        currentDataSource: [],
        action: 'filter',
      },
    );
  });

  it('fires pagination change event', async () => {
    const onPaginationChange = jest.fn();
    const { container } = render(createTable({ pagination: { onChange: onPaginationChange } }));
    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    fireEvent.click(container.querySelectorAll('.ant-dropdown-menu-item')[0]);
    fireEvent.click(container.querySelector('.ant-table-filter-dropdown-btns .ant-btn-primary')!);
    await waitFor(() => expect(onPaginationChange).toHaveBeenCalledWith(1, 10));
  });

  it('should not fire change event when close filterDropdown without changing anything', async () => {
    const handleChange = jest.fn();
    const { container } = render(createTable({ onChange: handleChange }));

    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    fireEvent.click(container.querySelector('.ant-table-filter-dropdown-btns .ant-btn-primary')!);
    await waitFor(() => expect(handleChange).not.toHaveBeenCalled());
  });

  it('should not fire change event when close a filtered filterDropdown without changing anything', async () => {
    const handleChange = jest.fn();
    const { container } = render(
      createTable({
        onChange: handleChange,
        columns: [
          {
            ...column,
            defaultFilteredValue: ['boy', 'designer'],
          },
        ],
      }),
    );
    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    fireEvent.click(container.querySelector('.ant-table-filter-dropdown-btns .ant-btn-primary')!);
    await waitFor(() => expect(handleChange).not.toHaveBeenCalled());
  });

  it('three levels menu', () => {
    const onChange = jest.fn();
    const filters = [
      { text: 'Upper', value: 'Upper' },
      { text: 'Lower', value: 'Lower' },
      {
        text: 'Level2',
        value: 'Level2',
        children: [
          { text: 'Large', value: 'Large' },
          { text: 'Small', value: 'Small' },
          {
            text: 'Level3',
            value: 'Level3',
            children: [
              { text: 'Black', value: 'Black' },
              { text: 'White', value: 'White' },
              { text: 'Jack', value: 'Jack' },
            ],
          },
        ],
      },
    ];
    const { container } = render(createTable({ columns: [{ ...column, filters }], onChange }));

    expect(renderedNames(container)).toEqual(['Jack', 'Lucy', 'Tom', 'Jerry']);

    // Open
    fireEvent.click(container.querySelector('.ant-table-filter-trigger')!);

    function getFilterMenu() {
      return container.querySelector('.ant-table-filter-dropdown');
    }

    // Open Level2
    fireEvent.mouseEnter(
      getFilterMenu()?.querySelectorAll('div.ant-dropdown-menu-submenu-title')[0]!,
    );
    refreshTimer();

    // Open Level3
    fireEvent.mouseEnter(
      getFilterMenu()?.querySelectorAll('div.ant-dropdown-menu-submenu-title')[1]!,
    );
    refreshTimer();

    // Select Level3 value
    const items = getFilterMenu()?.querySelectorAll('li.ant-dropdown-menu-item');
    fireEvent.click(items?.[items.length - 1]!);
    fireEvent.click(
      getFilterMenu()?.querySelector('.ant-table-filter-dropdown-btns .ant-btn-primary')!,
    );
    refreshTimer();

    expect(onChange).toHaveBeenCalled();
    onChange.mock.calls.forEach(([, currentFilters]) => {
      const [, val] = Object.entries(currentFilters)[0];
      expect(val).toEqual(['Jack']);
    });

    expect(renderedNames(container)).toEqual(['Jack']);

    // What's this? Is that a coverage case? Or check a crash?
    const latestItems = getFilterMenu()?.querySelectorAll('li.ant-dropdown-menu-item');
    fireEvent.click(latestItems?.[latestItems?.length - 1]!);
  });

  describe('should support value types', () => {
    const filterKeys = new Set();
    [
      ['Light', 93],
      ['Bamboo', false],
    ].forEach(([text, value]) => {
      it(`${typeof value} type`, async () => {
        const onChange = jest.fn();
        const filters = [{ text, value }];
        const { container } = render(
          createTable({
            columns: [
              {
                ...column,
                filters,
                onFilter: (val) => {
                  expect(val).toBe(value);
                  filterKeys.add(val);
                  return false;
                },
              },
            ],
            onChange,
          }),
        );

        fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);

        fireEvent.click(container.querySelectorAll('.ant-dropdown-menu-item')[0]);

        // This test can be remove if refactor
        fireEvent.click(
          container.querySelector('.ant-table-filter-dropdown-btns .ant-btn-primary')!,
        );

        await waitFor(() =>
          expect(
            container
              ?.querySelector('.ant-table-filter-dropdown')
              ?.querySelectorAll<HTMLInputElement>('.ant-checkbox-input')[0].checked,
          ).toEqual(true),
        );

        expect(typeof Array.from(filterKeys)[0]).toEqual('number');

        expect(Array.from(filterKeys).length > 0).toBeTruthy();

        onChange.mock.calls.forEach(([, currentFilters]) => {
          const [, val] = Object.entries(currentFilters)[0];
          expect(val).toEqual([value]);
        });
        // Another time of Filter show
        // https://github.com/ant-design/ant-design/issues/15593
        fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);

        fireEvent.click(container.querySelectorAll('.ant-dropdown-menu-item')[0]);

        expect(
          container
            ?.querySelector('.ant-table-filter-dropdown')
            ?.querySelectorAll<HTMLInputElement>('.ant-checkbox-input')[0].checked,
        ).toBe(false);
      });
    });
  });

  it('works with JSX in controlled mode', () => {
    const { Column } = Table;
    const App: React.FC = () => {
      const [filters, setFilters] = React.useState<{ name?: ColumnType<any>['filteredValue'] }>({});
      const handleChange: TableProps<any>['onChange'] = (_, filter) => {
        setFilters(filter);
      };
      return (
        <Table dataSource={data} onChange={handleChange}>
          <Column
            title="name"
            dataIndex="name"
            key="name"
            onFilter={filterFn}
            filteredValue={filters.name}
            filters={[
              { text: 'Jack', value: 'Jack' },
              { text: 'Lucy', value: 'Lucy' },
            ]}
          />
        </Table>
      );
    };

    const { container } = render(<App />);

    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    expect(container.querySelector('.ant-dropdown-open')).toBeTruthy();

    fireEvent.click(container.querySelectorAll('.ant-dropdown-menu-item')[0]);
    fireEvent.click(container.querySelector('.ant-table-filter-dropdown-btns .ant-btn-primary')!);
    expect(renderedNames(container)).toEqual(['Jack']);
    expect(container.querySelector('.ant-dropdown-open')).toBeFalsy();

    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    fireEvent.click(container.querySelector('.ant-table-filter-dropdown-btns .ant-btn-link')!);

    expect(container.querySelector('.ant-dropdown-open')).toBeTruthy();
    expect(renderedNames(container)).toEqual(['Jack']);

    fireEvent.click(container.querySelector('.ant-table-filter-dropdown-btns .ant-btn-primary')!);
    expect(renderedNames(container)).toEqual(['Jack', 'Lucy', 'Tom', 'Jerry']);
    expect(container.querySelector('.ant-dropdown-open')).toBeFalsy();
  });

  it('works with grouping columns in controlled mode', () => {
    const columns = [
      {
        title: 'group',
        key: 'group',
        children: [
          {
            title: 'Name',
            dataIndex: 'name',
            key: 'name',
            filters: [
              { text: 'Jack', value: 'Jack' },
              { text: 'Lucy', value: 'Lucy' },
            ],
            onFilter: filterFn,
            filteredValue: ['Jack'],
          },
          {
            title: 'Age',
            dataIndex: 'age',
            key: 'age',
          },
        ],
      },
    ];
    const testData = [
      { key: 0, name: 'Jack', age: 11 },
      { key: 1, name: 'Lucy', age: 20 },
      { key: 2, name: 'Tom', age: 21 },
      { key: 3, name: 'Jerry', age: 22 },
    ];
    const { container } = render(<Table columns={columns} dataSource={testData} />);

    expect(renderedNames(container)).toEqual(['Jack']);
  });

  // Warning: An update to Item ran an effect, but was not wrapped in act(...).
  it('confirm filter when dropdown hidden', () => {
    const handleChange = jest.fn();
    const { container } = render(
      createTable({
        columns: [
          {
            ...column,
            filters: [
              { text: 'Jack', value: 'Jack' },
              { text: 'Lucy', value: 'Lucy' },
            ],
          },
        ],
        onChange: handleChange,
      }),
    );
    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    fireEvent.click(container.querySelector('.ant-dropdown-menu-item')!);
    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);

    expect(handleChange).toHaveBeenCalled();
    expect(handleChange.mock.calls[0][3].currentDataSource.length).toBe(1);
  });

  it('renders custom filter icon correctly', () => {
    const filterIcon = (filtered: boolean): React.ReactNode => (
      <span className="customize-icon">{filtered ? 'filtered' : 'unfiltered'}</span>
    );
    const { container } = render(
      createTable({
        columns: [
          {
            ...column,
            filterIcon,
          },
        ],
      }),
    );
    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    fireEvent.click(container.querySelector('.ant-dropdown-menu-item')!);
    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    expect(container.querySelector('.customize-icon')).toMatchSnapshot();
  });

  it('renders custom filter icon as string correctly', () => {
    const filterIcon = () => 'string';
    const { asFragment } = render(
      createTable({
        columns: [
          {
            ...column,
            filterIcon,
          },
        ],
      }),
    );
    expect(asFragment().firstChild).toMatchSnapshot();
  });

  it('renders custom filter icon with right Tooltip title', () => {
    const filterIcon = () => (
      <Tooltip title="title" open>
        Tooltip
      </Tooltip>
    );
    const { asFragment } = render(
      createTable({
        columns: [
          {
            ...column,
            filterIcon,
          },
        ],
      }),
    );
    expect(asFragment().firstChild).toMatchSnapshot();
  });

  it('renders custom filter icon as ReactNode', () => {
    const filterIcon = <span className="customize-icon" />;
    const { container, asFragment } = render(
      createTable({
        columns: [
          {
            ...column,
            filterIcon,
          },
        ],
      }),
    );
    expect(asFragment().firstChild).toMatchSnapshot();
    expect(container.querySelector('span.customize-icon')).toBeTruthy();
  });

  // https://github.com/ant-design/ant-design/issues/13028
  it('reset dropdown filter correctly', () => {
    const Demo: React.FC = () => {
      const [name, setName] = React.useState<ColumnType<any>['filteredValue']>();
      const onChange = () => {
        setName('' as unknown as ColumnType<any>['filteredValue']);
      };
      return createTable({
        onChange,
        columns: [
          {
            title: 'Name',
            dataIndex: 'name',
            key: 'name',
            filteredValue: name,
            // eslint-disable-next-line react/no-unstable-nested-components
            filterDropdown: ({ setSelectedKeys, selectedKeys, confirm }) => (
              <div>
                <Input
                  value={selectedKeys[0]}
                  onChange={(e) => {
                    setSelectedKeys(e.target.value ? [e.target.value] : []);
                  }}
                />
                <Button onClick={() => confirm()}>Confirm</Button>
              </div>
            ),
          },
        ],
      });
    };

    const { container } = render(<Demo />);
    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    fireEvent.change(container.querySelector('.ant-input')!, { target: { value: 'test' } });
    expect(container.querySelector<HTMLInputElement>('.ant-input')?.value).toBe('test');
    fireEvent.click(container.querySelector('.ant-btn')!);
    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    expect(container.querySelector<HTMLInputElement>('.ant-input')?.value).toBe('');
  });

  // https://github.com/ant-design/ant-design/issues/17833
  it('should not trigger onChange when blurring custom filterDropdown', () => {
    const onChange = jest.fn();
    const filterDropdown = ({ setSelectedKeys }: FilterDropdownProps) => (
      <input onChange={(e) => setSelectedKeys([e.target.value])} />
    );
    const { container } = render(
      createTable({
        onChange,
        columns: [
          {
            title: 'Name',
            dataIndex: 'name',
            key: 'name',
            filterDropdown,
          },
        ],
      }),
    );
    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    fireEvent.change(container.querySelector('input')!, { target: { value: 'whatevervalue' } });
    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    expect(onChange).not.toHaveBeenCalled();
  });

  it('should trigger onChange with correct params if defines custom filterDropdown', () => {
    const onChange = jest.fn();
    const filterDropdown = ({ setSelectedKeys, confirm }: FilterDropdownProps) => (
      <div>
        <input onChange={(e) => setSelectedKeys([e.target.value])} />
        <button className="confirm-btn" type="submit" onClick={() => confirm()}>
          Confirm
        </button>
      </div>
    );
    const { container } = render(
      createTable({
        onChange,
        columns: [
          {
            title: 'Name',
            dataIndex: 'name',
            key: 'name',
            filterDropdown,
          },
        ],
      }),
    );

    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    fireEvent.change(container.querySelector('input')!, { target: { value: 'test' } });
    fireEvent.click(container.querySelector('.confirm-btn')!);

    expect(onChange).toHaveBeenCalled();
    onChange.mock.calls.forEach(([, currentFilters]) => {
      const [, val] = Object.entries(currentFilters)[0];
      expect(val).toEqual(['test']);
    });
  });

  it('should work as expected with complex custom filterDropdown', () => {
    let renderSelectedKeys = null;
    const onChange = jest.fn();

    const filterDropdown = ({ setSelectedKeys, selectedKeys, confirm }: FilterDropdownProps) => {
      renderSelectedKeys = selectedKeys;
      const handleChange: SelectProps['onChange'] = (selectedValues) => {
        setSelectedKeys(selectedValues);
      };

      return (
        <div>
          <Select
            mode="multiple"
            allowClear
            labelInValue
            style={{ width: 200 }}
            value={selectedKeys}
            onChange={handleChange}
            options={[
              {
                value: 1,
                label: 'Not Identified',
              },
              {
                value: 2,
                label: 'Closed',
              },
              {
                value: 3,
                label: 'Communicated',
              },
            ]}
          />
          <button className="confirm-btn" type="submit" onClick={() => confirm()}>
            Confirm
          </button>
        </div>
      );
    };
    const filteredValue = [
      {
        value: 2,
        label: 'Closed',
      },
    ] as unknown as ColumnType<any>['filteredValue'];
    const selectedValue = [
      {
        key: 2,
        value: 2,
        label: 'Closed',
      },
      {
        key: 1,
        value: 1,
        label: 'Not Identified',
      },
    ];
    const { container } = render(
      createTable({
        onChange,
        columns: [
          {
            title: 'Name',
            dataIndex: 'name',
            key: 'name',
            filterDropdown,
            filteredValue,
          },
        ],
      }),
    );

    expect(renderSelectedKeys).toEqual(filteredValue);

    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    fireEvent.mouseDown(container.querySelector('.ant-select-selector')!);
    fireEvent.click(container.querySelector('.ant-select-item-option')!);
    fireEvent.click(container.querySelector('.confirm-btn')!);
    expect(onChange).toHaveBeenCalled();
    onChange.mock.calls.forEach(([, currentFilters]) => {
      const [, val] = Object.entries(currentFilters)[0];
      expect(val).toEqual(selectedValue);
    });
  });

  // https://github.com/ant-design/ant-design/issues/17089
  it('not crash when dynamic change filter', () => {
    const onChange = jest.fn();

    const Test: React.FC<{ filters?: ColumnFilterItem[] }> = ({ filters }) => (
      <Table
        onChange={onChange}
        rowKey="name"
        columns={[
          {
            title: 'Name',
            dataIndex: 'name',
            filters,
            onFilter: (value: any, record: any) => record.name.indexOf(value) === 0,
            sorter: (a, b) => a.name.length - b.name.length,
            sortDirections: ['descend'],
          },
        ]}
        dataSource={[
          {
            name: 'Jack',
          },
        ]}
      />
    );

    const { container, rerender } = render(
      <Test
        filters={[
          {
            text: 'Bill',
            value: 'Bill',
          },
        ]}
      />,
    );

    // Warning: An update to Item ran an effect, but was not wrapped in act(...).
    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    fireEvent.click(container.querySelector('.ant-dropdown-menu-item')!);
    fireEvent.click(container.querySelector('.ant-table-filter-dropdown-btns .ant-btn-primary')!);
    expect(onChange).toHaveBeenCalled();
    onChange.mockReset();
    expect(onChange).not.toHaveBeenCalled();
    rerender(
      <Test
        filters={[
          {
            text: 'Jim',
            value: 'Jim',
          },
        ]}
      />,
    );

    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    fireEvent.click(container.querySelector('.ant-dropdown-menu-item')!);
    fireEvent.click(container.querySelector('.ant-table-filter-dropdown-btns .ant-btn-primary')!);
    expect(onChange).toHaveBeenCalled();
  });

  it('should support getPopupContainer', () => {
    const getPopupContainer = jest.fn((node) => node.parentNode);

    render(
      createTable({
        columns: [
          {
            ...column,
            filterDropdownOpen: true,
          },
        ],
        getPopupContainer,
      }),
    );
    expect(getPopupContainer).toHaveBeenCalled();
  });

  it('should support getPopupContainer from ConfigProvider', () => {
    const getPopupContainer = jest.fn((node) => node.parentNode);

    render(
      <ConfigProvider getPopupContainer={getPopupContainer}>
        {createTable({
          columns: [
            {
              ...column,
              filterDropdownOpen: true,
            },
          ],
        })}
      </ConfigProvider>,
    );
    expect(getPopupContainer).toHaveBeenCalled();
  });

  it('pass visible prop to filterDropdown', () => {
    const filterDropdownMock = jest.fn().mockReturnValue(<span>test</span>);
    const filterDropdown = (...args: any[]) => filterDropdownMock(...args);

    const Test = () => (
      <Table
        rowKey="name"
        columns={[{ title: 'Name', dataIndex: 'name', filterDropdown }]}
        dataSource={[{ name: 'Jack' }]}
      />
    );

    render(<Test />);
    expect(filterDropdownMock).toHaveBeenCalledWith(
      expect.objectContaining({
        visible: false,
      }),
    );
  });

  it('visible prop of filterDropdown changes on click', () => {
    const filterDropdownMock = jest.fn().mockReturnValue(<span>test</span>);
    const filterDropdown = (...args: any[]) => filterDropdownMock(...args);

    const Test: React.FC = () => (
      <Table
        rowKey="name"
        columns={[{ title: 'Name', dataIndex: 'name', filterDropdown }]}
        dataSource={[{ name: 'Jack' }]}
      />
    );

    const { container } = render(<Test />);

    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    expect(filterDropdownMock).toHaveBeenCalledWith(
      expect.objectContaining({
        visible: true,
      }),
    );
    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    expect(filterDropdownMock).toHaveBeenCalledWith(
      expect.objectContaining({
        visible: false,
      }),
    );
  });

  it('should reset pagination after filter', () => {
    const handleChange = jest.fn();
    const { container } = render(
      createTable({
        onChange: handleChange,
        dataSource: longData,
        pagination: true as TableProps<any>['pagination'],
      }),
    );
    // Warning: An update to Item ran an effect, but was not wrapped in act(...).
    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    fireEvent.click(container.querySelector('.ant-dropdown-menu-item')!);
    fireEvent.click(container.querySelector('.ant-table-filter-dropdown-btns .ant-btn-primary')!);

    expect(handleChange).toHaveBeenCalledWith(
      {
        current: 1,
        pageSize: 10,
      },
      { name: ['boy'] },
      {},
      {
        currentDataSource: [],
        action: 'filter',
      },
    );
    expect(container.querySelectorAll('.ant-pagination-item')).toHaveLength(0);
  });

  it('should keep pagination current after filter', () => {
    const handleChange = jest.fn();
    const { container } = render(
      createTable({
        onChange: handleChange,
        dataSource: longData,
        pagination: {
          current: 3,
        },
      }),
    );

    expect(container.querySelector('.ant-pagination-item-active')?.textContent).toBe('3');
    // Warning: An update to Item ran an effect, but was not wrapped in act(...).
    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    fireEvent.click(container.querySelector('.ant-dropdown-menu-item')!);
    fireEvent.click(container.querySelector('.ant-table-filter-dropdown-btns .ant-btn-primary')!);

    expect(handleChange).toHaveBeenCalledWith(
      {
        current: 1,
        pageSize: 10,
      },
      { name: ['boy'] },
      {},
      {
        currentDataSource: [],
        action: 'filter',
      },
    );
  });

  // https://github.com/ant-design/ant-design/issues/19274
  it('should not crash', () => {
    const TestTable: React.FC = () => {
      const [cols, setCols] = React.useState<ColumnsType<any>>([]);
      useEffect(() => {
        setCols([{ title: 'test', key: 'test', filterDropdown: 123 }]);
      }, []);
      return <Table columns={cols} dataSource={[]} scroll={{ x: 1000 }} />;
    };
    render(<TestTable />);
  });

  // https://github.com/ant-design/ant-design/issues/20854
  it('Not cache for onChange state', () => {
    const onChange = jest.fn();

    const { container } = render(
      <Table<{ name?: string; gender?: string }>
        columns={[
          {
            title: 'Name',
            dataIndex: 'name',
            sorter: true,
          },
          {
            title: 'Gender',
            dataIndex: 'gender',
            filters: [
              { text: 'Male', value: 'male' },
              { text: 'Female', value: 'female' },
            ],
          },
        ]}
        dataSource={[]}
        onChange={onChange}
      />,
    );

    // Sort it
    fireEvent.click(container.querySelector('.ant-table-column-sorters')!, nativeEvent);
    expect(onChange).toHaveBeenCalledWith(
      expect.anything(),
      { gender: null },
      expect.objectContaining({
        column: {
          dataIndex: 'name',
          sorter: true,
          title: 'Name',
        },
      }),
      {
        currentDataSource: expect.anything(),
        action: 'sort',
      },
    );

    // Filter it
    onChange.mockReset();
    // Warning: An update to Item ran an effect, but was not wrapped in act(...).
    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!, nativeEvent);
    fireEvent.click(container.querySelector('.ant-dropdown-menu-item')!);
    fireEvent.click(container.querySelector('.ant-table-filter-dropdown-btns .ant-btn-primary')!);

    expect(onChange).toHaveBeenCalledWith(
      expect.anything(),
      {
        gender: ['male'],
      },
      expect.objectContaining({
        column: {
          dataIndex: 'name',
          sorter: true,
          title: 'Name',
        },
      }),
      {
        currentDataSource: expect.anything(),
        action: 'filter',
      },
    );
  });

  it('locale should work', () => {
    const { container } = render(
      createTable({
        locale: { filterConfirm: 'Bamboo' },
        columns: [
          {
            ...column,
            filterDropdownOpen: true,
            filterSearch: true,
            filterMode: 'tree',
          },
        ],
      }),
    );

    expect(
      container.querySelector('.ant-table-filter-dropdown-btns .ant-btn-primary')?.textContent,
    ).toEqual('Bamboo');
    expect(
      container.querySelector('.ant-table-filter-dropdown-btns .ant-btn-link')?.textContent,
    ).toEqual('Reset');
    expect(container.querySelector('.ant-table-filter-dropdown-checkall')?.textContent).toEqual(
      'Select all items',
    );
    expect(container.querySelector('.ant-input')?.getAttribute('placeholder')).toEqual(
      'Search in filters',
    );
  });

  it('filtered should work', () => {
    const { container } = render(
      createTable({
        columns: [
          {
            ...column,
            filtered: true,
          },
        ],
      }),
    );

    expect(
      container.querySelector('.ant-table-filter-trigger')?.className.includes('active'),
    ).toBeTruthy();
  });

  it('filtered should work after change', () => {
    const App: React.FC = () => {
      const [filtered, setFiltered] = React.useState(true);
      const columns: TableProps['columns'] = [
        {
          title: 'Name',
          dataIndex: 'name',
          filtered,
          filters: [],
        },
      ];

      return (
        <div className="App">
          <Button
            id="change-filtered-btn"
            onClick={() => {
              setFiltered(!filtered);
            }}
          >
            Set
          </Button>
          <Table columns={columns} dataSource={data} />
        </div>
      );
    };
    const { container } = render(<App />);

    expect(
      container.querySelector('.ant-table-filter-trigger')?.className.includes('active'),
    ).toBeTruthy();

    fireEvent.click(container.querySelector('#change-filtered-btn')!);

    refreshTimer();

    expect(
      container.querySelector('.ant-table-filter-trigger')?.className.includes('active'),
    ).toBeFalsy();
  });

  it('filteredValue with empty array should not active the filtered icon', () => {
    const { container } = render(
      createTable({
        columns: [
          {
            ...column,
            filteredValue: [],
          },
        ],
      }),
    );

    expect(
      container.querySelector('.ant-table-filter-trigger')?.className.includes('active'),
    ).toBeFalsy();
  });

  it('with onFilter', () => {
    const onFilter = jest.fn((value, record) => record.key === value);
    const columns: TableProps['columns'] = [{ dataIndex: 'key', filteredValue: [5], onFilter }];
    const testData = [{ key: 1 }, { key: 3 }, { key: 5 }];
    const { container } = render(<Table columns={columns} dataSource={testData} />);

    expect(onFilter).toHaveBeenCalled();
    expect(container.querySelectorAll('tbody tr')).toHaveLength(1);
  });

  it('jsx work', () => {
    const { container } = render(
      <Table dataSource={data}>
        <Table.Column
          title="Name"
          dataIndex="name"
          filters={[
            { text: 'Jack', value: 'Jack' },
            { text: 'Lucy', value: 'Lucy' },
          ]}
          onFilter={(value, record: any) => record.name.includes(value)}
          defaultFilteredValue={['Jack']}
        />
      </Table>,
    );

    expect(container.querySelectorAll('tbody tr')).toHaveLength(1);
    expect(container.querySelector('tbody tr td')?.textContent).toEqual('Jack');
  });

  it(`shouldn't keep status when controlled filteredValue isn't change`, () => {
    const filterControlledColumn = {
      title: 'Name',
      dataIndex: 'name',
      filteredValue: null,
      filters: [
        { text: 'Boy', value: 'boy' },
        { text: 'Girl', value: 'girl' },
      ],
      onFilter: filterFn,
    };
    const { container } = render(createTable({ columns: [filterControlledColumn] }));
    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    fireEvent.click(container.querySelector('.ant-dropdown-menu-item')!);
    fireEvent.click(container.querySelector('.ant-table-filter-dropdown-btns .ant-btn-primary')!); // close dropdown
    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!); // reopen
    const checkbox = container
      ?.querySelector('.ant-dropdown-menu-item')
      ?.querySelector<HTMLInputElement>('input[type=checkbox]');
    expect(checkbox?.checked).toBe(false);
  });

  it('should not trigger onChange when filters is empty', () => {
    const onChange = jest.fn();
    const Test: React.FC<{ filters?: ColumnFilterItem[] }> = ({ filters }) => (
      <Table
        onChange={onChange}
        rowKey="name"
        columns={[{ title: 'Name', dataIndex: 'name', filters }]}
        dataSource={[{ name: 'Jack' }]}
      />
    );
    const { container, unmount } = render(<Test filters={[]} />);
    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    fireEvent.click(container.querySelector('.ant-table-filter-dropdown-btns .ant-btn-primary')!);
    expect(onChange).not.toHaveBeenCalled();
    onChange.mockReset();
    unmount();
  });

  it('filters in children should render', () => {
    const columns = [
      {
        title: 'English Score',
        dataIndex: 'english',
        filters: [{ text: '1', value: 1 }],
        onFilter: (record: any) => String(record.english1).includes(String(1)),
        children: [
          {
            title: 'English Score1',
            dataIndex: 'english1',
            filters: [{ text: '2', value: 2 }],
            onFilter: (record: any) => String(record.english2).includes(String(2)),
          },
          {
            title: 'English Score2',
            dataIndex: 'english2',
            filters: [{ text: '2', value: 3 }],
            onFilter: (record: any) => String(record.english2).includes(String(3)),
          },
        ],
      },
    ];
    const dataSource = [
      {
        key: '1',
        english: 71,
        english1: 71,
        english2: 72,
      },
      {
        key: '2',
        english: 89,
        english1: 72,
        english2: 72,
      },
      {
        key: '3',
        english: 70,
        english1: 71,
        english2: 73,
      },
      {
        key: '4',
        english: 89,
        english1: 71,
        english2: 72,
      },
    ];
    const { container } = render(
      createTable({
        columns,
        dataSource,
      }),
    );

    expect(container.querySelectorAll('.ant-table-filter-column')).toHaveLength(3);
  });

  //  Warning: An update to Item ran an effect, but was not wrapped in act(...).
  it('should pagination.current be 1 after filtering', () => {
    const onChange = jest.fn();
    const columns = [
      {
        title: 'Name',
        dataIndex: 'name',
        filters: [
          {
            text: 'Jim',
            value: 'Jim',
          },
          {
            text: 'Joe',
            value: 'Joe',
          },
        ],
        onFilter: (value: any, record: any) => record.name.indexOf(value) === 0,
        sorter: (a: any, b: any) => a.name.length - b.name.length,
        sortDirections: ['descend'],
      },
    ] as TableProps<any>['columns'];
    const dataSource = [
      {
        key: '1',
        name: 'John Brown',
        age: 32,
        address: 'New York No. 1 Lake Park',
      },
      {
        key: '2',
        name: 'Joe Black',
        age: 32,
        address: 'Sydney No. 1 Lake Park',
      },
    ];

    const { container } = render(
      <Table onChange={onChange} rowKey="name" columns={columns} dataSource={dataSource} />,
    );
    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    fireEvent.click(container.querySelector('.ant-dropdown-menu-item')!);
    fireEvent.click(container.querySelector('.ant-table-filter-dropdown-btns .ant-btn-primary')!);

    expect(onChange.mock.calls[0][0].current).toBe(1);

    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    fireEvent.click(container.querySelectorAll('.ant-dropdown-menu-item')[1]!);
    fireEvent.click(container.querySelector('.ant-table-filter-dropdown-btns .ant-btn-primary')!);
    expect(onChange.mock.calls[1][0].current).toBe(1);
  });

  // https://github.com/ant-design/ant-design/issues/30454
  it('should not trigger onFilterDropdownOpenChange when call confirm({ closeDropdown: false })', () => {
    const onFilterDropdownOpenChange = jest.fn();
    const { container } = render(
      createTable({
        columns: [
          {
            title: 'Name',
            dataIndex: 'name',
            key: 'name',
            filteredValue: name as unknown as FilterValue,
            filterDropdown: ({ confirm }) => (
              <>
                <button id="confirm-and-close" type="button" onClick={() => confirm()}>
                  confirm
                </button>
                <button
                  id="confirm-only"
                  type="button"
                  onClick={() => confirm({ closeDropdown: false })}
                >
                  confirm
                </button>
              </>
            ),
            onFilterDropdownOpenChange,
          },
        ],
      }),
    );

    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    expect(onFilterDropdownOpenChange).toHaveBeenCalledTimes(1);

    fireEvent.click(container.querySelector('#confirm-only')!);
    expect(onFilterDropdownOpenChange).toHaveBeenCalledTimes(1);

    fireEvent.click(container.querySelector('#confirm-and-close')!);
    expect(onFilterDropdownOpenChange).toHaveBeenCalledTimes(2);
    expect(onFilterDropdownOpenChange).toHaveBeenLastCalledWith(false);
  });

  // Warning: An update to Item ran an effect, but was not wrapped in act(...).
  it('Column with filter and children filters properly.', () => {
    const App: React.FC = () => {
      const [filteredInfo, setFilteredInfo] = useState<Record<string, FilterValue | null>>({});
      const [sortedInfo, setSortedInfo] = useState<SorterResult<any> | SorterResult<any>[]>({});
      const handleChange: TableProps<any>['onChange'] = (_, filters, sorter) => {
        setFilteredInfo(filters);
        setSortedInfo(sorter);
      };
      const columns: TableProps['columns'] = [
        {
          title: 'Name',
          dataIndex: 'name',
          key: 'name',
          filters: [
            { text: 'Joe', value: 'Joe' },
            { text: 'Jim', value: 'Jim' },
          ],
          filteredValue: filteredInfo?.name || null,
          onFilter: (value: any, record: any) => record.name.includes(value),
          children: [{ title: 'Age', dataIndex: 'age', key: 'age' }],
        },
        {
          title: 'Age',
          dataIndex: 'age',
          key: 'age',
          sorter: (a: any, b: any) => a.age - b.age,
          sortOrder: (sortedInfo as any)?.columnKey === 'age' && (sortedInfo as any)?.order,
          ellipsis: true,
        },
      ];
      return (
        <Table
          columns={columns}
          onChange={handleChange}
          dataSource={[
            {
              key: '1',
              name: 'John Brown',
              age: 32,
              address: 'New York No. 1 Lake Park',
            },
            {
              key: '2',
              name: 'Jim Green',
              age: 42,
              address: 'London No. 1 Lake Park',
            },
            {
              key: '3',
              name: 'Joe Black',
              age: 66,
              address: 'Sydney No. 1 Lake Park',
            },
            {
              key: '4',
              name: 'Jim Red',
              age: 32,
              address: 'London No. 2 Lake Park',
            },
          ]}
        />
      );
    };

    const { container } = render(<App />);

    expect(container.querySelector('.ant-table-tbody .ant-table-cell')?.textContent).toEqual(
      `${32}`,
    );
    fireEvent.click(container.querySelector('.ant-dropdown-trigger.ant-table-filter-trigger')!);
    fireEvent.click(container.querySelector('.ant-dropdown-menu-item')!);
    fireEvent.click(container.querySelector('.ant-btn.ant-btn-primary.ant-btn-sm')!);
    expect(container.querySelector('.ant-table-tbody .ant-table-cell')?.textContent).toEqual(
      `${66}`,
    );
  });

  it('Columns with filters should filter correctly after reset it.', () => {
    interface DataType {
      key: React.Key;
      name?: string;
      name1?: string;
      age?: number;
      address?: string;
    }

    const columns: ColumnsType<DataType> = [
      {
        title: 'Name',
        dataIndex: 'name',
        filters: [
          {
            text: 'Joe',
            value: 'Joe',
          },
          {
            text: 'Jim',
            value: 'Jim',
          },
          {
            text: 'Submenu',
            value: 'Submenu',
            children: [
              {
                text: 'Green',
                value: 'Green',
              },
              {
                text: 'Black',
                value: 'Black',
              },
            ],
          },
        ],
        // specify the condition of filtering result
        // here is that finding the name started with `value`
        onFilter: (value, record) => record.name?.indexOf(value as string) === 0,
        sorter: (a, b) => a.name!.length - b.name!.length,
        sortDirections: ['descend'],
      },
      {
        title: 'Age',
        dataIndex: 'age',
        defaultSortOrder: 'descend',
        sorter: (a, b) => a.age! - b.age!,
      },
      {
        title: 'Address',
        dataIndex: 'address',
        filters: [
          {
            text: 'London',
            value: 'London',
          },
          {
            text: 'New York',
            value: 'New York',
          },
        ],
        onFilter: (value, record) => record.address?.indexOf(value as string) === 0,
      },
    ];

    const App: React.FC = () => {
      const [ddd, setData] = React.useState<Array<DataType>>([
        {
          key: '1',
          name: 'John Brown',
          age: 32,
          address: 'New York No. 1 Lake Park',
        },
        {
          key: '2',
          name: 'Jim Green',
          age: 42,
          address: 'London No. 1 Lake Park',
        },
        {
          key: '3',
          name: 'Joe Black',
          age: 32,
          address: 'Sydney No. 1 Lake Park',
        },
        {
          key: '4',
          name: 'Jim Red',
          age: 32,
          address: 'London No. 2 Lake Park',
        },
      ]);
      const [cs, setCs] = React.useState(columns);

      const handleClick = () => {
        setCs([
          {
            title: 'name1',
            dataIndex: 'name1',
          },
          {
            title: 'Address',
            dataIndex: 'address',
            filters: [
              {
                text: 'London',
                value: 'London',
              },
              {
                text: 'New York',
                value: 'New York',
              },
            ],
            onFilter: (value, record) => record.address?.indexOf(value as string) === 0,
          },
        ]);
        setData([
          {
            key: '1',
            name1: 'Joe Brown',
            address: 'New York No. 1 Lake Park',
          },
          {
            key: '2',
            name1: 'Jim Green',
            address: 'London No. 1 Lake Park',
          },
          {
            key: '3',
            name1: 'Joe Black',
            address: 'Sydney No. 1 Lake Park',
          },
          {
            key: '4',
            name1: 'Jim Red',
            address: 'London No. 2 Lake Park',
          },
        ]);
      };

      return (
        <div>
          <span className="rest-btn" onClick={handleClick}>
            refresh
          </span>
          <Table columns={cs} dataSource={ddd} />
        </div>
      );
    };

    const { container } = render(<App />);

    expect(container.querySelectorAll('.ant-table-tbody .ant-table-row').length).toEqual(4);
    // Open
    fireEvent.click(container.querySelector('.ant-table-filter-trigger')!);
    function getFilterMenu() {
      return container.querySelector('.ant-table-filter-dropdown');
    }

    const items = getFilterMenu()?.querySelectorAll('li.ant-dropdown-menu-item');
    fireEvent.click(items?.[0]!);
    fireEvent.click(
      getFilterMenu()?.querySelector('.ant-table-filter-dropdown-btns .ant-btn-primary')!,
    );
    refreshTimer();

    expect(container.querySelectorAll('.ant-table-tbody .ant-table-row').length).toEqual(1);

    fireEvent.click(container.querySelector('.rest-btn')!);

    expect(container.querySelectorAll('.ant-table-tbody .ant-table-row').length).toEqual(4);
  });

  describe('filter tree mode', () => {
    it('supports filter tree', () => {
      jest.spyOn(console, 'error').mockImplementation(() => undefined);
      const { container } = render(
        createTable({
          columns: [
            {
              ...column,
              filterMode: 'tree',
            },
          ],
        }),
      );
      fireEvent.click(container.querySelector('span.ant-dropdown-trigger')!, nativeEvent);
      act(() => {
        jest.runAllTimers();
      });
      expect(container.querySelectorAll('.ant-table-filter-dropdown-tree').length).toBe(1);
      expect(container.querySelectorAll('.ant-tree-checkbox').length).toBe(5);
    });

    it('supports search input in filter tree', () => {
      jest.spyOn(console, 'error').mockImplementation(() => undefined);
      const { container } = render(
        createTable({
          columns: [
            {
              ...column,
              filterMode: 'tree',
              filterSearch: true,
            },
          ],
        }),
      );
      fireEvent.click(container.querySelector('span.ant-dropdown-trigger')!, nativeEvent);
      act(() => {
        jest.runAllTimers();
      });
      expect(container.querySelectorAll('.ant-table-filter-dropdown-tree').length).toBe(1);
      expect(container.querySelectorAll('.ant-input').length).toBe(1);
      fireEvent.change(container.querySelector('.ant-input')!, { target: { value: '111' } });
    });

    it('renders empty element when search not found', () => {
      const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => undefined);
      const { container, unmount } = render(
        createTable({
          columns: [
            {
              ...column,
              filters: [
                {
                  text: '123',
                  value: '456',
                },
                {
                  text: 123456,
                  value: '456',
                },
                {
                  text: '456',
                  value: '456',
                },
              ],
              filterSearch: true,
            },
          ],
        }),
      );
      fireEvent.click(container.querySelector('span.ant-dropdown-trigger')!, nativeEvent);
      act(() => {
        jest.runAllTimers();
      });
      expect(container.querySelectorAll('.ant-table-filter-dropdown-search').length).toBe(1);
      expect(container.querySelectorAll('.ant-input').length).toBe(1);
      fireEvent.change(container.querySelector('.ant-input')!, { target: { value: '111' } });
      expect(container.querySelector('.ant-empty')).toBeTruthy();

      unmount();
      errorSpy.mockRestore();
    });

    it('supports search input in filter menu', () => {
      jest.spyOn(console, 'error').mockImplementation(() => undefined);
      const { container } = render(
        createTable({
          columns: [{ ...column, filterSearch: true }],
        }),
      );
      fireEvent.click(container.querySelector('span.ant-dropdown-trigger')!, nativeEvent);
      act(() => {
        jest.runAllTimers();
      });
      expect(container.querySelectorAll('.ant-table-filter-dropdown-search').length).toBe(1);
      expect(container.querySelectorAll('.ant-input').length).toBe(1);
      fireEvent.change(container.querySelector('.ant-input')!, { target: { value: '111' } });
    });

    it('should skip search when filters[0].text is ReactNode', () => {
      jest.spyOn(console, 'error').mockImplementation(() => undefined);
      const { container, unmount } = render(
        createTable({
          columns: [
            {
              ...column,
              filters: [
                {
                  text: '123',
                  value: '456',
                },
                {
                  text: 123456,
                  value: '456',
                },
                {
                  text: <span>123</span>,
                  value: '456',
                },
              ],
              filterSearch: true,
            },
          ],
        }),
      );

      fireEvent.click(container.querySelector('span.ant-dropdown-trigger')!, nativeEvent);
      act(() => {
        jest.runAllTimers();
      });
      expect(container.querySelectorAll('.ant-table-filter-dropdown-search').length).toBe(1);
      expect(container.querySelectorAll('.ant-input').length).toBe(1);
      expect(container.querySelectorAll('li.ant-dropdown-menu-item').length).toBe(3);
      fireEvent.change(container.querySelector('.ant-input')!, { target: { value: '123' } });
      expect(container.querySelectorAll('li.ant-dropdown-menu-item').length).toBe(2);

      unmount();
    });

    it('should supports filterSearch has type of function', () => {
      const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => undefined);
      const { container, unmount } = render(
        createTable({
          columns: [
            {
              ...column,
              filters: [
                { text: '123', value: '123' },
                { text: 123456, value: '456' },
                { text: <span>123</span>, value: '456' },
              ],
              filterSearch: (input: any, record: any) => record.value.includes(input),
            },
          ],
        }),
      );
      fireEvent.click(container.querySelector('span.ant-dropdown-trigger')!, nativeEvent);
      act(() => {
        jest.runAllTimers();
      });
      expect(container.querySelectorAll('.ant-table-filter-dropdown-search').length).toBe(1);
      expect(container.querySelectorAll('.ant-input').length).toBe(1);
      expect(container.querySelectorAll('li.ant-dropdown-menu-item').length).toBe(3);
      fireEvent.change(container.querySelector('.ant-input')!, { target: { value: '456' } });
      expect(container.querySelectorAll('li.ant-dropdown-menu-item').length).toBe(2);

      unmount();
      errorSpy.mockRestore();
    });

    it('should supports filterSearch has type of function when filterMode is tree', () => {
      jest.spyOn(console, 'error').mockImplementation(() => undefined);
      const { container } = render(
        createTable({
          columns: [
            {
              ...column,
              filterMode: 'tree',
              filters: [
                { text: '节点一', value: 'node1' },
                { text: '节点二', value: 'node2' },
                { text: '节点三', value: 'node3' },
              ],
              filterSearch: (input, record) => ((record as any).title as string).includes(input),
            },
          ],
        }),
      );
      fireEvent.click(container.querySelector('span.ant-dropdown-trigger')!, nativeEvent);
      act(() => {
        jest.runAllTimers();
      });
      expect(container.querySelectorAll('.ant-table-filter-dropdown-tree').length).toBe(1);
      expect(container.querySelectorAll('.ant-input').length).toBe(1);
      fireEvent.change(container.querySelector('.ant-input')!, { target: { value: '节点二' } });
      expect(container.querySelectorAll('.ant-tree-treenode.filter-node').length).toBe(1);
    });

    it('supports check all items', () => {
      jest.spyOn(console, 'error').mockImplementation(() => undefined);
      const { container } = render(
        createTable({
          columns: [{ ...column, filterMode: 'tree', filterSearch: true }],
        }),
      );
      fireEvent.click(container.querySelector('span.ant-dropdown-trigger')!, nativeEvent);
      act(() => {
        jest.runAllTimers();
      });
      expect(container.querySelectorAll('.ant-table-filter-dropdown-checkall').length).toBe(1);
      expect(container.querySelector('.ant-table-filter-dropdown-checkall')?.textContent).toBe(
        'Select all items',
      );
      expect(container.querySelectorAll('.ant-tree-checkbox-checked').length).toBe(0);

      // fireEvent.change(container.querySelector('.ant-table-filter-dropdown-checkall input'), { target: { checked: true } });
      // 为什么 fireEvent.change 模拟 checkbox 触发会失败
      fireEvent.click(container.querySelector('.ant-table-filter-dropdown-checkall')!);
      expect(container.querySelectorAll('.ant-tree-checkbox-checked').length).toBe(5);

      fireEvent.click(container.querySelector('.ant-table-filter-dropdown-checkall')!);
      expect(container.querySelectorAll('.ant-tree-checkbox-checked').length).toBe(0);
    });

    it('supports check item by selecting it', () => {
      jest.spyOn(console, 'error').mockImplementation(() => undefined);
      const { container } = render(
        createTable({
          columns: [
            {
              ...column,
              filterMode: 'tree',
              filterSearch: true,
            },
          ],
        }),
      );
      fireEvent.click(container.querySelector('span.ant-dropdown-trigger')!, nativeEvent);
      act(() => {
        jest.runAllTimers();
      });
      expect(container.querySelectorAll('.ant-table-filter-dropdown-checkall').length).toBe(1);
      expect(container.querySelector('.ant-table-filter-dropdown-checkall')?.textContent).toBe(
        'Select all items',
      );
      fireEvent.click(container.querySelector('.ant-tree-node-content-wrapper')!);

      expect(
        container
          ?.querySelector('.ant-tree-checkbox')
          ?.className.includes('ant-tree-checkbox-checked'),
      ).toBe(true);
      expect(
        container
          ?.querySelector('.ant-table-filter-dropdown-checkall .ant-checkbox')
          ?.className.includes('ant-checkbox-indeterminate'),
      ).toBe(true);
    });

    it('select-all checkbox should change when all items are selected', () => {
      jest.spyOn(console, 'error').mockImplementation(() => undefined);
      const { container } = render(
        createTable({
          columns: [
            {
              ...column,
              filterMode: 'tree',
              filters: [
                { text: 'Boy', value: 'boy' },
                { text: 'Girl', value: 'girl' },
              ],
            },
          ],
        }),
      );
      fireEvent.click(container.querySelector('span.ant-dropdown-trigger')!, nativeEvent);
      act(() => {
        jest.runAllTimers();
      });
      fireEvent.click(container.querySelectorAll('.ant-tree-node-content-wrapper')[0]);
      fireEvent.click(container.querySelectorAll('.ant-tree-node-content-wrapper')[1]);

      expect(
        container
          ?.querySelector('.ant-table-filter-dropdown-checkall .ant-checkbox')
          ?.className.includes('ant-checkbox-checked'),
      ).toBe(true);
    });
  });

  it('filterMultiple is false - check item', () => {
    jest.spyOn(console, 'error').mockImplementation(() => undefined);
    const { container } = render(
      createTable({
        columns: [{ ...column, filterMode: 'tree', filterMultiple: false }],
      }),
    );

    fireEvent.click(container.querySelector('span.ant-dropdown-trigger')!, nativeEvent);
    act(() => {
      jest.runAllTimers();
    });
    expect(container.querySelectorAll('.ant-tree-checkbox').length).toBe(5);
    expect(container.querySelector('.ant-table-filter-dropdown-checkall')).toBe(null);
    expect(container.querySelectorAll('.ant-tree-checkbox-checked').length).toBe(0);

    fireEvent.click(container.querySelectorAll('.ant-tree-checkbox')[2]);
    expect(
      container
        .querySelectorAll('.ant-tree-checkbox')[2]
        .className.includes('ant-tree-checkbox-checked'),
    ).toBe(true);
    expect(container.querySelectorAll('.ant-tree-checkbox-checked').length).toBe(1);

    fireEvent.click(container.querySelectorAll('.ant-tree-checkbox')[1]);
    expect(
      container
        .querySelectorAll('.ant-tree-checkbox')[1]
        .className.includes('ant-tree-checkbox-checked'),
    ).toBe(true);
    expect(container.querySelectorAll('.ant-tree-checkbox-checked').length).toBe(1);

    fireEvent.click(container.querySelectorAll('.ant-tree-checkbox')[1]);
    expect(
      container
        .querySelectorAll('.ant-tree-checkbox')[1]
        .className.includes('ant-tree-checkbox-checked'),
    ).toBe(false);
    expect(container.querySelectorAll('.ant-tree-checkbox-checked').length).toBe(0);
  });

  it('filterMultiple is false - select item', () => {
    jest.spyOn(console, 'error').mockImplementation(() => undefined);
    const { container } = render(
      createTable({
        columns: [
          {
            ...column,
            filterMode: 'tree',
            filterMultiple: false,
          },
        ],
      }),
    );
    fireEvent.click(container.querySelector('span.ant-dropdown-trigger')!, nativeEvent);
    act(() => {
      jest.runAllTimers();
    });

    expect(container.querySelectorAll('.ant-tree-checkbox').length).toBe(5);
    expect(container.querySelectorAll('.ant-tree-checkbox-checked').length).toBe(0);

    fireEvent.click(container.querySelectorAll('.ant-tree-node-content-wrapper')[2]);
    expect(
      container
        .querySelectorAll('.ant-tree-checkbox')[2]
        .className.includes('ant-tree-checkbox-checked'),
    ).toBe(true);
    expect(container.querySelectorAll('.ant-tree-checkbox-checked').length).toBe(1);

    fireEvent.click(container.querySelectorAll('.ant-tree-node-content-wrapper')[1]);
    expect(
      container
        .querySelectorAll('.ant-tree-checkbox')[1]
        .className.includes('ant-tree-checkbox-checked'),
    ).toBe(true);
    expect(container.querySelectorAll('.ant-tree-checkbox-checked').length).toBe(1);

    fireEvent.click(container.querySelectorAll('.ant-tree-node-content-wrapper')[1]);
    expect(
      container
        .querySelectorAll('.ant-tree-checkbox')[1]
        .className.includes('ant-tree-checkbox-checked'),
    ).toBe(false);
    expect(container.querySelectorAll('.ant-tree-checkbox-checked').length).toBe(0);
  });

  it('should select children when select parent', () => {
    jest.spyOn(console, 'error').mockImplementation(() => undefined);
    const { container } = render(
      createTable({
        columns: [
          {
            ...column,
            filters: [
              { text: 'Boy', value: 'boy' },
              { text: 'Girl', value: 'girl' },
              {
                text: 'Title',
                value: 'title',
                children: [
                  { text: 'Jack', value: 'Jack' },
                  { text: 'Coder', value: 'coder' },
                ],
              },
            ],
            filterMode: 'tree',
          },
        ],
      }),
    );
    fireEvent.click(container.querySelector('span.ant-dropdown-trigger')!, nativeEvent);
    act(() => {
      jest.runAllTimers();
    });
    // check parentnode

    fireEvent.click(container.querySelectorAll('.ant-tree-checkbox')[2]);

    expect(
      container
        .querySelectorAll('.ant-tree-checkbox')[2]
        .className.includes('ant-tree-checkbox-checked'),
    ).toBe(true);
    expect(
      container
        .querySelectorAll('.ant-tree-checkbox')[3]
        .className.includes('ant-tree-checkbox-checked'),
    ).toBe(true);
    expect(
      container
        .querySelectorAll('.ant-tree-checkbox')[4]
        .className.includes('ant-tree-checkbox-checked'),
    ).toBe(true);

    fireEvent.click(container.querySelector('.ant-table-filter-dropdown-btns .ant-btn-primary')!);
    expect(renderedNames(container)).toEqual(['Jack']);

    fireEvent.click(container.querySelector('span.ant-dropdown-trigger')!, nativeEvent);
    act(() => {
      jest.runAllTimers();
    });

    fireEvent.click(container.querySelectorAll('.ant-tree-checkbox-inner')[2]);
    fireEvent.click(container.querySelector('.ant-table-filter-dropdown-btns .ant-btn-primary')!);
    expect(renderedNames(container)).toEqual(['Jack', 'Lucy', 'Tom', 'Jerry']);

    fireEvent.click(container.querySelector('span.ant-dropdown-trigger')!, nativeEvent);
    act(() => {
      jest.runAllTimers();
    });

    fireEvent.click(container.querySelectorAll('.ant-tree-node-content-wrapper')[2]);
    fireEvent.click(container.querySelector('.ant-table-filter-dropdown-btns .ant-btn-primary')!);
    expect(renderedNames(container)).toEqual(['Jack']);
  });

  it('clearFilters should support params', () => {
    const filterConfig = [
      ['Jack', 'NoParams', {}, ['Jack'], true],
      ['Lucy', 'Confirm', { confirm: true }, ['Jack', 'Lucy', 'Tom', 'Jerry'], true],
      ['Tom', 'Close', { closeDropdown: true }, ['Tom'], false],
      [
        'Jerry',
        'Params',
        { closeDropdown: true, confirm: true },
        ['Jack', 'Lucy', 'Tom', 'Jerry'],
        false,
      ],
    ];

    let renderSelectedKeys;
    const filter = ({
      prefixCls,
      setSelectedKeys,
      selectedKeys,
      confirm,
      clearFilters,
    }: FilterDropdownProps): React.ReactNode => {
      renderSelectedKeys = selectedKeys;

      return (
        <div className={`${prefixCls}-view`} id="customFilter">
          {filterConfig.map(([text, id, param]) => (
            <>
              <span
                onClick={() => {
                  setSelectedKeys([text as React.Key]);
                  confirm();
                }}
                id={`set${id}`}
              >
                setSelectedKeys
              </span>
              <span onClick={() => (clearFilters as any)?.(param)} id={`reset${id}`}>
                Reset
              </span>
            </>
          ))}
        </div>
      );
    };

    const { container } = render(
      createTable({
        columns: [
          {
            ...column,
            filterDropdown: filter,
          },
        ],
      }),
    );

    // check if renderer well
    fireEvent.click(container.querySelector('span.ant-dropdown-trigger')!);
    expect(container.querySelector('#customFilter')).toMatchSnapshot();
    expect(renderSelectedKeys).toHaveLength(0);

    filterConfig.forEach(([text, id, , matchNames, visible]) => {
      fireEvent.click(container.querySelector(`#set${id}`)!);
      expect(renderedNames(container)).toEqual([text]);

      fireEvent.click(container.querySelector('span.ant-dropdown-trigger')!);
      fireEvent.click(container.querySelector(`#reset${id}`)!);
      expect(renderedNames(container)).toEqual(matchNames);

      expect(container.querySelector('.ant-dropdown-open'))[visible ? 'toBeTruthy' : 'toBeFalsy']();
    });
  });

  it('filterDropdown should support filterResetToDefaultFilteredValue', () => {
    jest.spyOn(console, 'error').mockImplementation(() => undefined);

    const columnFilter: ColumnGroupType<any> | ColumnType<any> = {
      ...column,
      filterMode: 'tree',
      filterSearch: true,
      defaultFilteredValue: ['girl'],
    };

    const { container } = render(
      createTable({
        columns: [columnFilter],
      }),
    );
    fireEvent.click(container.querySelector('span.ant-dropdown-trigger')!, nativeEvent);
    act(() => {
      jest.runAllTimers();
    });
    expect(container.querySelectorAll('.ant-tree-checkbox-checked').length).toBe(1);

    fireEvent.click(container.querySelector('.ant-table-filter-dropdown-checkall')!);
    expect(container.querySelectorAll('.ant-tree-checkbox-checked').length).toBe(5);
    fireEvent.click(container.querySelector('button.ant-btn-link')!, nativeEvent);
    expect(container.querySelectorAll('.ant-tree-checkbox-checked').length).toBe(0);

    const { container: container2 } = render(
      createTable({
        columns: [
          {
            ...columnFilter,
            filterResetToDefaultFilteredValue: true,
          },
        ],
      }),
    );

    fireEvent.click(container2.querySelector('span.ant-dropdown-trigger')!, nativeEvent);
    act(() => {
      jest.runAllTimers();
    });
    fireEvent.click(container2.querySelector('.ant-table-filter-dropdown-checkall')!);
    expect(container2.querySelectorAll('.ant-tree-checkbox-checked').length).toBe(5);
    fireEvent.click(container2.querySelector('button.ant-btn-link')!, nativeEvent);
    expect(container2.querySelectorAll('.ant-tree-checkbox-checked').length).toBe(1);
    expect(container2.querySelector('.ant-tree-checkbox-checked+span')?.textContent).toBe('Girl');
  });

  it('filterDropdown should not override customize Menu selectable', () => {
    const onSelect = jest.fn();

    const { container } = render(
      createTable({
        columns: [
          {
            ...column,
            filterDropdown: (
              <div className="custom-filter-dropdown">
                <Menu
                  onSelect={onSelect}
                  items={[
                    {
                      key: '1',
                      label: 'Item 1',
                    },
                  ]}
                />
              </div>
            ),
          },
        ],
      }),
    );

    // Open Filter
    fireEvent.click(container.querySelector('span.ant-dropdown-trigger')!);
    act(() => {
      jest.runAllTimers();
    });

    // Click Item
    fireEvent.click(container.querySelector('.ant-table-filter-dropdown .ant-dropdown-menu-item')!);

    expect(onSelect).toHaveBeenCalled();
  });

  describe('filteredKeys should all be controlled or not controlled', () => {
    let errorSpy: jest.SpyInstance;

    beforeEach(() => {
      resetWarned();
      errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
      errorSpy.mockReset();
    });

    afterEach(() => {
      errorSpy.mockRestore();
    });

    const tableData = [
      {
        key: '1',
        name: 'John Brown',
        age: 32,
      },
    ];
    const getColumns = () => [
      {
        title: 'name',
        dataIndex: 'name',
        key: 'name',
        filters: [],
      },
      {
        title: 'age',
        dataIndex: 'age',
        key: 'age',
        filters: [],
      },
    ];

    it('all uncontrolled', () => {
      render(
        createTable({
          columns: getColumns(),
          data: tableData,
        } as TableProps<any>),
      );
      expect(errorSpy).not.toHaveBeenCalled();
    });

    it('part controlled', () => {
      const columns = getColumns();
      (columns[0] as any).filteredValue = [];
      render(
        createTable({
          columns,
          data: tableData,
        } as TableProps<any>),
      );
      expect(errorSpy).toHaveBeenCalledWith(
        'Warning: [antd: Table] Columns should all contain `filteredValue` or not contain `filteredValue`.',
      );
    });

    it('all controlled', () => {
      const columns = getColumns();
      (columns[0] as any).filteredValue = [];
      (columns[1] as any).filteredValue = [];
      render(
        createTable({
          columns,
          data: tableData,
        } as TableProps<any>),
      );
      expect(errorSpy).not.toHaveBeenCalled();
    });
  });

  // Warning: An update to Item ran an effect, but was not wrapped in act(...).
  it('can reset if filterResetToDefaultFilteredValue and filter is changing', () => {
    const { container } = render(
      createTable({
        columns: [
          {
            ...column,
            filters: [
              { text: 'Jack', value: 'Jack' },
              { text: 'Lucy', value: 'Lucy' },
            ],
            defaultFilteredValue: ['Jack'],
            filterResetToDefaultFilteredValue: true,
          },
        ],
      }),
    );
    expect(container.querySelectorAll('tbody tr').length).toBe(1);
    expect(container.querySelector('tbody tr')?.textContent).toBe('Jack');

    // open filter

    fireEvent.click(container.querySelector('span.ant-dropdown-trigger')!);
    expect(
      container.querySelector<HTMLLinkElement>('.ant-table-filter-dropdown-btns .ant-btn-link')
        ?.disabled,
    ).toBeTruthy();
    expect(container.querySelectorAll('li.ant-dropdown-menu-item')[0].textContent).toBe('Jack');
    expect(container.querySelectorAll('li.ant-dropdown-menu-item')[1].textContent).toBe('Lucy');

    // deselect default
    fireEvent.click(container.querySelectorAll('li.ant-dropdown-menu-item')[0]);
    expect(
      container.querySelector<HTMLLinkElement>('.ant-table-filter-dropdown-btns .ant-btn-link')
        ?.disabled,
    ).toBeFalsy();
    // select other one
    fireEvent.click(container.querySelectorAll('li.ant-dropdown-menu-item')[1]);
    expect(
      container.querySelector<HTMLLinkElement>('.ant-table-filter-dropdown-btns .ant-btn-link')
        ?.disabled,
    ).toBeFalsy();
    // deselect other one
    fireEvent.click(container.querySelectorAll('li.ant-dropdown-menu-item')[1]);
    expect(
      container.querySelector<HTMLLinkElement>('.ant-table-filter-dropdown-btns .ant-btn-link')
        ?.disabled,
    ).toBeFalsy();
    // select default
    fireEvent.click(container.querySelectorAll('li.ant-dropdown-menu-item')[0]);
    expect(
      container.querySelector<HTMLLinkElement>('.ant-table-filter-dropdown-btns .ant-btn-link')
        ?.disabled,
    ).toBeTruthy();
  });

  it('title render function support `filter`', () => {
    const title = jest.fn(() => 'RenderTitle');
    const { container } = render(
      createTable({
        columns: [
          {
            ...column,
            title,
            filteredValue: ['boy'],
          },
        ],
      }),
    );

    expect(container.querySelector('.ant-table-column-title')?.textContent).toEqual('RenderTitle');
    expect(title).toHaveBeenCalledWith(
      expect.objectContaining({
        filters: { name: ['boy'] },
      }),
    );
  });
  it('should be hidden and not commit when call close()', () => {
    const onFilterDropdownOpenChange = jest.fn();
    const onFilter = jest.fn();
    const { container } = render(
      createTable({
        columns: [
          {
            title: 'Name',
            dataIndex: 'name',
            key: 'name',
            filteredValue: name as unknown as FilterValue,
            filterDropdown: ({ close }) => (
              <button id="close-only" type="button" onClick={() => close()}>
                close
              </button>
            ),
            onFilterDropdownOpenChange,
            onFilter,
          },
        ],
      }),
    );

    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    expect(onFilterDropdownOpenChange).toHaveBeenCalledTimes(1);

    fireEvent.click(container.querySelector('#close-only')!);
    expect(onFilterDropdownOpenChange).toHaveBeenCalledTimes(2);
    expect(onFilter).toHaveBeenCalledTimes(0);
  });

  it('works with grouping columns correctly', () => {
    const columns = [
      {
        title: 'group',
        key: 'group',
        children: [
          {
            title: 'Name',
            dataIndex: 'name',
            key: 'name',
            filters: [
              { text: 'Jack', value: 'Jack' },
              { text: 'Lucy', value: 'Lucy' },
            ],
            onFilter: filterFn,
          },
          {
            title: 'Age',
            dataIndex: 'age',
            key: 'age',
          },
        ],
      },
    ];
    const testData = [
      { key: 0, name: 'Jack', age: 11 },
      { key: 1, name: 'Lucy', age: 20 },
      { key: 2, name: 'Tom', age: 21 },
      { key: 3, name: 'Jerry', age: 22 },
    ];
    const { container } = render(<Table columns={columns} dataSource={testData} />);

    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    fireEvent.click(container.querySelectorAll('.ant-dropdown-menu-item')[0]);
    fireEvent.click(container.querySelector('.ant-table-filter-dropdown-btns .ant-btn-primary')!);

    expect(renderedNames(container)).toEqual(['Jack']);
  });

  it('changes to table data should not reset the filter dropdown state being changed by a user', () => {
    const tableProps = {
      key: 'stabletable',
      rowKey: 'name',
      dataSource: [],
      columns: [
        {
          title: 'Name',
          dataIndex: 'name',
          filteredValue: [], // User is controlling filteredValue. It begins with no items checked.
          filters: [{ text: 'J', value: 'J' }],
          onFilter: (value: any, record: any) => record.name.includes(value),
        },
      ],
    };

    const { container, rerender } = render(createTable(tableProps));

    // User opens filter Dropdown.
    fireEvent.click(container.querySelector('.ant-dropdown-trigger.ant-table-filter-trigger')!);

    // There is one checkbox and it begins unchecked.
    expect(container.querySelector<HTMLInputElement>('input[type="checkbox"]')!.checked).toEqual(
      false,
    );

    // User checks it.
    fireEvent.click(container.querySelector('input[type="checkbox"]')!);

    // The checkbox is now checked.
    expect(container.querySelector<HTMLInputElement>('input[type="checkbox"]')!.checked).toEqual(
      true,
    );

    // Table data changes while the dropdown is open and a user is setting filters.
    rerender(createTable({ ...tableProps, dataSource: [{ name: 'Foo' }] }));

    // The checkbox is still checked.
    expect(container.querySelector<HTMLInputElement>('input[type="checkbox"]')!.checked).toEqual(
      true,
    );
  });

  it('should not crash when filterDropdown is boolean', () => {
    const tableProps = {
      key: 'stabletable',
      rowKey: 'name',
      dataSource: [],
      columns: [
        {
          title: 'Name',
          dataIndex: 'name',
          filterDropdown: true,
        },
      ],
    };

    const { container } = render(createTable(tableProps));

    // User opens filter Dropdown.
    fireEvent.click(container.querySelector('.ant-dropdown-trigger.ant-table-filter-trigger')!);
  });

  it('should not fire change event when dropdown dismisses if filterOnClose is false', () => {
    const handleChange = jest.fn();
    const { container } = render(
      createTable({
        onChange: handleChange,
        columns: [
          {
            ...column,
            filterOnClose: false,
          },
        ],
      }),
    );
    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    fireEvent.click(container.querySelectorAll('.ant-dropdown-menu-item')[0]);
    fireEvent.click(container.querySelector('.ant-dropdown-trigger')!);
    expect(handleChange).not.toHaveBeenCalled();
  });
});