ensure options in listbox can be objects

This commit is contained in:
Robin Malfait
2020-10-05 20:05:53 +02:00
parent 7d402c4213
commit fc42ddedcf
4 changed files with 125 additions and 16 deletions
@@ -579,6 +579,59 @@ describe('Keyboard interactions', () => {
})
)
it(
'should be possible to open the listbox with Enter, and focus the selected option (with a list of objects)',
suppressConsoleLogs(async () => {
const myOptions = [
{ id: 'a', name: 'Option A' },
{ id: 'b', name: 'Option B' },
{ id: 'c', name: 'Option C' },
]
const selectedOption = myOptions[1]
render(
<Listbox value={selectedOption} onChange={console.log}>
<Listbox.Button>Trigger</Listbox.Button>
<Listbox.Options>
{myOptions.map(myOption => (
<Listbox.Option key={myOption.id} value={myOption}>
{myOption.name}
</Listbox.Option>
))}
</Listbox.Options>
</Listbox>
)
assertListboxButton({
state: ListboxState.Closed,
attributes: { id: 'headlessui-listbox-button-1' },
})
assertListbox({ state: ListboxState.Closed })
// Focus the button
getListboxButton()?.focus()
// Open listbox
await press(Keys.Enter)
// Verify it is open
assertListboxButton({ state: ListboxState.Open })
assertListbox({
state: ListboxState.Open,
attributes: { id: 'headlessui-listbox-options-2' },
})
assertActiveElement(getListbox())
assertListboxButtonLinkedWithListbox()
// Verify we have listbox options
const options = getListboxOptions()
expect(options).toHaveLength(3)
options.forEach((option, i) => assertListboxOption(option, { selected: i === 1 }))
// Verify that the second listbox option is active (because it is already selected)
assertActiveListboxOption(options[1])
})
)
it(
'should have no active listbox option when there are no listbox options at all',
suppressConsoleLogs(async () => {
@@ -12,7 +12,7 @@
<ListboxButton
class="relative w-full py-2 pl-3 pr-10 text-left transition duration-150 ease-in-out bg-white border border-gray-300 rounded-md cursor-default focus:outline-none focus:shadow-outline-blue focus:border-blue-300 sm:text-sm sm:leading-5"
>
<span class="block truncate">{{ active }}</span>
<span class="block truncate">{{ active.name }}</span>
<span class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<svg
class="w-5 h-5 text-gray-400"
@@ -36,9 +36,9 @@
class="py-1 overflow-auto text-base leading-6 rounded-md shadow-xs max-h-60 focus:outline-none sm:text-sm sm:leading-5"
>
<ListboxOption
v-for="name in people"
:key="name"
:value="name"
v-for="person in people"
:key="person.id"
:value="person"
:className="resolveListboxOptionClassName"
v-slot="{ active, selected }"
>
@@ -47,7 +47,7 @@
classNames('block truncate', selected ? 'font-semibold' : 'font-normal')
"
>
{{ name }}
{{ person.name }}
</span>
<span
v-if="selected"
@@ -94,16 +94,16 @@ export default {
components: { Listbox, ListboxLabel, ListboxButton, ListboxOptions, ListboxOption },
setup(props, context) {
const people = [
'Wade Cooper',
'Arlene Mccoy',
'Devon Webb',
'Tom Cook',
'Tanya Fox',
'Hellen Schmidt',
'Caroline Schultz',
'Mason Heaney',
'Claudie Smitham',
'Emil Schaefer',
{ id: 1, name: 'Wade Cooper' },
{ id: 2, name: 'Arlene Mccoy' },
{ id: 3, name: 'Devon Webb' },
{ id: 4, name: 'Tom Cook' },
{ id: 5, name: 'Tanya Fox' },
{ id: 6, name: 'Hellen Schmidt' },
{ id: 7, name: 'Caroline Schultz' },
{ id: 8, name: 'Mason Heaney' },
{ id: 9, name: 'Claudie Smitham' },
{ id: 10, name: 'Emil Schaefer' },
]
const active = ref(people[Math.floor(Math.random() * people.length)])
@@ -622,6 +622,61 @@ describe('Keyboard interactions', () => {
})
)
it(
'should be possible to open the listbox with Enter, and focus the selected option (with a list of objects)',
suppressConsoleLogs(async () => {
renderTemplate({
template: `
<Listbox v-model="value">
<ListboxButton>Trigger</ListboxButton>
<ListboxOptions>
<ListboxOption v-for="option in options" key="option.id" :value="option">{{ option.name }}</ListboxOption>
</ListboxOptions>
</Listbox>
`,
setup: () => {
const options = [
{ id: 'a', name: 'Option A' },
{ id: 'b', name: 'Option B' },
{ id: 'c', name: 'Option C' },
]
const value = ref(options[1])
return { value, options }
},
})
assertListboxButton({
state: ListboxState.Closed,
attributes: { id: 'headlessui-listbox-button-1' },
})
assertListbox({ state: ListboxState.Closed })
// Focus the button
getListboxButton()?.focus()
// Open listbox
await press(Keys.Enter)
// Verify it is open
assertListboxButton({ state: ListboxState.Open })
assertListbox({
state: ListboxState.Open,
attributes: { id: 'headlessui-listbox-options-2' },
})
assertActiveElement(getListbox())
assertListboxButtonLinkedWithListbox()
// Verify we have listbox options
const options = getListboxOptions()
expect(options).toHaveLength(3)
options.forEach((option, i) => assertListboxOption(option, { selected: i === 1 }))
// Verify that the second listbox option is active (because it is already selected)
assertActiveListboxOption(options[1])
})
)
it(
'should have no active listbox option when there are no listbox options at all',
suppressConsoleLogs(async () => {
@@ -11,6 +11,7 @@ import {
Ref,
ComputedRef,
watchEffect,
toRaw,
} from 'vue'
import { match } from '../../utils/match'
import { render } from '../../utils/render'
@@ -476,7 +477,7 @@ export const ListboxOption = defineComponent({
: false
})
const selected = computed(() => api.value.value === value)
const selected = computed(() => toRaw(api.value.value) === toRaw(value))
const dataRef = ref<ListboxOptionDataRef['value']>({ disabled, value, textValue: '' })
onMounted(() => {