Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
217 changes: 217 additions & 0 deletions __tests__/components/ui/select.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
/**
* Tests for components/ui/select.tsx
*
* Covers three key states of the primitive:
* 1. Placeholder — shown when no value is selected
* 2. Value selection — selecting an item updates the displayed value
* 3. Disabled — interaction is blocked when the select is disabled
*
* Implementation notes
* ─────────────────────
* @base-ui/react Select renders its popup via a Portal into document.body.
* jsdom supports this, so we use userEvent.click to open the popup and
* select an item exactly as a user would.
*
* Placeholder state is signalled by `data-placeholder` on SelectTrigger
* (and SelectValue), per the base-ui Select API:
* https://base-ui.com/react/components/select#trigger
*
* Disabled state is signalled by `data-disabled` on SelectTrigger and the
* native HTML `disabled` attribute on the underlying <button>.
*/

import { describe, it, expect, vi } from 'vitest'
import { render, screen, within } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'

// ─── Shared test fixture ──────────────────────────────────────────────────────

interface TestSelectProps {
value?: string
defaultValue?: string
onValueChange?: (value: string) => void
disabled?: boolean
placeholder?: string
}

/**
* Minimal but realistic Select assembly that mirrors how the component is
* used throughout the app. SelectContent is rendered without animation
* classes in jsdom so the items are immediately accessible after the popup
* opens.
*/
function TestSelect({
value,
defaultValue,
onValueChange,
disabled = false,
placeholder = 'Pick an option',
}: TestSelectProps) {
return (
<Select
value={value}
defaultValue={defaultValue}
onValueChange={onValueChange}
disabled={disabled}
>
<SelectTrigger aria-label="test-select">
<SelectValue placeholder={placeholder} />
</SelectTrigger>
<SelectContent>
<SelectItem value="apple">Apple</SelectItem>
<SelectItem value="banana">Banana</SelectItem>
<SelectItem value="cherry">Cherry</SelectItem>
</SelectContent>
</Select>
)
}

// ─── Placeholder ──────────────────────────────────────────────────────────────

