Skip to content

Commit 4df71de

Browse files
committed
fix(VSelect): external model reactivity
fixes #20992 closes #20997
1 parent ab6eba4 commit 4df71de

File tree

2 files changed

+52
-3
lines changed

2 files changed

+52
-3
lines changed

packages/vuetify/src/components/VSelect/__tests__/VSelect.spec.browser.tsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { VListItem } from '@/components/VList'
55

66
// Utilities
77
import { commands, generate, render, screen, userEvent } from '@test'
8+
import { getAllByRole } from '@testing-library/vue'
89
import { cloneVNode, ref } from 'vue'
910

1011
const variants = ['underlined', 'outlined', 'filled', 'solo', 'plain'] as const
@@ -629,6 +630,55 @@ describe('VSelect', () => {
629630
items.value[0].title = 'Bar'
630631
await expect.poll(() => screen.getByText('Bar')).toBeVisible()
631632
})
633+
634+
it('adds a selection externally', async () => {
635+
const items = ref(['Foo', 'Bar'])
636+
const selection = ref(['Foo'])
637+
const { element } = render(() => (
638+
<VSelect v-model={ selection.value } items={ items.value } multiple />
639+
))
640+
641+
await userEvent.click(element)
642+
const menu = await screen.findByRole('listbox')
643+
await expect.element(menu).toBeVisible()
644+
645+
selection.value.push('Bar')
646+
await expect.poll(() => screen.getAllByText('Bar')).toHaveLength(2)
647+
expect(getAllByRole(menu, 'option', { selected: true })).toHaveLength(2)
648+
})
649+
650+
it('removes a selection externally', async () => {
651+
const items = ref(['Foo', 'Bar'])
652+
const selection = ref(['Foo', 'Bar'])
653+
const { element } = render(() => (
654+
<VSelect v-model={ selection.value } items={ items.value } multiple />
655+
))
656+
657+
await userEvent.click(element)
658+
const menu = await screen.findByRole('listbox')
659+
await expect.element(menu).toBeVisible()
660+
661+
selection.value.splice(1, 1)
662+
await expect.poll(() => screen.getAllByText('Bar')).toHaveLength(1)
663+
expect(getAllByRole(menu, 'option', { selected: true })).toHaveLength(1)
664+
})
665+
666+
it('adds a selected item', async () => {
667+
const items = ref(['Foo'])
668+
const selection = ref(['Foo'])
669+
const { element } = render(() => (
670+
<VSelect v-model={ selection.value } items={ items.value } multiple />
671+
))
672+
673+
await userEvent.click(element)
674+
const menu = await screen.findByRole('listbox')
675+
await expect.element(menu).toBeVisible()
676+
677+
items.value.push('Bar')
678+
selection.value.push('Bar')
679+
await expect.poll(() => screen.getAllByText('Bar')).toHaveLength(2)
680+
expect(getAllByRole(menu, 'option', { selected: true })).toHaveLength(2)
681+
})
632682
})
633683

634684
describe('Showcase', () => {

packages/vuetify/src/composables/list-items.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Utilities
2-
import { computed, shallowRef, toRaw, watchEffect } from 'vue'
2+
import { computed, shallowRef, watchEffect } from 'vue'
33
import { deepEqual, getPropertyFromItem, isPrimitive, omit, pick, propsFactory } from '@/util'
44

55
// Types
@@ -128,7 +128,6 @@ export function useItems (props: ItemProps) {
128128
function transformIn (value: any[]): ListItem[] {
129129
// Cache unrefed values outside the loop,
130130
// proxy getters can be slow when you call them a billion times
131-
const _value = toRaw(value)
132131
const _items = itemsMap.value
133132
const _allItems = items.value
134133
const _keylessItems = keylessItems.value
@@ -146,7 +145,7 @@ export function useItems (props: ItemProps) {
146145
])
147146

148147
const returnValue: ListItem[] = []
149-
main: for (const v of _value) {
148+
main: for (const v of value) {
150149
// When the model value is null, return an InternalItem
151150
// based on null only if null is one of the items
152151
if (!_hasNullItem && v === null) continue

0 commit comments

Comments
 (0)