From 7cf273e9ab2aa39f40941f3b33c6965c9abe678e Mon Sep 17 00:00:00 2001 From: Ferit Date: Sun, 18 Oct 2026 22:44:10 +0300 Subject: [PATCH] fix: skip changes for unchanged switch values --- src/index.tsx | 2 +- tests/index.spec.js | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/index.tsx b/src/index.tsx index c3daa4b..0915ee4 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -61,7 +61,7 @@ const Switch = React.forwardRef( ) { let mergedChecked = innerChecked; - if (!disabled) { + if (!disabled && newChecked !== innerChecked) { mergedChecked = newChecked; setInnerChecked(mergedChecked); onChange?.(mergedChecked, event); diff --git a/tests/index.spec.js b/tests/index.spec.js index c980bf3..6eed099 100644 --- a/tests/index.spec.js +++ b/tests/index.spec.js @@ -38,6 +38,43 @@ describe('rc-switch', () => { expect(switchNode.getAttribute('aria-checked')).toBe('false'); }); + it.each([false, true])('does not emit a change for an arrow that retains %s', (checked) => { + const onChange = jest.fn(); + const onKeyDown = jest.fn(); + const onClick = jest.fn(); + const { getByRole } = createSwitch({ defaultChecked: checked, onChange, onKeyDown, onClick }); + const switchNode = getByRole('switch'); + + keyDownWithWhich(switchNode, checked ? KeyCode.RIGHT : KeyCode.LEFT); + expect(onChange).not.toHaveBeenCalled(); + expect(onKeyDown).toHaveBeenCalledTimes(1); + expect(switchNode).toHaveAttribute('aria-checked', String(checked)); + + keyDownWithWhich(switchNode, checked ? KeyCode.LEFT : KeyCode.RIGHT); + expect(onChange).toHaveBeenCalledTimes(1); + expect(onChange).toHaveBeenLastCalledWith( + !checked, + expect.objectContaining({ type: 'keydown' }), + ); + fireEvent.click(switchNode); + expect(onChange).toHaveBeenCalledTimes(2); + expect(onClick).toHaveBeenCalledWith(checked, expect.objectContaining({ type: 'click' })); + }); + + it('retains controlled change requests while skipping an unchanged checked value', () => { + const onChange = jest.fn(); + const { getByRole, rerender } = createSwitch({ checked: false, onChange }); + const switchNode = getByRole('switch'); + + keyDownWithWhich(switchNode, KeyCode.RIGHT); + expect(onChange).toHaveBeenCalledWith(true, expect.objectContaining({ type: 'keydown' })); + expect(switchNode).toHaveAttribute('aria-checked', 'false'); + rerender(); + onChange.mockClear(); + keyDownWithWhich(switchNode, KeyCode.RIGHT); + expect(onChange).not.toHaveBeenCalled(); + }); + it('should change from an initial checked state of true to false on click', () => { const onChange = jest.fn(); const { getByRole } = createSwitch({ defaultChecked: true, onChange });