describe('placeholder', () => {
it('shows the placeholder text when no value is selected', () => {
render(<TestSelect />)

// The SelectValue span renders the placeholder text as its content when
// no value is set.
expect(screen.getByText('Pick an option')).toBeInTheDocument()
})

it('marks the trigger with data-placeholder when no value is selected', () => {
render(<TestSelect />)

const trigger = screen.getByRole('button', { name: 'test-select' })

Check failure on line 90 in __tests__/components/ui/select.test.tsx

View workflow job for this annotation

GitHub Actions / build

__tests__/components/ui/select.test.tsx > placeholder > marks the trigger with data-placeholder when no value is selected

TestingLibraryElementError: Unable to find an accessible element with the role "button" and name "test-select" Here are the accessible roles: combobox: Name "test-select": <button aria-expanded="false" aria-haspopup="listbox" aria-label="test-select" class="flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4" data-placeholder="" data-size="default" data-slot="select-trigger" id="base-ui-_r_3_" role="combobox" tabindex="0" type="button" /> -------------------------------------------------- Ignored nodes: comments, script, style <body> <div> <button aria-expanded="false" aria-haspopup="listbox" aria-label="test-select" class="flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4" data-placeholder="" data-size="default" data-slot="select-trigger" id="base-ui-_r_3_" role="combobox" tabindex="0" type="button" > <span class="flex flex-1 text-left" data-placeholder="" data-slot="select-value" > Pick an option </span> <svg aria-hidden="true" class="lucide lucide-chevron-down pointer-events-none size-4 text-muted-foreground" fill="none" height="24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg" > <path d="m6 9 6 6 6-6" /> ▼ </svg> </button> <input aria-hidden="true" id="base-ui-_r_3_-hidden-input" style="clip-path: inset(50%); overflow: hidden; white-space: nowrap; border: 0px; padding: 0px; width: 1px; height: 1px; margin: -1px; position: fixed; top: 0px; left: 0px;" tabindex="-1" value="" /> </div> </body> ❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:76:38 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:52:17 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:95:19 ❯ __tests__/components/ui/select.test.tsx:90:28

// base-ui sets data-placeholder on the trigger element when the select
// has no value — this is what drives the muted text-color in the CSS.
expect(trigger).toHaveAttribute('data-placeholder')
})

it('does not show data-placeholder once a value is set via defaultValue', () => {
render(<TestSelect defaultValue="apple" />)

const trigger = screen.getByRole('button', { name: 'test-select' })

Check failure on line 100 in __tests__/components/ui/select.test.tsx

View workflow job for this annotation

GitHub Actions / build

__tests__/components/ui/select.test.tsx > placeholder > does not show data-placeholder once a value is set via defaultValue

TestingLibraryElementError: Unable to find an accessible element with the role "button" and name "test-select" Here are the accessible roles: combobox: Name "test-select": <button aria-expanded="false" aria-haspopup="listbox" aria-label="test-select" class="flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4" data-size="default" data-slot="select-trigger" id="base-ui-_r_6_" role="combobox" tabindex="0" type="button" /> -------------------------------------------------- Ignored nodes: comments, script, style <body> <div> <button aria-expanded="false" aria-haspopup="listbox" aria-label="test-select" class="flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4" data-size="default" data-slot="select-trigger" id="base-ui-_r_6_" role="combobox" tabindex="0" type="button" > <span class="flex flex-1 text-left" data-slot="select-value" > apple </span> <svg aria-hidden="true" class="lucide lucide-chevron-down pointer-events-none size-4 text-muted-foreground" fill="none" height="24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg" > <path d="m6 9 6 6 6-6" /> ▼ </svg> </button> <input aria-hidden="true" id="base-ui-_r_6_-hidden-input" style="clip-path: inset(50%); overflow: hidden; white-space: nowrap; border: 0px; padding: 0px; width: 1px; height: 1px; margin: -1px; position: fixed; top: 0px; left: 0px;" tabindex="-1" value="apple" /> </div> </body> ❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:76:38 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:52:17 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:95:19 ❯ __tests__/components/ui/select.test.tsx:100:28
expect(trigger).not.toHaveAttribute('data-placeholder')
})

it('does not render the placeholder text when a defaultValue is provided', () => {
render(<TestSelect defaultValue="banana" placeholder="Pick an option" />)

expect(screen.queryByText('Pick an option')).not.toBeInTheDocument()
})
})

// ─── Value selection ──────────────────────────────────────────────────────────

