From eb1153d83657b1bbaa0c41b23972d170fc142185 Mon Sep 17 00:00:00 2001 From: bymyself Date: Sat, 7 Mar 2026 16:21:38 -0800 Subject: [PATCH 1/5] fix: update FormDropdown filteredItems when items prop changes Replace computed itemsKey with a Symbol ref and add a watcher that re-runs the searcher whenever the items prop changes. This fixes the Load Video node not showing videos in the Media Assets panel dropdown when switching between asset types with identical or empty item sets. --- .../form/dropdown/FormDropdown.test.ts | 124 ++++++++++++++++++ .../components/form/dropdown/FormDropdown.vue | 18 ++- 2 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.test.ts diff --git a/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.test.ts b/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.test.ts new file mode 100644 index 00000000000..77c1e689941 --- /dev/null +++ b/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.test.ts @@ -0,0 +1,124 @@ +import { mount } from '@vue/test-utils' +import PrimeVue from 'primevue/config' +import { createI18n } from 'vue-i18n' +import { describe, expect, it, vi } from 'vitest' +import { defineComponent, h, nextTick } from 'vue' + +import FormDropdown from './FormDropdown.vue' +import type { FormDropdownItem } from './types' + +function createItem(id: string, name: string): FormDropdownItem { + return { id, preview_url: '', name, label: name } +} + +const i18n = createI18n({ legacy: false, locale: 'en', messages: { en: {} } }) + +vi.mock('@/platform/updates/common/toastStore', () => ({ + useToastStore: () => ({ + addAlert: vi.fn() + }) +})) + +const MockFormDropdownMenu = defineComponent({ + name: 'FormDropdownMenu', + props: { + items: { type: Array as () => FormDropdownItem[], default: () => [] }, + updateKey: { type: null, default: undefined }, + searcher: { type: Function, default: undefined }, + isSelected: { type: Function, default: undefined }, + filterOptions: { type: Array, default: () => [] }, + sortOptions: { type: Array, default: () => [] }, + maxSelectable: { type: Number, default: 1 }, + disabled: { type: Boolean, default: false }, + showOwnershipFilter: { type: Boolean, default: false }, + ownershipOptions: { type: Array, default: () => [] }, + showBaseModelFilter: { type: Boolean, default: false }, + baseModelOptions: { type: Array, default: () => [] } + }, + setup() { + return () => h('div', { class: 'mock-menu' }) + } +}) + +function mountDropdown(items: FormDropdownItem[]) { + return mount(FormDropdown, { + props: { items }, + global: { + plugins: [PrimeVue, i18n], + stubs: { + FormDropdownInput: true, + Popover: { template: '
' }, + FormDropdownMenu: MockFormDropdownMenu + } + } + }) +} + +function getMenuItems( + wrapper: ReturnType +): FormDropdownItem[] { + return wrapper + .findComponent(MockFormDropdownMenu) + .props('items') as FormDropdownItem[] +} + +describe('FormDropdown', () => { + describe('filteredItems updates when items prop changes', () => { + it('updates displayed items when items prop changes', async () => { + const wrapper = mountDropdown([ + createItem('input-0', 'video1.mp4'), + createItem('input-1', 'video2.mp4') + ]) + await nextTick() + await nextTick() + + expect(getMenuItems(wrapper)).toHaveLength(2) + + await wrapper.setProps({ + items: [ + createItem('output-0', 'rendered1.mp4'), + createItem('output-1', 'rendered2.mp4') + ] + }) + await nextTick() + await nextTick() + + const menuItems = getMenuItems(wrapper) + expect(menuItems).toHaveLength(2) + expect(menuItems[0].name).toBe('rendered1.mp4') + }) + + it('updates when items change but IDs stay the same', async () => { + const wrapper = mountDropdown([createItem('1', 'alpha')]) + await nextTick() + await nextTick() + + await wrapper.setProps({ items: [createItem('1', 'beta')] }) + await nextTick() + await nextTick() + + expect(getMenuItems(wrapper)[0].name).toBe('beta') + }) + + it('updates when switching between empty and non-empty items', async () => { + const wrapper = mountDropdown([]) + await nextTick() + await nextTick() + + expect(getMenuItems(wrapper)).toHaveLength(0) + + await wrapper.setProps({ items: [createItem('1', 'video.mp4')] }) + await nextTick() + await nextTick() + + expect(getMenuItems(wrapper)).toHaveLength(1) + expect(getMenuItems(wrapper)[0].name).toBe('video.mp4') + + await wrapper.setProps({ items: [] }) + await nextTick() + await nextTick() + + expect(getMenuItems(wrapper)).toHaveLength(0) + }) + }) +}) diff --git a/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue b/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue index 12c30a1ab8c..5715f1211e8 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue @@ -1,6 +1,6 @@