From d5bba8db35f26055a62408809a1f619b5ec90124 Mon Sep 17 00:00:00 2001 From: Shekh Date: Mon, 27 Oct 2025 16:00:56 +0530 Subject: [PATCH 1/9] fix: trigger onChange when manually clearing input via select all + delete When users manually select all text (Ctrl+A) and press Delete/Backspace, the onChange callback is now properly triggered with null, matching the behavior of the clear button. Previously, the input would reset to the previous value on blur without triggering onChange, which was inconsistent and prevented users from clearing dates via keyboard. Changes: - Modified useInputProps to detect empty input and trigger onChange(null) - Added handlers in Input component for both formatted and non-formatted inputs - Updated SingleSelector to properly handle null values by calling onClear - Added comprehensive test coverage with 11 new tests Fixes: #52473 (ant-design/ant-design) All existing tests pass (453 tests), ensuring backward compatibility. --- src/PickerInput/Selector/Input.tsx | 29 ++ .../Selector/SingleSelector/index.tsx | 6 +- .../Selector/hooks/useInputProps.ts | 7 +- tests/manual-clear.spec.tsx | 307 ++++++++++++++++++ 4 files changed, 347 insertions(+), 2 deletions(-) create mode 100644 tests/manual-clear.spec.tsx diff --git a/src/PickerInput/Selector/Input.tsx b/src/PickerInput/Selector/Input.tsx index 22051545c..e38cfcc4d 100644 --- a/src/PickerInput/Selector/Input.tsx +++ b/src/PickerInput/Selector/Input.tsx @@ -213,6 +213,23 @@ const Input = React.forwardRef((props, ref) => { // ======================= Keyboard ======================= const onSharedKeyDown: React.KeyboardEventHandler = (event) => { + const { key } = event; + + if ((key === 'Backspace' || key === 'Delete') && !format) { + const inputElement = inputRef.current; + if ( + inputElement && + inputElement.selectionStart === 0 && + inputElement.selectionEnd === inputValue.length && + inputValue.length > 0 + ) { + onChange(''); + setInputValue(''); + event.preventDefault(); + return; + } + } + if (event.key === 'Enter' && validateFormat(inputValue)) { onSubmit(); } @@ -264,6 +281,18 @@ const Input = React.forwardRef((props, ref) => { // =============== Remove =============== case 'Backspace': case 'Delete': + const inputElement = inputRef.current; + if ( + inputElement && + inputElement.selectionStart === 0 && + inputElement.selectionEnd === inputValue.length && + inputValue.length > 0 + ) { + onChange(''); + setInputValue(''); + event.preventDefault(); + return; + } nextCellText = ''; nextFillText = cellFormat; break; diff --git a/src/PickerInput/Selector/SingleSelector/index.tsx b/src/PickerInput/Selector/SingleSelector/index.tsx index 7a373b9c0..d2d1a4ee2 100644 --- a/src/PickerInput/Selector/SingleSelector/index.tsx +++ b/src/PickerInput/Selector/SingleSelector/index.tsx @@ -131,7 +131,11 @@ function SingleSelector( // ======================== Change ======================== const onSingleChange = (date: DateType) => { - onChange([date]); + if (date === null) { + onClear?.(); + } else { + onChange([date]); + } }; const onMultipleRemove = (date: DateType) => { diff --git a/src/PickerInput/Selector/hooks/useInputProps.ts b/src/PickerInput/Selector/hooks/useInputProps.ts index 251777982..5b97a0a8a 100644 --- a/src/PickerInput/Selector/hooks/useInputProps.ts +++ b/src/PickerInput/Selector/hooks/useInputProps.ts @@ -185,8 +185,13 @@ export default function useInputProps( return; } + if (text === '') { + onInvalid(false, index); + onChange(null, index); + return; + } + // Tell outer that the value typed is invalid. - // If text is empty, it means valid. onInvalid(!!text, index); }, onHelp: () => { diff --git a/tests/manual-clear.spec.tsx b/tests/manual-clear.spec.tsx new file mode 100644 index 000000000..d5722e0ef --- /dev/null +++ b/tests/manual-clear.spec.tsx @@ -0,0 +1,307 @@ +import { fireEvent, render } from '@testing-library/react'; +import React from 'react'; +import { Picker, RangePicker } from '../src'; +import dayGenerateConfig from '../src/generate/dayjs'; +import enUS from '../src/locale/en_US'; +import { getDay, openPicker, waitFakeTimer } from './util/commonUtil'; + +describe('Picker.ManualClear', () => { + beforeEach(() => { + jest.useFakeTimers().setSystemTime(getDay('1990-09-03 00:00:00').valueOf()); + }); + + afterEach(() => { + jest.clearAllTimers(); + jest.useRealTimers(); + }); + + describe('Single Picker', () => { + it('should trigger onChange when manually clearing input (select all + delete)', async () => { + const onChange = jest.fn(); + const { container } = render( + , + ); + + const input = container.querySelector('input') as HTMLInputElement; + + openPicker(container); + input.setSelectionRange(0, input.value.length); + fireEvent.keyDown(input, { key: 'Delete', code: 'Delete' }); + + await waitFakeTimer(); + + expect(onChange).toHaveBeenCalledWith(null, null); + }); + + it('should trigger onChange when manually clearing input (select all + backspace)', async () => { + const onChange = jest.fn(); + const { container } = render( + , + ); + + const input = container.querySelector('input') as HTMLInputElement; + + openPicker(container); + input.setSelectionRange(0, input.value.length); + fireEvent.keyDown(input, { key: 'Backspace', code: 'Backspace' }); + + await waitFakeTimer(); + + expect(onChange).toHaveBeenCalledWith(null, null); + }); + + it('should trigger onChange when manually clearing via input change event', async () => { + const onChange = jest.fn(); + const { container } = render( + , + ); + + const input = container.querySelector('input') as HTMLInputElement; + + openPicker(container); + fireEvent.change(input, { target: { value: '' } }); + + await waitFakeTimer(); + + expect(onChange).toHaveBeenCalledWith(null, null); + }); + + it('should reset invalid partial input on blur without triggering onChange', async () => { + const onChange = jest.fn(); + const { container } = render( + , + ); + + const input = container.querySelector('input') as HTMLInputElement; + + openPicker(container); + + const initialOnChangeCallCount = onChange.mock.calls.length; + + fireEvent.blur(input); + await waitFakeTimer(); + + expect(onChange.mock.calls.length).toBe(initialOnChangeCallCount); + expect(input.value).toBe('2023-08-01'); + }); + + it('should work with different picker modes', async () => { + const onChange = jest.fn(); + const { container } = render( + , + ); + + const input = container.querySelector('input') as HTMLInputElement; + + openPicker(container); + input.setSelectionRange(0, input.value.length); + fireEvent.keyDown(input, { key: 'Delete', code: 'Delete' }); + + await waitFakeTimer(); + + expect(onChange).toHaveBeenCalledWith(null, null); + }); + + it('should clear input value when manually clearing', async () => { + const onChange = jest.fn(); + const { container } = render( + , + ); + + const input = container.querySelector('input') as HTMLInputElement; + + expect(input.value).toBe('2023-08-01'); + + // Open picker + openPicker(container); + + // Simulate selecting all text and delete + input.setSelectionRange(0, input.value.length); + fireEvent.keyDown(input, { key: 'Delete', code: 'Delete' }); + + await waitFakeTimer(); + + // Input should be empty + expect(input.value).toBe(''); + }); + }); + + describe('Range Picker', () => { + it('should trigger onChange when manually clearing start input', async () => { + const onChange = jest.fn(); + const { container } = render( + , + ); + + const startInput = container.querySelectorAll('input')[0] as HTMLInputElement; + + openPicker(container, 0); + startInput.setSelectionRange(0, startInput.value.length); + fireEvent.keyDown(startInput, { key: 'Delete', code: 'Delete' }); + fireEvent.blur(startInput); + + await waitFakeTimer(); + + expect(startInput.value).toBe(''); + }); + + it('should trigger onChange when manually clearing end input', async () => { + const onChange = jest.fn(); + const { container } = render( + , + ); + + const endInput = container.querySelectorAll('input')[1] as HTMLInputElement; + + openPicker(container, 1); + endInput.setSelectionRange(0, endInput.value.length); + fireEvent.keyDown(endInput, { key: 'Delete', code: 'Delete' }); + fireEvent.blur(endInput); + + await waitFakeTimer(); + + expect(endInput.value).toBe(''); + }); + + it('should trigger onChange when manually clearing both inputs', async () => { + const onChange = jest.fn(); + const { container } = render( + , + ); + + const startInput = container.querySelectorAll('input')[0] as HTMLInputElement; + const endInput = container.querySelectorAll('input')[1] as HTMLInputElement; + + openPicker(container, 0); + startInput.setSelectionRange(0, startInput.value.length); + fireEvent.keyDown(startInput, { key: 'Delete', code: 'Delete' }); + fireEvent.blur(startInput); + await waitFakeTimer(); + + openPicker(container, 1); + endInput.setSelectionRange(0, endInput.value.length); + fireEvent.keyDown(endInput, { key: 'Delete', code: 'Delete' }); + fireEvent.blur(endInput); + await waitFakeTimer(); + + expect(startInput.value).toBe(''); + expect(endInput.value).toBe(''); + }); + + it('should clear input values when manually clearing', async () => { + const onChange = jest.fn(); + const { container } = render( + , + ); + + const startInput = container.querySelectorAll('input')[0] as HTMLInputElement; + + expect(startInput.value).toBe('2023-08-01'); + + openPicker(container, 0); + startInput.setSelectionRange(0, startInput.value.length); + fireEvent.keyDown(startInput, { key: 'Delete', code: 'Delete' }); + + await waitFakeTimer(); + + expect(startInput.value).toBe(''); + }); + }); + + describe('Comparison with clear button', () => { + it('manual clear should behave the same as clear button for Picker', async () => { + const onChangeManual = jest.fn(); + const onChangeClear = jest.fn(); + + const { container: container1 } = render( + , + ); + + const input1 = container1.querySelector('input') as HTMLInputElement; + openPicker(container1); + input1.setSelectionRange(0, input1.value.length); + fireEvent.keyDown(input1, { key: 'Delete', code: 'Delete' }); + await waitFakeTimer(); + + const { container: container2 } = render( + , + ); + + const clearBtn = container2.querySelector('.rc-picker-clear'); + fireEvent.mouseDown(clearBtn); + fireEvent.mouseUp(clearBtn); + fireEvent.click(clearBtn); + await waitFakeTimer(); + + expect(onChangeManual).toHaveBeenCalledWith(null, null); + expect(onChangeClear).toHaveBeenCalledWith(null, null); + }); + }); +}); From 88c80f25ee23c992f3a6769fa44124de34238880 Mon Sep 17 00:00:00 2001 From: Shekh Date: Mon, 27 Oct 2025 16:11:06 +0530 Subject: [PATCH 2/9] chore: add relevant comment --- src/PickerInput/Selector/hooks/useInputProps.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PickerInput/Selector/hooks/useInputProps.ts b/src/PickerInput/Selector/hooks/useInputProps.ts index 5b97a0a8a..c9156df93 100644 --- a/src/PickerInput/Selector/hooks/useInputProps.ts +++ b/src/PickerInput/Selector/hooks/useInputProps.ts @@ -185,8 +185,9 @@ export default function useInputProps( return; } + // Handle intentional clearing: when text is empty, trigger onChange with null if (text === '') { - onInvalid(false, index); + onInvalid(false, index); // Reset invalid state before clearing the value onChange(null, index); return; } From fa46b0c1c22718e2c12a0cfe5c53e9836180349a Mon Sep 17 00:00:00 2001 From: Shekh Date: Mon, 27 Oct 2025 16:15:47 +0530 Subject: [PATCH 3/9] refactor: extract handleManualClear to reduce code duplication - Extracted duplicate logic into reusable handleManualClear function - Added JSDoc comment explaining the function's purpose - Added inline comment explaining why event.preventDefault() is called - Improved code maintainability and testability --- src/PickerInput/Selector/Input.tsx | 44 ++++++++++++++++-------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/PickerInput/Selector/Input.tsx b/src/PickerInput/Selector/Input.tsx index e38cfcc4d..49e262c31 100644 --- a/src/PickerInput/Selector/Input.tsx +++ b/src/PickerInput/Selector/Input.tsx @@ -212,20 +212,32 @@ const Input = React.forwardRef((props, ref) => { }); // ======================= Keyboard ======================= + /** + * Handle manual clear when user selects all text and presses Delete/Backspace + * @returns true if input was cleared, false otherwise + */ + const handleManualClear = (event: React.KeyboardEvent): boolean => { + const inputElement = inputRef.current; + if ( + inputElement && + inputElement.selectionStart === 0 && + inputElement.selectionEnd === inputValue.length && + inputValue.length > 0 + ) { + onChange(''); + setInputValue(''); + // Prevent default browser behavior (e.g., navigation) after clearing + event.preventDefault(); + return true; + } + return false; + }; + const onSharedKeyDown: React.KeyboardEventHandler = (event) => { const { key } = event; if ((key === 'Backspace' || key === 'Delete') && !format) { - const inputElement = inputRef.current; - if ( - inputElement && - inputElement.selectionStart === 0 && - inputElement.selectionEnd === inputValue.length && - inputValue.length > 0 - ) { - onChange(''); - setInputValue(''); - event.preventDefault(); + if (handleManualClear(event)) { return; } } @@ -281,22 +293,12 @@ const Input = React.forwardRef((props, ref) => { // =============== Remove =============== case 'Backspace': case 'Delete': - const inputElement = inputRef.current; - if ( - inputElement && - inputElement.selectionStart === 0 && - inputElement.selectionEnd === inputValue.length && - inputValue.length > 0 - ) { - onChange(''); - setInputValue(''); - event.preventDefault(); + if (handleManualClear(event)) { return; } nextCellText = ''; nextFillText = cellFormat; break; - // =============== Arrows =============== // Left key case 'ArrowLeft': From a3d4b3c878d69e360af21c34162892eba22e9a78 Mon Sep 17 00:00:00 2001 From: Shekh Date: Mon, 27 Oct 2025 16:17:51 +0530 Subject: [PATCH 4/9] refactor: rename onSingleChange to handleSingleChange for clarity - Renamed function to follow event handler naming convention - Added explanatory comment for null date handling - Clarifies that null dates are delegated to onClear handler --- src/PickerInput/Selector/SingleSelector/index.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/PickerInput/Selector/SingleSelector/index.tsx b/src/PickerInput/Selector/SingleSelector/index.tsx index d2d1a4ee2..14f6f2b7f 100644 --- a/src/PickerInput/Selector/SingleSelector/index.tsx +++ b/src/PickerInput/Selector/SingleSelector/index.tsx @@ -130,8 +130,10 @@ function SingleSelector( const rootProps = useRootProps(restProps); // ======================== Change ======================== - const onSingleChange = (date: DateType) => { + const handleSingleChange = (date: DateType) => { if (date === null) { + // When date is null (from manual clear), delegate to onClear handler + // to properly trigger onChange and close the picker onClear?.(); } else { onChange([date]); @@ -154,7 +156,7 @@ function SingleSelector( const [getInputProps, getText] = useInputProps( { ...props, - onChange: onSingleChange, + onChange: handleSingleChange, }, ({ valueTexts }) => ({ value: valueTexts[0] || '', From acb8fdb82fc51d17f7be33b806ad597309771670 Mon Sep 17 00:00:00 2001 From: Shekh Date: Mon, 27 Oct 2025 16:24:16 +0530 Subject: [PATCH 5/9] fix: review comment --- src/PickerInput/Selector/SingleSelector/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PickerInput/Selector/SingleSelector/index.tsx b/src/PickerInput/Selector/SingleSelector/index.tsx index 14f6f2b7f..0471b7969 100644 --- a/src/PickerInput/Selector/SingleSelector/index.tsx +++ b/src/PickerInput/Selector/SingleSelector/index.tsx @@ -130,7 +130,7 @@ function SingleSelector( const rootProps = useRootProps(restProps); // ======================== Change ======================== - const handleSingleChange = (date: DateType) => { + const handleSingleChange = (date: DateType | null) => { if (date === null) { // When date is null (from manual clear), delegate to onClear handler // to properly trigger onChange and close the picker From 0172a1ae3f40484bf2933c50b7cf3104f29c29ee Mon Sep 17 00:00:00 2001 From: Shekh Date: Mon, 27 Oct 2025 18:54:09 +0530 Subject: [PATCH 6/9] test: add coverage for formatted input manual clear - Added test case for mask format inputs to cover onFormatKeyDown path - Ensures handleManualClear return statement is covered by tests - Increases test coverage to 456 tests total --- tests/manual-clear.spec.tsx | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/manual-clear.spec.tsx b/tests/manual-clear.spec.tsx index d5722e0ef..dfc4009c2 100644 --- a/tests/manual-clear.spec.tsx +++ b/tests/manual-clear.spec.tsx @@ -156,6 +156,30 @@ describe('Picker.ManualClear', () => { // Input should be empty expect(input.value).toBe(''); }); + + it('should clear formatted input with mask format', async () => { + const onChange = jest.fn(); + const { container } = render( + , + ); + + const input = container.querySelector('input') as HTMLInputElement; + + openPicker(container); + input.setSelectionRange(0, input.value.length); + fireEvent.keyDown(input, { key: 'Delete', code: 'Delete' }); + + await waitFakeTimer(); + + expect(onChange).toHaveBeenCalledWith(null, null); + expect(input.value).toBe(''); + }); }); describe('Range Picker', () => { From 91955bd8ddd791429f095e1a1598c634a787f149 Mon Sep 17 00:00:00 2001 From: Shekh Date: Fri, 31 Oct 2025 10:36:21 +0530 Subject: [PATCH 7/9] fix: implement manual clear via onChange detection instead of keyDown - Changed approach per reviewer feedback (@zombieJ) - Detect manual clear in onChange event when input becomes empty - Only allow manual clear when clearIcon is present (allowClear enabled) - If allowClear is disabled, input resets to previous valid date - Works for all input methods (keyboard, paste, voice, etc) - No keyboard event detection needed - cleaner and more general Changes: - Input.tsx: Check clearIcon in onChange to allow/prevent manual clear - SingleSelector: Delegate to onClear when date is null and clearIcon exists - useInputProps: Trigger onChange(null) when text becomes empty - Tests: Updated to use fireEvent.change instead of keyDown - Added test for allowClear={false} behavior All 455 tests pass with 100% line coverage on new code --- src/PickerInput/Selector/Input.tsx | 44 +++-------- .../Selector/SingleSelector/index.tsx | 12 +-- tests/manual-clear.spec.tsx | 73 +++++++------------ 3 files changed, 41 insertions(+), 88 deletions(-) diff --git a/src/PickerInput/Selector/Input.tsx b/src/PickerInput/Selector/Input.tsx index 49e262c31..fe9660f6d 100644 --- a/src/PickerInput/Selector/Input.tsx +++ b/src/PickerInput/Selector/Input.tsx @@ -142,10 +142,18 @@ const Input = React.forwardRef((props, ref) => { // Directly trigger `onChange` if `format` is empty const onInternalChange: React.ChangeEventHandler = (event) => { + const text = event.target.value; + + // Handle manual clear only when clearIcon is present (allowClear was enabled) + // If clearIcon is not set, reset back to previous valid date instead + if (text === '' && value !== '' && clearIcon) { + onChange(''); + setInputValue(''); + return; + } + // Hack `onChange` with format to do nothing if (!format) { - const text = event.target.value; - onModify(text); setInputValue(text); onChange(text); @@ -212,36 +220,7 @@ const Input = React.forwardRef((props, ref) => { }); // ======================= Keyboard ======================= - /** - * Handle manual clear when user selects all text and presses Delete/Backspace - * @returns true if input was cleared, false otherwise - */ - const handleManualClear = (event: React.KeyboardEvent): boolean => { - const inputElement = inputRef.current; - if ( - inputElement && - inputElement.selectionStart === 0 && - inputElement.selectionEnd === inputValue.length && - inputValue.length > 0 - ) { - onChange(''); - setInputValue(''); - // Prevent default browser behavior (e.g., navigation) after clearing - event.preventDefault(); - return true; - } - return false; - }; - const onSharedKeyDown: React.KeyboardEventHandler = (event) => { - const { key } = event; - - if ((key === 'Backspace' || key === 'Delete') && !format) { - if (handleManualClear(event)) { - return; - } - } - if (event.key === 'Enter' && validateFormat(inputValue)) { onSubmit(); } @@ -293,9 +272,6 @@ const Input = React.forwardRef((props, ref) => { // =============== Remove =============== case 'Backspace': case 'Delete': - if (handleManualClear(event)) { - return; - } nextCellText = ''; nextFillText = cellFormat; break; diff --git a/src/PickerInput/Selector/SingleSelector/index.tsx b/src/PickerInput/Selector/SingleSelector/index.tsx index 0471b7969..f5df0f8c5 100644 --- a/src/PickerInput/Selector/SingleSelector/index.tsx +++ b/src/PickerInput/Selector/SingleSelector/index.tsx @@ -130,14 +130,14 @@ function SingleSelector( const rootProps = useRootProps(restProps); // ======================== Change ======================== - const handleSingleChange = (date: DateType | null) => { - if (date === null) { - // When date is null (from manual clear), delegate to onClear handler - // to properly trigger onChange and close the picker + const onSingleChange = (date: DateType | null) => { + if (date === null && clearIcon) { + // Only allow manual clear when clearIcon is present (allowClear was enabled) onClear?.(); - } else { + } else if (date !== null) { onChange([date]); } + // If date is null but clearIcon is not set, do nothing - let it reset to previous value }; const onMultipleRemove = (date: DateType) => { @@ -156,7 +156,7 @@ function SingleSelector( const [getInputProps, getText] = useInputProps( { ...props, - onChange: handleSingleChange, + onChange: onSingleChange, }, ({ valueTexts }) => ({ value: valueTexts[0] || '', diff --git a/tests/manual-clear.spec.tsx b/tests/manual-clear.spec.tsx index dfc4009c2..d5316293e 100644 --- a/tests/manual-clear.spec.tsx +++ b/tests/manual-clear.spec.tsx @@ -16,7 +16,7 @@ describe('Picker.ManualClear', () => { }); describe('Single Picker', () => { - it('should trigger onChange when manually clearing input (select all + delete)', async () => { + it('should trigger onChange when manually clearing input', async () => { const onChange = jest.fn(); const { container } = render( { value={getDay('2023-08-01')} onChange={onChange} locale={enUS} + allowClear />, ); const input = container.querySelector('input') as HTMLInputElement; openPicker(container); - input.setSelectionRange(0, input.value.length); - fireEvent.keyDown(input, { key: 'Delete', code: 'Delete' }); + fireEvent.change(input, { target: { value: '' } }); await waitFakeTimer(); expect(onChange).toHaveBeenCalledWith(null, null); }); - it('should trigger onChange when manually clearing input (select all + backspace)', async () => { + it('should NOT clear when allowClear is disabled - reset to previous value', async () => { const onChange = jest.fn(); const { container } = render( { value={getDay('2023-08-01')} onChange={onChange} locale={enUS} + allowClear={false} />, ); const input = container.querySelector('input') as HTMLInputElement; - openPicker(container); - input.setSelectionRange(0, input.value.length); - fireEvent.keyDown(input, { key: 'Backspace', code: 'Backspace' }); - - await waitFakeTimer(); - - expect(onChange).toHaveBeenCalledWith(null, null); - }); - - it('should trigger onChange when manually clearing via input change event', async () => { - const onChange = jest.fn(); - const { container } = render( - , - ); - - const input = container.querySelector('input') as HTMLInputElement; + expect(input.value).toBe('2023-08-01'); openPicker(container); fireEvent.change(input, { target: { value: '' } }); + fireEvent.blur(input); await waitFakeTimer(); - expect(onChange).toHaveBeenCalledWith(null, null); + expect(onChange).not.toHaveBeenCalled(); + expect(input.value).toBe('2023-08-01'); }); it('should reset invalid partial input on blur without triggering onChange', async () => { @@ -115,14 +98,14 @@ describe('Picker.ManualClear', () => { onChange={onChange} locale={enUS} picker="month" + allowClear />, ); const input = container.querySelector('input') as HTMLInputElement; openPicker(container); - input.setSelectionRange(0, input.value.length); - fireEvent.keyDown(input, { key: 'Delete', code: 'Delete' }); + fireEvent.change(input, { target: { value: '' } }); await waitFakeTimer(); @@ -137,6 +120,7 @@ describe('Picker.ManualClear', () => { value={getDay('2023-08-01')} onChange={onChange} locale={enUS} + allowClear />, ); @@ -144,16 +128,11 @@ describe('Picker.ManualClear', () => { expect(input.value).toBe('2023-08-01'); - // Open picker openPicker(container); - - // Simulate selecting all text and delete - input.setSelectionRange(0, input.value.length); - fireEvent.keyDown(input, { key: 'Delete', code: 'Delete' }); + fireEvent.change(input, { target: { value: '' } }); await waitFakeTimer(); - // Input should be empty expect(input.value).toBe(''); }); @@ -166,14 +145,14 @@ describe('Picker.ManualClear', () => { onChange={onChange} locale={enUS} format={{ type: 'mask', format: 'YYYY-MM-DD' }} + allowClear />, ); const input = container.querySelector('input') as HTMLInputElement; openPicker(container); - input.setSelectionRange(0, input.value.length); - fireEvent.keyDown(input, { key: 'Delete', code: 'Delete' }); + fireEvent.change(input, { target: { value: '' } }); await waitFakeTimer(); @@ -192,14 +171,14 @@ describe('Picker.ManualClear', () => { onChange={onChange} locale={enUS} needConfirm={false} + allowClear />, ); const startInput = container.querySelectorAll('input')[0] as HTMLInputElement; openPicker(container, 0); - startInput.setSelectionRange(0, startInput.value.length); - fireEvent.keyDown(startInput, { key: 'Delete', code: 'Delete' }); + fireEvent.change(startInput, { target: { value: '' } }); fireEvent.blur(startInput); await waitFakeTimer(); @@ -216,14 +195,14 @@ describe('Picker.ManualClear', () => { onChange={onChange} locale={enUS} needConfirm={false} + allowClear />, ); const endInput = container.querySelectorAll('input')[1] as HTMLInputElement; openPicker(container, 1); - endInput.setSelectionRange(0, endInput.value.length); - fireEvent.keyDown(endInput, { key: 'Delete', code: 'Delete' }); + fireEvent.change(endInput, { target: { value: '' } }); fireEvent.blur(endInput); await waitFakeTimer(); @@ -240,6 +219,7 @@ describe('Picker.ManualClear', () => { onChange={onChange} locale={enUS} needConfirm={false} + allowClear />, ); @@ -247,14 +227,12 @@ describe('Picker.ManualClear', () => { const endInput = container.querySelectorAll('input')[1] as HTMLInputElement; openPicker(container, 0); - startInput.setSelectionRange(0, startInput.value.length); - fireEvent.keyDown(startInput, { key: 'Delete', code: 'Delete' }); + fireEvent.change(startInput, { target: { value: '' } }); fireEvent.blur(startInput); await waitFakeTimer(); openPicker(container, 1); - endInput.setSelectionRange(0, endInput.value.length); - fireEvent.keyDown(endInput, { key: 'Delete', code: 'Delete' }); + fireEvent.change(endInput, { target: { value: '' } }); fireEvent.blur(endInput); await waitFakeTimer(); @@ -270,6 +248,7 @@ describe('Picker.ManualClear', () => { value={[getDay('2023-08-01'), getDay('2023-08-15')]} onChange={onChange} locale={enUS} + allowClear />, ); @@ -278,8 +257,7 @@ describe('Picker.ManualClear', () => { expect(startInput.value).toBe('2023-08-01'); openPicker(container, 0); - startInput.setSelectionRange(0, startInput.value.length); - fireEvent.keyDown(startInput, { key: 'Delete', code: 'Delete' }); + fireEvent.change(startInput, { target: { value: '' } }); await waitFakeTimer(); @@ -304,8 +282,7 @@ describe('Picker.ManualClear', () => { const input1 = container1.querySelector('input') as HTMLInputElement; openPicker(container1); - input1.setSelectionRange(0, input1.value.length); - fireEvent.keyDown(input1, { key: 'Delete', code: 'Delete' }); + fireEvent.change(input1, { target: { value: '' } }); await waitFakeTimer(); const { container: container2 } = render( From 669467f4a215b4bde10fefd98b565291ec7d4220 Mon Sep 17 00:00:00 2001 From: Shekh Date: Fri, 31 Oct 2025 12:01:19 +0530 Subject: [PATCH 8/9] test: fix misleading test names for RangePicker manual clear - Renamed tests to accurately reflect what they test - RangePicker doesn't immediately trigger onChange when clearing individual inputs (this is expected behavior due to range validation logic) - Tests verify input values are cleared, not onChange behavior - All 11 manual clear tests passing Before: 'should trigger onChange when manually clearing start input' After: 'should clear start input value when manually clearing' This addresses review feedback while clarifying the actual behavior being tested. --- tests/manual-clear.spec.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/manual-clear.spec.tsx b/tests/manual-clear.spec.tsx index d5316293e..a6dc8def5 100644 --- a/tests/manual-clear.spec.tsx +++ b/tests/manual-clear.spec.tsx @@ -162,7 +162,7 @@ describe('Picker.ManualClear', () => { }); describe('Range Picker', () => { - it('should trigger onChange when manually clearing start input', async () => { + it('should clear start input value when manually clearing', async () => { const onChange = jest.fn(); const { container } = render( { expect(startInput.value).toBe(''); }); - it('should trigger onChange when manually clearing end input', async () => { + it('should clear end input value when manually clearing', async () => { const onChange = jest.fn(); const { container } = render( { expect(endInput.value).toBe(''); }); - it('should trigger onChange when manually clearing both inputs', async () => { + it('should clear both input values when manually clearing', async () => { const onChange = jest.fn(); const { container } = render( Date: Fri, 31 Oct 2025 13:24:37 +0530 Subject: [PATCH 9/9] fix: test --- tests/manual-clear.spec.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/manual-clear.spec.tsx b/tests/manual-clear.spec.tsx index a6dc8def5..c113dacba 100644 --- a/tests/manual-clear.spec.tsx +++ b/tests/manual-clear.spec.tsx @@ -75,16 +75,12 @@ describe('Picker.ManualClear', () => { format="YYYY-MM-DD" />, ); - const input = container.querySelector('input') as HTMLInputElement; - openPicker(container); - + fireEvent.change(input, { target: { value: '2023-08' } }); const initialOnChangeCallCount = onChange.mock.calls.length; - fireEvent.blur(input); await waitFakeTimer(); - expect(onChange.mock.calls.length).toBe(initialOnChangeCallCount); expect(input.value).toBe('2023-08-01'); });