describe('value selection', () => {
it('calls onValueChange with the selected item value when an option is clicked', async () => {
const user = userEvent.setup()
const onValueChange = vi.fn()

render(<TestSelect onValueChange={onValueChange} />)

// Open the popup
await user.click(screen.getByRole('button', { name: 'test-select' }))

Check failure on line 121 in __tests__/components/ui/select.test.tsx

View workflow job for this annotation

GitHub Actions / build

__tests__/components/ui/select.test.tsx > value selection > calls onValueChange with the selected item value when an option is clicked

TestingLibraryElementError: Unable to find an accessible element with the role "button" and name "test-select" Here are the accessible roles: combobox: Name "test-select": <button aria-expanded="false" aria-haspopup="listbox" aria-label="test-select" class="flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4" data-placeholder="" data-size="default" data-slot="select-trigger" id="base-ui-_r_c_" role="combobox" tabindex="0" type="button" /> -------------------------------------------------- Ignored nodes: comments, script, style <body> <div> <button aria-expanded="false" aria-haspopup="listbox" aria-label="test-select" class="flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4" data-placeholder="" data-size="default" data-slot="select-trigger" id="base-ui-_r_c_" role="combobox" tabindex="0" type="button" > <span class="flex flex-1 text-left" data-placeholder="" data-slot="select-value" > Pick an option </span> <svg aria-hidden="true" class="lucide lucide-chevron-down pointer-events-none size-4 text-muted-foreground" fill="none" height="24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg" > <path d="m6 9 6 6 6-6" /> ▼ </svg> </button> <input aria-hidden="true" id="base-ui-_r_c_-hidden-input" style="clip-path: inset(50%); overflow: hidden; white-space: nowrap; border: 0px; padding: 0px; width: 1px; height: 1px; margin: -1px; position: fixed; top: 0px; left: 0px;" tabindex="-1" value="" /> </div> </body> ❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:76:38 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:52:17 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:95:19 ❯ __tests__/components/ui/select.test.tsx:121:29

// The popup is portalled into document.body — query from there
const listbox = await screen.findByRole('listbox')
await user.click(within(listbox).getByText('Cherry'))

expect(onValueChange).toHaveBeenCalledOnce()
expect(onValueChange).toHaveBeenCalledWith('cherry')
})

it('removes data-placeholder from the trigger after a selection is made', async () => {
const user = userEvent.setup()
let currentValue = ''
const onValueChange = vi.fn((v: string) => {
currentValue = v
})

const { rerender } = render(
<TestSelect value={currentValue} onValueChange={onValueChange} />,
)

const trigger = screen.getByRole('button', { name: 'test-select' })

Check failure on line 142 in __tests__/components/ui/select.test.tsx

View workflow job for this annotation

GitHub Actions / build

__tests__/components/ui/select.test.tsx > value selection > removes data-placeholder from the trigger after a selection is made

TestingLibraryElementError: Unable to find an accessible element with the role "button" and name "test-select" Here are the accessible roles: combobox: Name "test-select": <button aria-expanded="false" aria-haspopup="listbox" aria-label="test-select" class="flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4" data-placeholder="" data-size="default" data-slot="select-trigger" id="base-ui-_r_f_" role="combobox" tabindex="0" type="button" /> -------------------------------------------------- Ignored nodes: comments, script, style <body> <div> <button aria-expanded="false" aria-haspopup="listbox" aria-label="test-select" class="flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4" data-placeholder="" data-size="default" data-slot="select-trigger" id="base-ui-_r_f_" role="combobox" tabindex="0" type="button" > <span class="flex flex-1 text-left" data-placeholder="" data-slot="select-value" > Pick an option </span> <svg aria-hidden="true" class="lucide lucide-chevron-down pointer-events-none size-4 text-muted-foreground" fill="none" height="24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg" > <path d="m6 9 6 6 6-6" /> ▼ </svg> </button> <input aria-hidden="true" id="base-ui-_r_f_-hidden-input" style="clip-path: inset(50%); overflow: hidden; white-space: nowrap; border: 0px; padding: 0px; width: 1px; height: 1px; margin: -1px; position: fixed; top: 0px; left: 0px;" tabindex="-1" value="" /> </div> </body> ❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:76:38 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:52:17 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:95:19 ❯ __tests__/components/ui/select.test.tsx:142:28
expect(trigger).toHaveAttribute('data-placeholder')

// Open and select
await user.click(trigger)
const listbox = await screen.findByRole('listbox')
await user.click(within(listbox).getByText('Apple'))

expect(onValueChange).toHaveBeenCalledWith('apple')

// Rerender with the new value to reflect controlled state
rerender(<TestSelect value={currentValue} onValueChange={onValueChange} />)
expect(trigger).not.toHaveAttribute('data-placeholder')
})

it('displays the selected item label in the trigger after selection', async () => {
const user = userEvent.setup()
let currentValue = ''
const onValueChange = vi.fn((v: string) => {
currentValue = v
})

const { rerender } = render(
<TestSelect value={currentValue} onValueChange={onValueChange} />,
)

await user.click(screen.getByRole('button', { name: 'test-select' }))

Check failure on line 168 in __tests__/components/ui/select.test.tsx

View workflow job for this annotation

GitHub Actions / build

__tests__/components/ui/select.test.tsx > value selection > displays the selected item label in the trigger after selection

TestingLibraryElementError: Unable to find an accessible element with the role "button" and name "test-select" Here are the accessible roles: combobox: Name "test-select": <button aria-expanded="false" aria-haspopup="listbox" aria-label="test-select" class="flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4" data-placeholder="" data-size="default" data-slot="select-trigger" id="base-ui-_r_i_" role="combobox" tabindex="0" type="button" /> -------------------------------------------------- Ignored nodes: comments, script, style <body> <div> <button aria-expanded="false" aria-haspopup="listbox" aria-label="test-select" class="flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4" data-placeholder="" data-size="default" data-slot="select-trigger" id="base-ui-_r_i_" role="combobox" tabindex="0" type="button" > <span class="flex flex-1 text-left" data-placeholder="" data-slot="select-value" > Pick an option </span> <svg aria-hidden="true" class="lucide lucide-chevron-down pointer-events-none size-4 text-muted-foreground" fill="none" height="24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg" > <path d="m6 9 6 6 6-6" /> ▼ </svg> </button> <input aria-hidden="true" id="base-ui-_r_i_-hidden-input" style="clip-path: inset(50%); overflow: hidden; white-space: nowrap; border: 0px; padding: 0px; width: 1px; height: 1px; margin: -1px; position: fixed; top: 0px; left: 0px;" tabindex="-1" value="" /> </div> </body> ❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:76:38 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:52:17 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:95:19 ❯ __tests__/components/ui/select.test.tsx:168:29
const listbox = await screen.findByRole('listbox')
await user.click(within(listbox).getByText('Banana'))

rerender(<TestSelect value={currentValue} onValueChange={onValueChange} />)

// The trigger should now display the chosen item's label
expect(screen.getByRole('button', { name: 'test-select' })).toHaveTextContent('Banana')
})
})

