-
Notifications
You must be signed in to change notification settings - Fork 0
Fix: Enhance keyboard navigation and focus for BasicSelect component #127
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughThis PR updates the test runner configuration to use npx for Jest execution, adds a comprehensive test suite for the BasicSelect component covering rendering, selection, keyboard navigation, and clear functionality, and refactors the BasicSelect component's internal structure with a wrapper div and repositioned clear button. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (2)
package.json (1)
12-12:"test": "npx jest"is likely redundant inside an npm scriptWithin
package.jsonscripts,jesttypically resolves to the localnode_modules/.bin/jestwithoutnpx. Unless you’re working around a specific environment/path issue, consider reverting to"test": "jest"to reduce indirection.src/__tests__/BasicSelect.test.tsx (1)
52-74: PreferuserEvent.keyboard(...)overfireEvent.keyDown(...)for key interactionsThis usually better matches how focus/keyboard events are dispatched and can reduce flakiness with Radix components.
Also applies to: 76-93
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
package.json(1 hunks)src/__tests__/BasicSelect.test.tsx(1 hunks)src/components/common/BasicSelect.tsx(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/__tests__/BasicSelect.test.tsx (2)
src/components/common/ErrorBoundary.tsx (1)
render(32-61)src/components/common/BasicSelect.tsx (1)
BasicSelect(20-95)
src/components/common/BasicSelect.tsx (2)
src/components/ui/select.tsx (6)
Select(150-150)SelectTrigger(158-158)SelectValue(159-159)SelectContent(151-151)SelectGroup(152-152)SelectItem(153-153)src/lib/utils.ts (1)
cn(4-6)
| it('should clear selection when allowEmpty is true and clear button is clicked', async () => { | ||
| render( | ||
| <BasicSelect | ||
| placeholder="Select an option" | ||
| items={mockItems} | ||
| onChange={mockOnChange} | ||
| value="1" | ||
| allowEmpty={true} | ||
| /> | ||
| ); | ||
|
|
||
| expect(screen.getByText('Option 1')).toBeInTheDocument(); | ||
|
|
||
| const clearButton = screen.getByRole('button', { name: /clear/i }); | ||
| await userEvent.click(clearButton); | ||
|
|
||
| expect(mockOnChange).toHaveBeenCalledWith(null); | ||
| expect(screen.getByText('Select an option')).toBeInTheDocument(); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clear-button test will fail unless the button has an accessible name
getByRole('button', { name: /clear/i }) won’t match the current clear button (icon-only, no aria-label). Please add an aria-label (or visible text) in src/components/common/BasicSelect.tsx, and then assert against that label.
🤖 Prompt for AI Agents
In src/__tests__/BasicSelect.test.tsx around lines 95 to 113 and
src/components/common/BasicSelect.tsx, the test queries the clear button by
accessible name but the component's icon-only clear button lacks an aria-label;
update BasicSelect.tsx to add an appropriate aria-label (e.g., "Clear selection"
or "Clear") on the clear button so it is discoverable by getByRole, and ensure
the test uses the same label (or a case-insensitive regex /clear/i) when
querying and asserting behavior.
| onOpenChange={(isOpen) => { | ||
| if (!isOpen && !hasValue) { | ||
| onChange(null); | ||
| } | ||
| }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential “select then immediately clear” bug via onOpenChange + stale hasValue
When an option is selected, the dropdown typically closes; if onOpenChange(false) fires before the parent updates value, hasValue can still be false and onChange(null) will run, wiping the selection. Consider removing this close-handler normalization, or guarding it with additional state/ref logic so it can’t run right after a non-empty selection.
🤖 Prompt for AI Agents
In src/components/common/BasicSelect.tsx around lines 50 to 54, the onOpenChange
handler uses hasValue to clear the selection when the dropdown closes which can
run with stale hasValue and wipe a just-selected value; remove this close-time
normalization or prevent it running immediately after a user selection by
tracking selection-in-flight with a ref/state. Specifically: either delete the
onOpenChange block that calls onChange(null), or add a ref like
selectionInFlightRef set when an option is chosen (and cleared after the parent
value updates or on next render), and short-circuit the onOpenChange handler to
skip clearing if selectionInFlightRef is true; ensure the fallback still clears
only when there truly is no value and no recent selection event.
| {allowEmpty && hasValue && ( | ||
| <button | ||
| type='button' | ||
| onClick={() => { | ||
| onChange(null); | ||
| }} | ||
| className='absolute right-2 top-1/2 -translate-y-1/2 hover:bg-neutral-100 dark:hover:bg-neutral-800 p-1.5 rounded-sm' | ||
| > | ||
| <X className='h-3.5 w-3.5 text-neutral-500 hover:text-neutral-900 dark:text-neutral-400 dark:hover:text-neutral-50' /> | ||
| </button> | ||
| )} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clear button needs an accessible name (and should match the test)
Add aria-label (e.g. "Clear selection") so screen readers can announce it and so getByRole(..., { name: /clear/i }) can pass.
{allowEmpty && hasValue && (
<button
type='button'
+ aria-label='Clear selection'
onClick={() => {
onChange(null);
}}
className='absolute right-2 top-1/2 -translate-y-1/2 hover:bg-neutral-100 dark:hover:bg-neutral-800 p-1.5 rounded-sm'
>
- <X className='h-3.5 w-3.5 text-neutral-500 hover:text-neutral-900 dark:text-neutral-400 dark:hover:text-neutral-50' />
+ <X
+ aria-hidden='true'
+ className='h-3.5 w-3.5 text-neutral-500 hover:text-neutral-900 dark:text-neutral-400 dark:hover:text-neutral-50'
+ />
</button>
)}🤖 Prompt for AI Agents
In src/components/common/BasicSelect.tsx around lines 80 to 90, the
clear-selection button is missing an accessible name which breaks screen-reader
accessibility and the existing test that queries by role/name; add an aria-label
attribute (e.g. "Clear selection" or similar matching the test's regex) to the
button element so getByRole(..., { name: /clear/i }) and assistive technologies
can identify it, keeping all other behavior and classes the same.
Overview: This PR addresses issues with keyboard navigation and focus management within the
BasicSelectcomponent.Changes:
src/components/common/BasicSelect.tsx.Summary by CodeRabbit
Tests
Chores
Refactor
✏️ Tip: You can customize this high-level summary in your review settings.