From 2f7630e323168f46aaa4c64e1cdfa597a60773c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E8=B1=AA?= Date: Sun, 24 Nov 2024 22:50:20 +0800 Subject: [PATCH] test: add keyboard operation test case --- docs/demo/basic.tsx | 3 + package.json | 1 + src/index.tsx | 34 +++------- tests/__snapshots__/index.test.tsx.snap | 68 ++++--------------- tests/index.test.tsx | 90 +++++++++++++++++++++++++ 5 files changed, 116 insertions(+), 80 deletions(-) diff --git a/docs/demo/basic.tsx b/docs/demo/basic.tsx index 9be5161..db4d63d 100644 --- a/docs/demo/basic.tsx +++ b/docs/demo/basic.tsx @@ -8,6 +8,8 @@ export default function App() {
console.log(value, typeof value)} />
@@ -15,6 +17,7 @@ export default function App() { console.log(value, typeof value)} /> diff --git a/package.json b/package.json index a5f5115..db3a299 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "@rc-component/father-plugin": "^1.0.1", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^14.2.1", + "@testing-library/user-event": "^14.5.2", "@types/classnames": "^2.2.9", "@types/jest": "^29.2.4", "@types/react": "^18.3.11", diff --git a/src/index.tsx b/src/index.tsx index f62efbb..7a950f9 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,6 +1,5 @@ import classNames from 'classnames'; import useMergedState from 'rc-util/lib/hooks/useMergedState'; -import KeyCode from 'rc-util/lib/KeyCode'; import omit from 'rc-util/lib/omit'; import { composeRef } from 'rc-util/lib/ref'; import * as React from 'react'; @@ -202,37 +201,22 @@ const Segmented = React.forwardRef( }; // ======================= Keyboard ======================== - const getNextIndex = (current: number, offset: number) => { - if (disabled) { - return current; - } - - const total = segmentedOptions.length; - const nextIndex = (current + offset + total) % total; - - if (segmentedOptions[nextIndex]?.disabled) { - return getNextIndex(nextIndex, offset); - } - return nextIndex; - }; - const handleKeyDown = (event: React.KeyboardEvent) => { - let offset = 0; + const total = segmentedOptions.length; + let nextIndex = currentIndex; - switch (event.which) { - case KeyCode.LEFT: - case KeyCode.UP: - offset = -1; + switch (event.key) { + case 'ArrowLeft': + case 'ArrowUp': + nextIndex = currentIndex === 0 ? total - 1 : currentIndex - 1; break; - case KeyCode.RIGHT: - case KeyCode.DOWN: - offset = 1; + case 'ArrowRight': + case 'ArrowDown': + nextIndex = currentIndex === total - 1 ? 0 : currentIndex + 1; break; } - const nextIndex = getNextIndex(currentIndex, offset); const nextOption = segmentedOptions[nextIndex]; - if (nextOption) { setRawValue(nextOption.value); onChange?.(nextOption.value); diff --git a/tests/__snapshots__/index.test.tsx.snap b/tests/__snapshots__/index.test.tsx.snap index 9865593..cba0121 100644 --- a/tests/__snapshots__/index.test.tsx.snap +++ b/tests/__snapshots__/index.test.tsx.snap @@ -4,7 +4,7 @@ exports[`rc-segmented render empty segmented 1`] = `
iOS @@ -50,7 +49,6 @@ exports[`rc-segmented render label with ReactNode 1`] = `

Web @@ -84,7 +81,7 @@ exports[`rc-segmented render segmented ok 1`] = `
iOS @@ -117,7 +113,6 @@ exports[`rc-segmented render segmented ok 1`] = `
Android @@ -133,7 +128,6 @@ exports[`rc-segmented render segmented ok 1`] = `
Web @@ -147,7 +141,7 @@ exports[`rc-segmented render segmented with CSSMotion basic 1`] = `
iOS @@ -180,7 +173,6 @@ exports[`rc-segmented render segmented with CSSMotion basic 1`] = `
Android @@ -196,7 +188,6 @@ exports[`rc-segmented render segmented with CSSMotion basic 1`] = `
Web3 @@ -210,7 +201,7 @@ exports[`rc-segmented render segmented with options 1`] = `
iOS @@ -243,7 +233,6 @@ exports[`rc-segmented render segmented with options 1`] = `
Android @@ -259,7 +248,6 @@ exports[`rc-segmented render segmented with options 1`] = `
Web @@ -273,8 +261,7 @@ exports[`rc-segmented render segmented with options null/undefined 1`] = `
@@ -331,7 +315,7 @@ exports[`rc-segmented render segmented with options: 1 1`] = `
1 @@ -364,7 +347,6 @@ exports[`rc-segmented render segmented with options: 1 1`] = `
2 @@ -380,7 +362,6 @@ exports[`rc-segmented render segmented with options: 1 1`] = `
3 @@ -396,7 +377,6 @@ exports[`rc-segmented render segmented with options: 1 1`] = `
4 @@ -412,7 +392,6 @@ exports[`rc-segmented render segmented with options: 1 1`] = `
5 @@ -426,7 +405,7 @@ exports[`rc-segmented render segmented with options: 2 1`] = `
iOS @@ -459,7 +437,6 @@ exports[`rc-segmented render segmented with options: 2 1`] = `
Android @@ -475,7 +452,6 @@ exports[`rc-segmented render segmented with options: 2 1`] = `
Web @@ -489,7 +465,7 @@ exports[`rc-segmented render segmented with options: disabled 1`] = `
iOS @@ -523,7 +498,6 @@ exports[`rc-segmented render segmented with options: disabled 1`] = `
Android @@ -539,7 +513,6 @@ exports[`rc-segmented render segmented with options: disabled 1`] = `
Web @@ -553,7 +526,7 @@ exports[`rc-segmented render segmented with title 1`] = `
Web @@ -586,7 +558,6 @@ exports[`rc-segmented render segmented with title 1`] = `
hello1 @@ -602,7 +573,6 @@ exports[`rc-segmented render segmented with title 1`] = `
test1 @@ -619,7 +589,6 @@ exports[`rc-segmented render segmented with title 1`] = `
hello1 @@ -635,7 +604,6 @@ exports[`rc-segmented render segmented with title 1`] = `
foo1 @@ -649,8 +617,7 @@ exports[`rc-segmented render segmented: disabled 1`] = `
iOS @@ -684,7 +650,6 @@ exports[`rc-segmented render segmented: disabled 1`] = `
Android @@ -701,7 +666,6 @@ exports[`rc-segmented render segmented: disabled 1`] = `
Web @@ -715,7 +679,7 @@ exports[`rc-segmented should render vertical segmented 1`] = `
iOS @@ -748,7 +711,6 @@ exports[`rc-segmented should render vertical segmented 1`] = `
Android @@ -764,7 +726,6 @@ exports[`rc-segmented should render vertical segmented 1`] = `
Web @@ -778,7 +739,7 @@ exports[`rc-segmented should render vertical segmented and handle thumb animatio
iOS @@ -811,7 +771,6 @@ exports[`rc-segmented should render vertical segmented and handle thumb animatio
Android @@ -827,7 +786,6 @@ exports[`rc-segmented should render vertical segmented and handle thumb animatio
Web diff --git a/tests/index.test.tsx b/tests/index.test.tsx index 8641abf..d1246a5 100644 --- a/tests/index.test.tsx +++ b/tests/index.test.tsx @@ -1,4 +1,5 @@ import { act, fireEvent, render } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import * as React from 'react'; import Segmented from '../src'; @@ -664,3 +665,92 @@ describe('rc-segmented', () => { }); }); }); + +describe('Segmented keyboard navigation', () => { + it('should be focusable through Tab key', async () => { + const user = userEvent.setup(); + const { getByRole, container } = render( + , + ); + + const segmentedContainer = getByRole('radiogroup'); + const inputs = container.querySelectorAll('.rc-segmented-item-input'); + const firstInput = inputs[0]; + + await user.tab(); + // segmented container should be focused + expect(segmentedContainer).toHaveFocus(); + await user.tab(); + // first segmented item should be focused + expect(firstInput).toHaveFocus(); + }); + + it('should handle circular navigation with arrow keys', async () => { + const user = userEvent.setup(); + const onChange = jest.fn(); + render( + , + ); + + // focus on segmented + await user.tab(); + // focus on first item + await user.tab(); + + // Test right navigation from first item and back to first item + await user.keyboard('{ArrowRight}'); + expect(onChange).toHaveBeenCalledWith('Android'); + await user.keyboard('{ArrowRight}'); + expect(onChange).toHaveBeenCalledWith('Web'); + await user.keyboard('{ArrowRight}'); + expect(onChange).toHaveBeenCalledWith('iOS'); + + // Test left navigation from first item to last item + await user.keyboard('{ArrowLeft}'); + expect(onChange).toHaveBeenCalledWith('Web'); + }); + + it('should skip Tab navigation when disabled', async () => { + const user = userEvent.setup(); + const { container } = render( + , + ); + + const segmentedContainer = container.querySelector('.rc-segmented'); + + await user.tab(); + + // Disabled state should not get focus + expect(segmentedContainer).not.toHaveFocus(); + + // Verify container has no tabIndex attribute + expect(segmentedContainer?.getAttribute('tabIndex')).toBeNull(); + }); + + it('should handle keyboard navigation with disabled options', async () => { + const user = userEvent.setup(); + const onChange = jest.fn(); + render( + , + ); + + await user.tab(); + await user.tab(); + + await user.keyboard('{ArrowRight}'); + expect(onChange).toHaveBeenCalledWith('Web'); + + onChange.mockClear(); + + await user.keyboard('{ArrowLeft}'); + expect(onChange).toHaveBeenCalledWith('iOS'); + }); +});