From f2dbc692bf3473fa2ca33e99b2eacd41a2001fa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Thu, 5 Feb 2026 17:44:13 +0800 Subject: [PATCH 1/2] fix: apply rawOpen fix to multiple mode Pass rawOpen through context and use it in MultipleContent to prevent search value from being cleared when emptyListContent blocks the dropdown from opening. Also adds test coverage for the multiple mode case. Co-Authored-By: Claude Opus 4.5 --- src/BaseSelect/index.tsx | 2 + src/SelectInput/Content/MultipleContent.tsx | 4 +- src/hooks/useBaseProps.ts | 1 + tests/Select.test.tsx | 78 ++++++++++++++------- 4 files changed, 60 insertions(+), 25 deletions(-) diff --git a/src/BaseSelect/index.tsx b/src/BaseSelect/index.tsx index c7f79449..788ecd05 100644 --- a/src/BaseSelect/index.tsx +++ b/src/BaseSelect/index.tsx @@ -645,6 +645,7 @@ const BaseSelect = React.forwardRef((props, ref) notFoundContent, open: mergedOpen, triggerOpen: mergedOpen, + rawOpen, id, showSearch, multiple, @@ -662,6 +663,7 @@ const BaseSelect = React.forwardRef((props, ref) showSearch, multiple, mergedOpen, + rawOpen, showScrollBar, styles, classNames, diff --git a/src/SelectInput/Content/MultipleContent.tsx b/src/SelectInput/Content/MultipleContent.tsx index 66ed4551..0ea065e6 100644 --- a/src/SelectInput/Content/MultipleContent.tsx +++ b/src/SelectInput/Content/MultipleContent.tsx @@ -36,6 +36,7 @@ export default React.forwardRef(function M disabled, showSearch, triggerOpen, + rawOpen, toggleOpen, autoClearSearchValue, tagRender: tagRenderFromContext, @@ -50,8 +51,9 @@ export default React.forwardRef(function M // ===================== Search ====================== // Apply autoClearSearchValue logic: when dropdown is closed and autoClearSearchValue is not false (default true), clear search value + // Use rawOpen to avoid clearing search when emptyListContent blocks open let computedSearchValue = searchValue; - if (!triggerOpen && mode === 'multiple' && autoClearSearchValue !== false) { + if (!rawOpen && mode === 'multiple' && autoClearSearchValue !== false) { computedSearchValue = ''; } diff --git a/src/hooks/useBaseProps.ts b/src/hooks/useBaseProps.ts index 569fa8b8..2b7101d8 100644 --- a/src/hooks/useBaseProps.ts +++ b/src/hooks/useBaseProps.ts @@ -8,6 +8,7 @@ import type { BaseSelectProps } from '../BaseSelect'; export interface BaseSelectContextProps extends BaseSelectProps { triggerOpen: boolean; + rawOpen: boolean; multiple: boolean; toggleOpen: (open?: boolean) => void; lockOptions: boolean; diff --git a/tests/Select.test.tsx b/tests/Select.test.tsx index 66dd165a..78be06d3 100644 --- a/tests/Select.test.tsx +++ b/tests/Select.test.tsx @@ -1997,32 +1997,62 @@ describe('Select.Basic', () => { expect(container.querySelector('.rc-select-dropdown-empty')).toBeFalsy(); }); - it('should allow typing when notFoundContent is null and no options match', () => { - const onSearch = jest.fn(); - const { container } = render( - , - ); + describe('should allow typing when notFoundContent is null and no options match', () => { + it('single', () => { + const onSearch = jest.fn(); + const { container } = render( + , + ); - const input = container.querySelector('input'); + const input = container.querySelector('input'); + + // Type 'j' - should match 'Jack' + fireEvent.change(input, { target: { value: 'j' } }); + expect(onSearch).toHaveBeenLastCalledWith('j'); + expect(input.value).toBe('j'); + expect(container.querySelectorAll('.rc-select-item-option')).toHaveLength(1); - // Type 'j' - should match 'Jack' - fireEvent.change(input, { target: { value: 'j' } }); - expect(onSearch).toHaveBeenLastCalledWith('j'); - expect(input.value).toBe('j'); - expect(container.querySelectorAll('.rc-select-item-option')).toHaveLength(1); - - // Type 'x' - no match, but input should still work - fireEvent.change(input, { target: { value: 'x' } }); - expect(onSearch).toHaveBeenLastCalledWith('x'); - expect(input.value).toBe('x'); - - // Type more characters - should continue working - fireEvent.change(input, { target: { value: 'xyz' } }); - expect(onSearch).toHaveBeenLastCalledWith('xyz'); - expect(input.value).toBe('xyz'); + // Type 'x' - no match, but input should still work + fireEvent.change(input, { target: { value: 'x' } }); + expect(onSearch).toHaveBeenLastCalledWith('x'); + expect(input.value).toBe('x'); + + // Type more characters - should continue working + fireEvent.change(input, { target: { value: 'xyz' } }); + expect(onSearch).toHaveBeenLastCalledWith('xyz'); + expect(input.value).toBe('xyz'); + }); + + it('multiple', () => { + const onSearch = jest.fn(); + const { container } = render( + , + ); + + const input = container.querySelector('input'); + + // Type 'j' - should match 'Jack' + fireEvent.change(input, { target: { value: 'j' } }); + expect(onSearch).toHaveBeenLastCalledWith('j'); + expect(input.value).toBe('j'); + expect(container.querySelectorAll('.rc-select-item-option')).toHaveLength(1); + + // Type 'x' - no match, but input should still work + fireEvent.change(input, { target: { value: 'x' } }); + expect(onSearch).toHaveBeenLastCalledWith('x'); + expect(input.value).toBe('x'); + + // Type more characters - should continue working + fireEvent.change(input, { target: { value: 'xyz' } }); + expect(onSearch).toHaveBeenLastCalledWith('xyz'); + expect(input.value).toBe('xyz'); + }); }); it('click outside to close select', () => { From 8fcb5c745f6062d4ada11ba1839092b926db54cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Thu, 5 Feb 2026 17:49:04 +0800 Subject: [PATCH 2/2] test: refactor typing test to use it.each Combine single and multiple mode tests into a parametrized test using it.each for better maintainability. Co-Authored-By: Claude Opus 4.5 --- tests/Select.test.tsx | 35 +++++------------------------------ 1 file changed, 5 insertions(+), 30 deletions(-) diff --git a/tests/Select.test.tsx b/tests/Select.test.tsx index 78be06d3..660f9ca3 100644 --- a/tests/Select.test.tsx +++ b/tests/Select.test.tsx @@ -1998,38 +1998,13 @@ describe('Select.Basic', () => { }); describe('should allow typing when notFoundContent is null and no options match', () => { - it('single', () => { - const onSearch = jest.fn(); - const { container } = render( - , - ); - - const input = container.querySelector('input'); - - // Type 'j' - should match 'Jack' - fireEvent.change(input, { target: { value: 'j' } }); - expect(onSearch).toHaveBeenLastCalledWith('j'); - expect(input.value).toBe('j'); - expect(container.querySelectorAll('.rc-select-item-option')).toHaveLength(1); - - // Type 'x' - no match, but input should still work - fireEvent.change(input, { target: { value: 'x' } }); - expect(onSearch).toHaveBeenLastCalledWith('x'); - expect(input.value).toBe('x'); - - // Type more characters - should continue working - fireEvent.change(input, { target: { value: 'xyz' } }); - expect(onSearch).toHaveBeenLastCalledWith('xyz'); - expect(input.value).toBe('xyz'); - }); - - it('multiple', () => { + it.each<{ mode: 'multiple' | undefined; label: string }>([ + { mode: undefined, label: 'single' }, + { mode: 'multiple', label: 'multiple' }, + ])('$label', ({ mode }) => { const onSearch = jest.fn(); const { container } = render( - ,