Skip to content

fix(F0Select): resolve stability audit BLOCKING issues#3902

Open
eliseo-juan wants to merge 1 commit intomainfrom
fix/stability-3874-f0select
Open

fix(F0Select): resolve stability audit BLOCKING issues#3902
eliseo-juan wants to merge 1 commit intomainfrom
fix/stability-3874-f0select

Conversation

@eliseo-juan
Copy link
Copy Markdown
Contributor

Summary

Resolves BLOCKING findings from stability audit issue #3874 for F0Select.

Changes

Accessibility

  • SelectionPreview: Replace hardcoded `Remove ${item.label}` aria-label with i18n key actions.removeItem (new key added to i18n-provider-defaults.ts)
  • SelectItem: Add aria-hidden="true" to decorative icon wrapper <div> so screen readers skip it
  • Custom trigger: Change wrapper from <div> to <button> so Radix merges combobox role onto interactive host
  • InputField inner button: Remove duplicate aria-label={label || placeholder} (already on outer trigger)
  • SelectAll: Use useId() for stable id instead of hardcoded id="select-all"

Bug fixes

  • handleApply stale closure: Add handleChangeOpenLocal to useCallback deps array

Test / story quality

  • Replace console.log onChange with vi.fn() in test setup
  • Fix openSelect helper: restore fireEvent.animationStart to enable virtualizer in JSDOM
  • Replace container.querySelector with screen.getByRole for clear button
  • Replace console.log callbacks with void in stories
  • Replace args as any with typed F0SelectProps<string> cast in stories

Testing

  • pnpm tsc --noEmit
  • pnpm lint
  • pnpm vitest:ci src/components/F0Select ✅ (12 passed, 1 skipped)

Closes #3874

- Replace hardcoded aria-label with i18n key (actions.removeItem) in SelectionPreview
- Add aria-hidden='true' to decorative icon wrapper in SelectItem
- Fix handleApply stale closure: add handleChangeOpenLocal to deps
- Change custom trigger wrapper from div to button for Radix combobox merge
- Remove duplicate aria-label from inner InputField button
- Use useId() for stable selectAllId in SelectAll (no hardcoded id)
- Replace console.log onChange with vi.fn() in tests
- Fix openSelect helper: add fireEvent.animationStart to enable virtualizer
- Replace container.querySelector with screen.getByRole for clear button
- Replace console.log callbacks with void in stories
- Replace args as any with typed F0SelectProps<string> cast in stories
@eliseo-juan eliseo-juan requested a review from a team as a code owner April 8, 2026 16:29
Copilot AI review requested due to automatic review settings April 8, 2026 16:30
@github-actions github-actions bot added fix react Changes affect packages/react labels Apr 8, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

✅ No New Circular Dependencies

No new circular dependencies detected. Current count: 0

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

📦 Alpha Package Version Published

Use pnpm i github:factorialco/f0#npm/alpha-pr-3902 to install the package

Use pnpm i github:factorialco/f0#08c1cbd1bbd03e398efeac75b647dcc9d4ebfae3 to install this specific commit

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Resolves stability-audit BLOCKING issues for F0Select, focusing on accessibility fixes, a stale-closure bug, and test/story cleanup.

Changes:

  • Improves a11y: i18n for “remove item” labels, hides decorative icons from SRs, stabilizes SelectAll checkbox IDs, and adjusts trigger semantics.
  • Fixes handleApply stale-closure by correcting useCallback dependencies.
  • Cleans up tests/stories (removes console.log, improves event usage and queries, removes as any casts).

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/react/src/lib/providers/i18n/i18n-provider-defaults.ts Adds actions.removeItem translation key for i18n’d remove labels.
packages/react/src/components/F0Select/F0Select.tsx Fixes handleApply deps and adjusts trigger rendering/ARIA behavior.
packages/react/src/components/F0Select/components/SelectItem.tsx Marks decorative icon wrapper as aria-hidden to avoid corrupting option names.
packages/react/src/components/F0Select/components/SelectionPreview.tsx Replaces hardcoded English aria-label with i18n translation key + interpolation.
packages/react/src/components/F0Select/components/SelectAll.tsx Replaces hardcoded checkbox id with useId() to avoid duplicate IDs.
packages/react/src/components/F0Select/tests/F0Select.test.tsx Aligns with testing conventions and improves interaction/query patterns.
packages/react/src/components/F0Select/stories/F0Select.stories.tsx Removes console.log and replaces as any with typed prop casts.
Comments suppressed due to low confidence (1)

packages/react/src/components/F0Select/F0Select.tsx:884

  • This trigger <button> doesn’t set type="button". Inside a form, the default type="submit" can cause unintended form submission when opening the select. Setting type="button" avoids relying on preventDefault() and keeps behavior consistent.
              <button
                className="flex w-full items-center justify-between"
                onClick={(e) => {
                  e.preventDefault()
                }}
              >

Comment on lines +820 to +825
<button
className="flex w-full items-center justify-between"
aria-label={label || placeholder}
>
{children}
</div>
</button>
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The custom-trigger branch wraps children inside a new <button> while SelectTrigger is already using asChild. Since children is typed as ReactNode, existing callers can (and do) pass interactive/focusable elements (e.g. a <button> or a tabIndex={0} div). Wrapping that inside another <button> produces invalid nested interactive HTML and can break focus/keyboard behavior and accessible naming (the wrapper’s aria-label will override the child’s label).

Consider making the trigger element itself be the asChild host (require children to be a single ReactElement and render it directly), and enforce/validate that it’s an actual interactive element (ideally a <button type="button">).

Copilot uses AI. Check for mistakes.
Comment on lines 49 to +60
const defaultSelectProps = {
error: undefined,
icon: undefined,
loading: false,
clearable: false,
labelIcon: undefined,
size: "md" as const,
disabled: false,
placeholder: "",
label: "Pick an option",
hideLabel: false,
onChange: (value: string) => {
console.log(value)
},
onChange: vi.fn(),
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

defaultSelectProps sets size: "md", but F0Select defaults to size = "sm" (packages/react/src/components/F0Select/F0Select.tsx:109). If the intent is to test defaults, consider removing the explicit size (or set it to "sm") so tests don’t accidentally rely on a non-default size.

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

🔍 Visual review for your branch is published 🔍

Here are the links to:

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

Coverage Report for packages/react

Status Category Percentage Covered / Total
🔵 Lines 45.42% 11169 / 24589
🔵 Statements 44.67% 11515 / 25777
🔵 Functions 37.16% 2510 / 6753
🔵 Branches 37.38% 7338 / 19629
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/react/src/components/F0Select/F0Select.tsx 76.02% 69.43% 69.56% 76.28% 162, 169, 182-184, 196-198, 238, 261-264, 393-394, 400, 404-405, 439, 457-458, 497, 510-543, 582, 594-598, 644-659, 665, 756, 774-811
packages/react/src/components/F0Select/components/SelectAll.tsx 8.33% 0% 0% 8.33% 32-89
packages/react/src/components/F0Select/components/SelectItem.tsx 100% 50% 100% 100%
packages/react/src/components/F0Select/components/SelectionPreview.tsx 4% 0% 0% 4.34% 35-136
packages/react/src/lib/providers/i18n/i18n-provider-defaults.ts 100% 100% 100% 100%
Generated in workflow #12671 for commit 0e9f37a by the Vitest Coverage Report Action

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix react Changes affect packages/react

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stability audit: F0Select

2 participants