From b47414a52f3485e159198b69cb657aaefb4b75f3 Mon Sep 17 00:00:00 2001 From: Dante Date: Wed, 18 Feb 2026 07:53:08 +0900 Subject: [PATCH] fix: prevent duplicate node search filters (#8935) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Add duplicate check in `addFilter` to prevent identical filter chips (same `filterDef.id` and `value`) from being added to the node search box ## Related Issue - Fixes https://github.com/Comfy-Org/ComfyUI_frontend/issues/3559 ## Changes - `NodeSearchBoxPopover.vue`: Guard `addFilter` with `isDuplicate` check comparing `filterDef.id` and `value` - `NodeSearchBoxPopover.test.ts`: Add unit tests covering duplicate prevention, distinct id, and distinct value cases ## QA - [x] `pnpm typecheck` passes - [x] `pnpm lint` passes - [x] `pnpm format:check` passes - [x] Unit tests pass (4/4) - [x] Bug reproduced with Playwright before fix ### as-is 스크린샷 2026-02-17 오후 5 45 48 ### to-be 스크린샷 2026-02-17 오후 5 44 25 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8935-fix-prevent-duplicate-node-search-filters-30a6d73d3650816797cfcc524228f270) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude Opus 4.6 --- browser_tests/tests/nodeSearchBox.spec.ts | 8 + .../searchbox/NodeSearchBoxPopover.test.ts | 173 ++++++++++++++++++ .../searchbox/NodeSearchBoxPopover.vue | 7 +- 3 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 src/components/searchbox/NodeSearchBoxPopover.test.ts diff --git a/browser_tests/tests/nodeSearchBox.spec.ts b/browser_tests/tests/nodeSearchBox.spec.ts index 89bfe4a5ec..4b50ee3ca3 100644 --- a/browser_tests/tests/nodeSearchBox.spec.ts +++ b/browser_tests/tests/nodeSearchBox.spec.ts @@ -215,6 +215,14 @@ test.describe('Node search box', { tag: '@node' }, () => { await expectFilterChips(comfyPage, ['MODEL', 'CLIP']) }) + test('Does not add duplicate filter with same type and value', async ({ + comfyPage + }) => { + await comfyPage.searchBox.addFilter('MODEL', 'Input Type') + await comfyPage.searchBox.addFilter('MODEL', 'Input Type') + await expectFilterChips(comfyPage, ['MODEL']) + }) + test('Can remove filter', async ({ comfyPage }) => { await comfyPage.searchBox.addFilter('MODEL', 'Input Type') await comfyPage.searchBox.removeFilter(0) diff --git a/src/components/searchbox/NodeSearchBoxPopover.test.ts b/src/components/searchbox/NodeSearchBoxPopover.test.ts new file mode 100644 index 0000000000..2f0da1dc89 --- /dev/null +++ b/src/components/searchbox/NodeSearchBoxPopover.test.ts @@ -0,0 +1,173 @@ +import { mount } from '@vue/test-utils' +import { createPinia, setActivePinia } from 'pinia' +import PrimeVue from 'primevue/config' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { defineComponent } from 'vue' +import { createI18n } from 'vue-i18n' + +import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore' +import type { FuseFilter, FuseFilterWithValue } from '@/utils/fuseUtil' + +import NodeSearchBoxPopover from './NodeSearchBoxPopover.vue' + +const mockStoreRefs = vi.hoisted(() => ({ + visible: { value: false }, + newSearchBoxEnabled: { value: true } +})) + +vi.mock('@/platform/settings/settingStore', () => ({ + useSettingStore: () => ({ + get: vi.fn() + }) +})) + +vi.mock('pinia', async () => { + const actual = await vi.importActual('pinia') + return { + ...(actual as Record), + storeToRefs: () => mockStoreRefs + } +}) + +vi.mock('@/stores/workspace/searchBoxStore', () => ({ + useSearchBoxStore: () => ({}) +})) + +vi.mock('@/services/litegraphService', () => ({ + useLitegraphService: () => ({ + getCanvasCenter: vi.fn(() => [0, 0]), + addNodeOnGraph: vi.fn() + }) +})) + +vi.mock('@/platform/workflow/management/stores/workflowStore', () => ({ + useWorkflowStore: () => ({ + activeWorkflow: null + }) +})) + +vi.mock('@/renderer/core/canvas/canvasStore', () => ({ + useCanvasStore: () => ({ + canvas: null, + getCanvas: vi.fn(() => ({ + linkConnector: { + events: new EventTarget(), + renderLinks: [] + } + })) + }) +})) + +vi.mock('@/stores/nodeDefStore', () => ({ + useNodeDefStore: () => ({ + nodeSearchService: { + nodeFilters: [], + inputTypeFilter: {}, + outputTypeFilter: {} + } + }) +})) + +const NodeSearchBoxStub = defineComponent({ + name: 'NodeSearchBox', + props: { + filters: { type: Array, default: () => [] } + }, + template: '