// ─── Disabled state ───────────────────────────────────────────────────────────

describe('disabled state', () => {
it('renders the trigger with the disabled attribute when the select is disabled', () => {
render(<TestSelect disabled />)

const trigger = screen.getByRole('button', { name: 'test-select' })

Check failure on line 185 in __tests__/components/ui/select.test.tsx

View workflow job for this annotation

GitHub Actions / build

__tests__/components/ui/select.test.tsx > disabled state > renders the trigger with the disabled attribute when the select is disabled

TestingLibraryElementError: Unable to find an accessible element with the role "button" and name "test-select" Here are the accessible roles: combobox: Name "test-select": <button aria-expanded="false" aria-haspopup="listbox" aria-label="test-select" class="flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4" data-disabled="" data-placeholder="" data-size="default" data-slot="select-trigger" disabled="" id="base-ui-_r_l_" role="combobox" tabindex="-1" type="button" /> -------------------------------------------------- Ignored nodes: comments, script, style <body> <div> <button aria-expanded="false" aria-haspopup="listbox" aria-label="test-select" class="flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4" data-disabled="" data-placeholder="" data-size="default" data-slot="select-trigger" disabled="" id="base-ui-_r_l_" role="combobox" tabindex="-1" type="button" > <span class="flex flex-1 text-left" data-placeholder="" data-slot="select-value" > Pick an option </span> <svg aria-hidden="true" class="lucide lucide-chevron-down pointer-events-none size-4 text-muted-foreground" fill="none" height="24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg" > <path d="m6 9 6 6 6-6" /> ▼ </svg> </button> <input aria-hidden="true" disabled="" id="base-ui-_r_l_-hidden-input" style="clip-path: inset(50%); overflow: hidden; white-space: nowrap; border: 0px; padding: 0px; width: 1px; height: 1px; margin: -1px; position: fixed; top: 0px; left: 0px;" tabindex="-1" value="" /> </div> </body> ❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:76:38 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:52:17 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:95:19 ❯ __tests__/components/ui/select.test.tsx:185:28
expect(trigger).toBeDisabled()
})

it('marks the trigger with data-disabled when disabled', () => {
render(<TestSelect disabled />)

const trigger = screen.getByRole('button', { name: 'test-select' })

Check failure on line 192 in __tests__/components/ui/select.test.tsx

View workflow job for this annotation

GitHub Actions / build

__tests__/components/ui/select.test.tsx > disabled state > marks the trigger with data-disabled when disabled

TestingLibraryElementError: Unable to find an accessible element with the role "button" and name "test-select" Here are the accessible roles: combobox: Name "test-select": <button aria-expanded="false" aria-haspopup="listbox" aria-label="test-select" class="flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4" data-disabled="" data-placeholder="" data-size="default" data-slot="select-trigger" disabled="" id="base-ui-_r_o_" role="combobox" tabindex="-1" type="button" /> -------------------------------------------------- Ignored nodes: comments, script, style <body> <div> <button aria-expanded="false" aria-haspopup="listbox" aria-label="test-select" class="flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4" data-disabled="" data-placeholder="" data-size="default" data-slot="select-trigger" disabled="" id="base-ui-_r_o_" role="combobox" tabindex="-1" type="button" > <span class="flex flex-1 text-left" data-placeholder="" data-slot="select-value" > Pick an option </span> <svg aria-hidden="true" class="lucide lucide-chevron-down pointer-events-none size-4 text-muted-foreground" fill="none" height="24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg" > <path d="m6 9 6 6 6-6" /> ▼ </svg> </button> <input aria-hidden="true" disabled="" id="base-ui-_r_o_-hidden-input" style="clip-path: inset(50%); overflow: hidden; white-space: nowrap; border: 0px; padding: 0px; width: 1px; height: 1px; margin: -1px; position: fixed; top: 0px; left: 0px;" tabindex="-1" value="" /> </div> </body> ❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:76:38 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:52:17 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:95:19 ❯ __tests__/components/ui/select.test.tsx:192:28
expect(trigger).toHaveAttribute('data-disabled')
})

it('does not open the popup when the trigger is clicked while disabled', async () => {
const user = userEvent.setup()

render(<TestSelect disabled />)

await user.click(screen.getByRole('button', { name: 'test-select' }))

Check failure on line 201 in __tests__/components/ui/select.test.tsx

View workflow job for this annotation

GitHub Actions / build

__tests__/components/ui/select.test.tsx > disabled state > does not open the popup when the trigger is clicked while disabled

TestingLibraryElementError: Unable to find an accessible element with the role "button" and name "test-select" Here are the accessible roles: combobox: Name "test-select": <button aria-expanded="false" aria-haspopup="listbox" aria-label="test-select" class="flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4" data-disabled="" data-placeholder="" data-size="default" data-slot="select-trigger" disabled="" id="base-ui-_r_r_" role="combobox" tabindex="-1" type="button" /> -------------------------------------------------- Ignored nodes: comments, script, style <body> <div> <button aria-expanded="false" aria-haspopup="listbox" aria-label="test-select" class="flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4" data-disabled="" data-placeholder="" data-size="default" data-slot="select-trigger" disabled="" id="base-ui-_r_r_" role="combobox" tabindex="-1" type="button" > <span class="flex flex-1 text-left" data-placeholder="" data-slot="select-value" > Pick an option </span> <svg aria-hidden="true" class="lucide lucide-chevron-down pointer-events-none size-4 text-muted-foreground" fill="none" height="24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg" > <path d="m6 9 6 6 6-6" /> ▼ </svg> </button> <input aria-hidden="true" disabled="" id="base-ui-_r_r_-hidden-input" style="clip-path: inset(50%); overflow: hidden; white-space: nowrap; border: 0px; padding: 0px; width: 1px; height: 1px; margin: -1px; position: fixed; top: 0px; left: 0px;" tabindex="-1" value="" /> </div> </body> ❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:76:38 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:52:17 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:95:19 ❯ __tests__/components/ui/select.test.tsx:201:29

// No listbox should appear in the document after clicking a disabled trigger
expect(screen.queryByRole('listbox')).not.toBeInTheDocument()
})

it('does not call onValueChange when disabled', async () => {
const user = userEvent.setup()
const onValueChange = vi.fn()

render(<TestSelect disabled onValueChange={onValueChange} />)

await user.click(screen.getByRole('button', { name: 'test-select' }))

expect(onValueChange).not.toHaveBeenCalled()
})
})
Loading