From 3b5af4960f3910786b96c6ba7097020347b25b26 Mon Sep 17 00:00:00 2001 From: Alexander Brown Date: Mon, 9 Mar 2026 12:49:47 -0700 Subject: [PATCH] fix: show load widget inputs in media dropdown (#9670) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Main targeted, built on https://github.com/Comfy-Org/ComfyUI_frontend/pull/9551 ## Summary Fix Load Image/Load Video input dropdown tabs not showing available input assets in Vue node select dropdown. ## Changes - **What**: Keep combo widget `options` object identity while exposing dynamic `values` for cloud/remote combos. - **What**: Remove temporary debug logging and restore clearer dropdown filter branching. - **What**: Remove stale `searcher`/`updateKey` prop plumbing in dropdown menu/actions and update related tests. ## Review Focus Verify `Load Image` / `Load Video` Inputs tab behavior and confirm cloud/remote combo option values still update correctly. Relates to #9551 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9670-fix-show-load-widget-inputs-in-media-dropdown-31e6d73d36508148b845e18268a60c2a) by [Unito](https://www.unito.io) --------- Co-authored-by: bymyself Co-authored-by: Amp --- .../form/dropdown/FormDropdown.test.ts | 115 ++++++++++++++++++ .../components/form/dropdown/FormDropdown.vue | 29 ++--- .../form/dropdown/FormDropdownMenu.vue | 11 +- .../form/dropdown/FormDropdownMenuActions.vue | 9 -- .../composables/useComboWidget.test.ts | 30 ++++- .../widgets/composables/useComboWidget.ts | 59 +++++---- 6 files changed, 187 insertions(+), 66 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 0000000000..50765a9846 --- /dev/null +++ b/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.test.ts @@ -0,0 +1,115 @@ +import { flushPromises, 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 } 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: () => [] }, + 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 flushPromises() + + expect(getMenuItems(wrapper)).toHaveLength(2) + + await wrapper.setProps({ + items: [ + createItem('output-0', 'rendered1.mp4'), + createItem('output-1', 'rendered2.mp4') + ] + }) + await flushPromises() + + 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 flushPromises() + + await wrapper.setProps({ items: [createItem('1', 'beta')] }) + await flushPromises() + + expect(getMenuItems(wrapper)[0].name).toBe('beta') + }) + + it('updates when switching between empty and non-empty items', async () => { + const wrapper = mountDropdown([]) + await flushPromises() + + expect(getMenuItems(wrapper)).toHaveLength(0) + + await wrapper.setProps({ items: [createItem('1', 'video.mp4')] }) + await flushPromises() + + expect(getMenuItems(wrapper)).toHaveLength(1) + expect(getMenuItems(wrapper)[0].name).toBe('video.mp4') + + await wrapper.setProps({ items: [] }) + await flushPromises() + + 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 12c30a1ab8..a9574e566f 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue @@ -1,4 +1,5 @@