Skip to content

Commit

Permalink
fix(VSelect): external model reactivity
Browse files Browse the repository at this point in the history
fixes #20992
closes #20997
  • Loading branch information
KaelWD committed Feb 19, 2025
1 parent ab6eba4 commit 4df71de
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { VListItem } from '@/components/VList'

// Utilities
import { commands, generate, render, screen, userEvent } from '@test'
import { getAllByRole } from '@testing-library/vue'
import { cloneVNode, ref } from 'vue'

const variants = ['underlined', 'outlined', 'filled', 'solo', 'plain'] as const
Expand Down Expand Up @@ -629,6 +630,55 @@ describe('VSelect', () => {
items.value[0].title = 'Bar'
await expect.poll(() => screen.getByText('Bar')).toBeVisible()
})

it('adds a selection externally', async () => {
const items = ref(['Foo', 'Bar'])
const selection = ref(['Foo'])
const { element } = render(() => (
<VSelect v-model={ selection.value } items={ items.value } multiple />
))

await userEvent.click(element)
const menu = await screen.findByRole('listbox')
await expect.element(menu).toBeVisible()

selection.value.push('Bar')
await expect.poll(() => screen.getAllByText('Bar')).toHaveLength(2)
expect(getAllByRole(menu, 'option', { selected: true })).toHaveLength(2)
})

it('removes a selection externally', async () => {
const items = ref(['Foo', 'Bar'])
const selection = ref(['Foo', 'Bar'])
const { element } = render(() => (
<VSelect v-model={ selection.value } items={ items.value } multiple />
))

await userEvent.click(element)
const menu = await screen.findByRole('listbox')
await expect.element(menu).toBeVisible()

selection.value.splice(1, 1)
await expect.poll(() => screen.getAllByText('Bar')).toHaveLength(1)
expect(getAllByRole(menu, 'option', { selected: true })).toHaveLength(1)
})

it('adds a selected item', async () => {
const items = ref(['Foo'])
const selection = ref(['Foo'])
const { element } = render(() => (
<VSelect v-model={ selection.value } items={ items.value } multiple />
))

await userEvent.click(element)
const menu = await screen.findByRole('listbox')
await expect.element(menu).toBeVisible()

items.value.push('Bar')
selection.value.push('Bar')
await expect.poll(() => screen.getAllByText('Bar')).toHaveLength(2)
expect(getAllByRole(menu, 'option', { selected: true })).toHaveLength(2)
})
})

describe('Showcase', () => {
Expand Down
5 changes: 2 additions & 3 deletions packages/vuetify/src/composables/list-items.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Utilities
import { computed, shallowRef, toRaw, watchEffect } from 'vue'
import { computed, shallowRef, watchEffect } from 'vue'
import { deepEqual, getPropertyFromItem, isPrimitive, omit, pick, propsFactory } from '@/util'

// Types
Expand Down Expand Up @@ -128,7 +128,6 @@ export function useItems (props: ItemProps) {
function transformIn (value: any[]): ListItem[] {
// Cache unrefed values outside the loop,
// proxy getters can be slow when you call them a billion times
const _value = toRaw(value)
const _items = itemsMap.value
const _allItems = items.value
const _keylessItems = keylessItems.value
Expand All @@ -146,7 +145,7 @@ export function useItems (props: ItemProps) {
])

const returnValue: ListItem[] = []
main: for (const v of _value) {
main: for (const v of value) {
// When the model value is null, return an InternalItem
// based on null only if null is one of the items
if (!_hasNullItem && v === null) continue
Expand Down

0 comments on commit 4df71de

Please sign in to comment.