Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 70 additions & 30 deletions packages/components/select/__tests__/select.test.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/* eslint-disable */
import { render, fireEvent, mockTimeout, mockDelay, act } from '@test/utils';
import React, { useState } from 'react';
import { act, fireEvent, mockTimeout, render } from '@test/utils';
import userEvent from '@testing-library/user-event';

import { Select, SelectProps } from '../index';
import Popup from '../../popup';
import Space from '../../space';
import Tag from '../../tag';
import { Select, SelectProps } from '../index';

const { Option, OptionGroup } = Select;

Expand All @@ -17,6 +17,7 @@ describe('Select 组件测试', () => {
{
label: 'Apple',
value: 'apple',
disabled: true,
},
{
label: 'Banana',
Expand Down Expand Up @@ -189,7 +190,7 @@ describe('Select 组件测试', () => {
return (
<Select value={value} onChange={onChange} multiple>
<Option key="all" label="All" value="all" checkAll />
<Option key="apple" label="Apple" value="apple" />
<Option key="apple" label="Apple" value="apple" disabled />
<Option key="orange" label="Orange" value="orange" />
<Option key="banana" label="Banana" value="banana" />
</Select>
Expand All @@ -200,15 +201,18 @@ describe('Select 组件测试', () => {

fireEvent.click(document.querySelector('.t-input'));

const disabledApple = document.querySelector('.t-select-option.t-is-disabled');
expect(disabledApple).toHaveTextContent('Apple');

// 点击全选,input 展示 Apple、Banana、Orange 选项
fireEvent.click(getByText('All'));
expect(document.querySelector(selectSelector)).toHaveTextContent('Apple');
expect(document.querySelector(selectSelector)).toHaveTextContent('Banana');
expect(document.querySelector(selectSelector)).toHaveTextContent('Orange');

// 再次点击全选,input 清空选项
// 再次点击全选,input 清空选项(保留原本禁用项)
fireEvent.click(getByText('All'));
expect(document.querySelector(selectSelector)).not.toHaveTextContent('Apple');
expect(document.querySelector(selectSelector)).toHaveTextContent('Apple');
expect(document.querySelector(selectSelector)).not.toHaveTextContent('Banana');
expect(document.querySelector(selectSelector)).not.toHaveTextContent('Orange');
});
Expand Down Expand Up @@ -256,6 +260,41 @@ describe('Select 组件测试', () => {
);
});

test('分组选择器全选测试', async () => {
const OptionGroupCheckAllSelect = () => {
const [value, setValue] = useState(['apple']);
const onChange = (value) => {
setValue(value);
};

return (
<Select value={value} onChange={onChange} multiple>
<Option key="all" label="All" value="all" checkAll />
<OptionGroup label="Fruit">
{options.map((item, index) => (
<Option label={item.label} value={item.value} disabled={item.disabled} key={index} />
))}
</OptionGroup>
</Select>
);
};

const { getByText } = render(<OptionGroupCheckAllSelect />);
fireEvent.click(document.querySelector('.t-input'));

// 点击全选,input 展示 Apple、Banana、Orange 选项
fireEvent.click(getByText('All'));
expect(document.querySelector(selectSelector)).toHaveTextContent('Apple');
expect(document.querySelector(selectSelector)).toHaveTextContent('Banana');
expect(document.querySelector(selectSelector)).toHaveTextContent('Orange');

// 再次点击全选,input 清空选项(保留原本禁用项)
fireEvent.click(getByText('All'));
expect(document.querySelector(selectSelector)).toHaveTextContent('Apple');
expect(document.querySelector(selectSelector)).not.toHaveTextContent('Banana');
expect(document.querySelector(selectSelector)).not.toHaveTextContent('Orange');
});

test('可过滤选择器测试', async () => {
const testId = 'test-id';
const FilterableSelect = () => {
Expand Down Expand Up @@ -472,17 +511,23 @@ describe('Select 组件测试', () => {
);
};
const { getByText, container } = render(<MultipleSelect />);
const tags0 = container.querySelectorAll('.t-tag');
expect(tags0.length).toBe(0);

// 检测第一次select value
// 初始化无 tag 展示
const tags = container.querySelectorAll('.t-tag');
expect(tags.length).toBe(0);

// 选择 Apple
expect(document.querySelectorAll(popupSelector).length).toBe(0);
fireEvent.click(document.querySelector('.t-input'));
fireEvent.click(getByText('Apple'));
const tags0 = container.querySelectorAll('.t-tag');
expect(tags0.length).toBe(0); // 禁用项不可选中

// 选择 Banana
fireEvent.click(getByText('Banana'));
const tags1 = container.querySelectorAll('.t-tag');
expect(tags1.length).toBe(1);
expect(tags1[0]).toHaveTextContent('Apple');
//input popup 消失
expect(tags1[0]).toHaveTextContent('Banana');
fireEvent.click(document.querySelector('.t-input'));
expect(document.querySelectorAll(popupSelector).length).toBe(1);
await mockTimeout(() => {
Expand All @@ -491,7 +536,7 @@ describe('Select 组件测试', () => {
});
});

// 检测第二次 select value
// 选择 Orange
fireEvent.click(document.querySelector('.t-input'));
await mockTimeout(() => {
expect(document.querySelector(popupSelector)).toHaveStyle({
Expand All @@ -501,51 +546,46 @@ describe('Select 组件测试', () => {
fireEvent.click(getByText('Orange'));
const tags2 = container.querySelectorAll('.t-tag');
expect(tags2.length).toBe(2);
expect(tags2[0]).toHaveTextContent('Apple');
expect(tags2[0]).toHaveTextContent('Banana');
expect(tags2[1]).toHaveTextContent('More(1)');
//input popup 消失

// input popup 消失
fireEvent.click(document.querySelector('.t-input'));
await mockTimeout(() => {
expect(document.querySelectorAll(popupSelector).length).toBe(1);
expect(document.querySelector(popupSelector)).toHaveStyle({
display: 'none',
});
});
// collapsedItems popup 展示

// 悬停 More,展示 collapsedItems popup
fireEvent.mouseEnter(tags2[1]);
expect(document.querySelectorAll(popupSelector).length).toBe(2);
// 判断展示的tag
const collapsedTags2 = document.querySelectorAll('.collapsed-items-popup .t-tag');
expect(collapsedTags2.length).toBe(1);
expect(collapsedTags2[0]).toHaveTextContent('Orange');

// 检测第三次 select value
// 取消选中 Orange
fireEvent.click(document.querySelector('.t-input'));
await mockTimeout(() => {
expect(document.querySelector(popupSelector)).toHaveStyle({
display: 'block',
});
});
fireEvent.click(getByText('Banana'));
const selectOptions = document.querySelectorAll('.t-select-option');
const orangeOption = Array.from(selectOptions).find((option) => option.textContent.includes('Orange'));
fireEvent.click(orangeOption);
const tags3 = container.querySelectorAll('.t-tag');
expect(tags3.length).toBe(2);
expect(tags3[0]).toHaveTextContent('Apple');
expect(tags3[1]).toHaveTextContent('More(2)');
//input popup 消失
expect(tags3.length).toBe(1);
expect(tags3[0]).toHaveTextContent('Banana');

// input popup 消失
fireEvent.click(document.querySelector('.t-input'));
await mockTimeout(() => {
expect(document.querySelectorAll(popupSelector).length).toBe(2);
expect(document.querySelectorAll(popupSelector).length).toBe(1);
expect(document.querySelector(popupSelector)).toHaveStyle({
display: 'none',
});
});
// collapsedItems popup 展示
fireEvent.mouseEnter(tags3[1]);
expect(document.querySelectorAll(popupSelector).length).toBe(2);
// 判断展示的tag
const collapsedTags3 = document.querySelectorAll('.collapsed-items-popup .t-tag');
expect(collapsedTags3.length).toBe(2);
expect(collapsedTags3[0]).toHaveTextContent('Orange');
expect(collapsedTags3[1]).toHaveTextContent('Banana');
});
});
8 changes: 4 additions & 4 deletions packages/components/select/_example/group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const { Option, OptionGroup } = Select;

const OptionGroupSelect = () => {
const [value, setValue] = useState('');
const [value2, setValue2] = useState([1]);
const [value2, setValue2] = useState([1, 4]);

const onChange = (value: string) => {
setValue(value);
Expand All @@ -15,7 +15,7 @@ const OptionGroupSelect = () => {
setValue2(value);
};
const options1 = [
{ label: '选项一', value: 1 },
{ label: '选项一', value: 1, disabled: true },
{ label: '选项二', value: 2 },
];

Expand Down Expand Up @@ -48,13 +48,13 @@ const OptionGroupSelect = () => {
];

return (
<Space breakLine style={{ width: '100%' }}>
<Space breakLine style={{ width: '800px' }}>
<Select value={value} onChange={onChange} style={{ width: '40%' }} options={groupOptions} filterable />
<Select value={value2} onChange={onChange2} style={{ width: '40%' }} multiple filterable>
<Option value="all" label="全选" checkAll></Option>
<OptionGroup label="分组一" divider={true}>
{options1.map((item, index) => (
<Option label={item.label} value={item.value} key={index} />
<Option label={item.label} value={item.value} disabled={item.disabled} key={index} />
))}
</OptionGroup>
<OptionGroup label="分组二" divider={true}>
Expand Down
3 changes: 2 additions & 1 deletion packages/components/select/base/Option.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,12 @@ const Option: React.FC<SelectOptionProps> = (props) => {

const label = propLabel || value;
const disabled = propDisabled || (multiple && Array.isArray(selectedValue) && max && selectedValue.length >= max);
const initCheckedStatus = !(Array.isArray(selectedValue) && selectedValue.length === props.optionLength);

let selected: boolean;
let indeterminate: boolean;
// 处理存在禁用项时,全选状态无法来回切换的问题
const [allSelectableChecked, setAllSelectableChecked] = useState(!selected);
const [allSelectableChecked, setAllSelectableChecked] = useState(initCheckedStatus);

const titleContent = useMemo(() => {
// 外部设置 props,说明希望受控
Expand Down
43 changes: 33 additions & 10 deletions packages/components/select/base/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -190,18 +190,41 @@ const Select = forwardRefWithStatics(
const { valueKey } = getKeyMapping(keys);
const isObjectType = valueType === 'object';

const enabledOptions = currentOptions.filter(
(opt) => !isSelectOptionGroup(opt) && !opt.checkAll && !opt.disabled,
);
const enabledOptions: SelectOption[] = [];

const currentValues = Array.isArray(value) ? value : [];
const disabledSelectedOptions = currentOptions.filter((opt) => {
if (isSelectOptionGroup(opt) || opt.checkAll) return false;
if (!opt.disabled) return false;
if (isObjectType) {
return currentValues.some((v) => get(v, valueKey) === opt[valueKey]);
currentOptions.forEach((option) => {
// 如果涉及分组,需要将分组内的选项进行计算,否则会影响全选的功能
if (isSelectOptionGroup(option)) {
option.children?.forEach((item) => {
if (!item.checkAll && !item.disabled) {
enabledOptions.push(item);
}
});
} else {
!option.checkAll && !option.disabled && enabledOptions.push(option);
}
});

const currentValues = Array.isArray(value) ? value : [];
const disabledSelectedOptions: SelectOption[] = [];

const isDisabledAndSelected = (opt: TdOptionProps) => {
if (opt.checkAll || !opt.disabled) return false;
if (isObjectType) return currentValues.some((v) => get(v, valueKey) === opt[valueKey]);
return currentValues.includes(opt[valueKey]);
};

currentOptions.forEach((opt) => {
if (isSelectOptionGroup(opt)) {
// 处理分组内的禁用选项
opt.children?.forEach((item) => {
if (isDisabledAndSelected(item)) {
disabledSelectedOptions.push(item);
}
});
} else if (isDisabledAndSelected(opt)) {
disabledSelectedOptions.push(opt);
}
});

let checkAllValue: SelectValue[];
Expand Down Expand Up @@ -263,7 +286,7 @@ const Select = forwardRefWithStatics(

if (multiple && context?.trigger === 'uncheck' && isFunction(onRemove)) {
const value = context?.value;
const option = (options as OptionsType).find((option) => option.value === value);
const option = (options as OptionsType)?.find((option) => option.value === value);
onRemove({
value,
data: option,
Expand Down
Loading
Loading