diff --git a/examples/dynamic-options.jsx b/examples/dynamic-options.jsx index 8c1c2872..9cc98769 100644 --- a/examples/dynamic-options.jsx +++ b/examples/dynamic-options.jsx @@ -40,6 +40,7 @@ class Demo extends React.Component { { label: `${targetOption.label}动态加载1`, value: 'dynamic1', + isLeaf: false, }, { label: `${targetOption.label}动态加载2`, @@ -50,7 +51,7 @@ class Demo extends React.Component { // eslint-disable-next-line react/no-access-state-in-setstate options: [...this.state.options], }); - }, 1000); + }, 500); }; render() { diff --git a/src/OptionList/index.tsx b/src/OptionList/index.tsx index 11df8ba6..cb93fe86 100644 --- a/src/OptionList/index.tsx +++ b/src/OptionList/index.tsx @@ -53,7 +53,7 @@ const RefOptionList = React.forwardRef((pro const { options: optionList } = restoreCompatibleValue(entity as any, fieldNames); const rawOptionList = optionList.map(opt => opt.node); - setLoadingKeys(keys => [...keys, optionList[optionList.length - 1].value]); + setLoadingKeys(keys => [...keys, entity.key]); loadData(rawOptionList); } @@ -64,7 +64,7 @@ const RefOptionList = React.forwardRef((pro if (loadingKeys.length) { loadingKeys.forEach(loadingKey => { const option = flattenOptions.find(opt => opt.value === loadingKey); - if (option.data.children || option.data.isLeaf === true) { + if (!option || option.data.children || option.data.isLeaf === true) { setLoadingKeys(keys => keys.filter(key => key !== loadingKey)); } }); diff --git a/tests/index.spec.tsx b/tests/index.spec.tsx index 54b2a66b..b1f4c84d 100644 --- a/tests/index.spec.tsx +++ b/tests/index.spec.tsx @@ -1,7 +1,6 @@ /* eslint-disable react/jsx-no-bind */ import React from 'react'; -import { act } from 'react-dom/test-utils'; import { resetWarned } from 'rc-util/lib/warning'; import { spyElementPrototypes } from 'rc-util/lib/test/domHook'; import { mount } from './enzyme'; @@ -511,136 +510,6 @@ describe('Cascader.Basic', () => { expect(wrapper.isOpen()).toBeFalsy(); }); - describe('loadData', () => { - it('basic load', () => { - const loadData = jest.fn(); - const wrapper = mount( - } - options={[ - { - label: 'Bamboo', - value: 'bamboo', - isLeaf: false, - }, - ]} - loadData={loadData} - open - />, - ); - - wrapper.find('.rc-cascader-menu-item-content').first().simulate('click'); - expect(wrapper.exists('.loading-icon')).toBeTruthy(); - expect(loadData).toHaveBeenCalledWith([ - expect.objectContaining({ - value: 'bamboo', - }), - ]); - - expect(wrapper.exists('.rc-cascader-menu-item-loading')).toBeTruthy(); - expect(wrapper.exists('.rc-cascader-menu-item-loading-icon')).toBeTruthy(); - - // Fill data - wrapper.setProps({ - options: [ - { - label: 'Bamboo', - value: 'bamboo', - isLeaf: false, - children: [], - }, - ], - }); - wrapper.update(); - expect(wrapper.exists('.loading-icon')).toBeFalsy(); - }); - - it('not load leaf', () => { - const loadData = jest.fn(); - const onValueChange = jest.fn(); - const wrapper = mount( - , - ); - - wrapper.clickOption(0, 0); - expect(onValueChange).toHaveBeenCalled(); - expect(loadData).not.toHaveBeenCalled(); - }); - - // https://github.com/ant-design/ant-design/issues/9084 - it('should trigger loadData when expandTrigger is hover', () => { - const options = [ - { - value: 'zhejiang', - label: 'Zhejiang', - isLeaf: false, - }, - { - value: 'jiangsu', - label: 'Jiangsu', - isLeaf: false, - }, - ]; - const loadData = jest.fn(); - const wrapper = mount( - - - , - ); - wrapper.find('input').simulate('click'); - const menus = wrapper.find('.rc-cascader-menu'); - const menu1Items = menus.at(0).find('.rc-cascader-menu-item'); - menu1Items.at(0).simulate('mouseEnter'); - jest.runAllTimers(); - expect(loadData).toHaveBeenCalled(); - }); - - it('change isLeaf back to true should not loop loading', async () => { - const Demo = () => { - const [options, setOptions] = React.useState([ - { value: 'zhejiang', label: 'Zhejiang', isLeaf: false }, - ]); - - const loadData = () => { - Promise.resolve().then(() => { - act(() => { - setOptions([ - { - value: 'zhejiang', - label: 'Zhejiang', - isLeaf: true, - }, - ]); - }); - }); - }; - - return ; - }; - - const wrapper = mount(); - wrapper.find('.rc-cascader-menu-item-content').first().simulate('click'); - expect(wrapper.exists('.rc-cascader-menu-item-loading')).toBeTruthy(); - - for (let i = 0; i < 3; i += 1) { - await Promise.resolve(); - } - wrapper.update(); - - expect(wrapper.exists('.rc-cascader-menu-item-loading')).toBeFalsy(); - }); - }); - // https://github.com/ant-design/ant-design/issues/9793 it('should not trigger onBlur and onFocus when select item', () => { // This function is handled by `rc-select` instead diff --git a/tests/loadData.spec.tsx b/tests/loadData.spec.tsx new file mode 100644 index 00000000..e7bb5522 --- /dev/null +++ b/tests/loadData.spec.tsx @@ -0,0 +1,185 @@ +/* eslint-disable react/jsx-no-bind */ + +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { mount } from './enzyme'; +import Cascader from '../src'; + +describe('Cascader.LoadData', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('basic load', () => { + const loadData = jest.fn(); + const wrapper = mount( + } + options={[ + { + label: 'Bamboo', + value: 'bamboo', + isLeaf: false, + }, + ]} + loadData={loadData} + open + />, + ); + + wrapper.find('.rc-cascader-menu-item-content').first().simulate('click'); + expect(wrapper.exists('.loading-icon')).toBeTruthy(); + expect(loadData).toHaveBeenCalledWith([ + expect.objectContaining({ + value: 'bamboo', + }), + ]); + + expect(wrapper.exists('.rc-cascader-menu-item-loading')).toBeTruthy(); + expect(wrapper.exists('.rc-cascader-menu-item-loading-icon')).toBeTruthy(); + + // Fill data + wrapper.setProps({ + options: [ + { + label: 'Bamboo', + value: 'bamboo', + isLeaf: false, + children: [], + }, + ], + }); + wrapper.update(); + expect(wrapper.exists('.loading-icon')).toBeFalsy(); + }); + + it('not load leaf', () => { + const loadData = jest.fn(); + const onValueChange = jest.fn(); + const wrapper = mount( + , + ); + + wrapper.clickOption(0, 0); + expect(onValueChange).toHaveBeenCalled(); + expect(loadData).not.toHaveBeenCalled(); + }); + + // https://github.com/ant-design/ant-design/issues/9084 + it('should trigger loadData when expandTrigger is hover', () => { + const options = [ + { + value: 'zhejiang', + label: 'Zhejiang', + isLeaf: false, + }, + { + value: 'jiangsu', + label: 'Jiangsu', + isLeaf: false, + }, + ]; + const loadData = jest.fn(); + const wrapper = mount( + + + , + ); + wrapper.find('input').simulate('click'); + const menus = wrapper.find('.rc-cascader-menu'); + const menu1Items = menus.at(0).find('.rc-cascader-menu-item'); + menu1Items.at(0).simulate('mouseEnter'); + jest.runAllTimers(); + expect(loadData).toHaveBeenCalled(); + }); + + it('change isLeaf back to true should not loop loading', async () => { + const Demo = () => { + const [options, setOptions] = React.useState([ + { value: 'zhejiang', label: 'Zhejiang', isLeaf: false }, + ]); + + const loadData = () => { + Promise.resolve().then(() => { + act(() => { + setOptions([ + { + value: 'zhejiang', + label: 'Zhejiang', + isLeaf: true, + }, + ]); + }); + }); + }; + + return ; + }; + + const wrapper = mount(); + wrapper.find('.rc-cascader-menu-item-content').first().simulate('click'); + expect(wrapper.exists('.rc-cascader-menu-item-loading')).toBeTruthy(); + + for (let i = 0; i < 3; i += 1) { + await Promise.resolve(); + } + wrapper.update(); + + expect(wrapper.exists('.rc-cascader-menu-item-loading')).toBeFalsy(); + }); + + it('nest load should not crash', async () => { + const Demo = () => { + const [options, setOptions] = React.useState([{ label: 'top', value: 'top', isLeaf: false }]); + + const loadData = selectedOptions => { + Promise.resolve().then(() => { + act(() => { + selectedOptions[selectedOptions.length - 1].children = [ + { + label: 'child', + value: 'child', + isLeaf: false, + }, + ]; + setOptions(list => [...list]); + }); + }); + }; + + return ; + }; + + const wrapper = mount(); + + // First column click + wrapper.find('.rc-cascader-menu-item-content').last().simulate('click'); + for (let i = 0; i < 3; i += 1) { + await Promise.resolve(); + } + wrapper.update(); + + // Second column click + wrapper.find('.rc-cascader-menu-item-content').last().simulate('click'); + for (let i = 0; i < 3; i += 1) { + await Promise.resolve(); + } + wrapper.update(); + + expect(wrapper.find('ul.rc-cascader-menu')).toHaveLength(3); + }); +});