mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
## Summary Phase 3 of the VTL migration: migrate 8 hard-case component tests from @vue/test-utils to @testing-library/vue (68 tests). Stacked on #10490. ## Changes - **What**: Migrate SignInForm, CurrentUserButton, NodeSearchBoxPopover, BaseThumbnail, JobAssetsList, SelectionToolbox, QueueOverlayExpanded, PackVersionSelectorPopover from VTU to VTL - **`wrapper.vm` elimination**: 13 instances across 4 files (5 in SignInForm, 3 in CurrentUserButton, 3 in PackVersionSelectorPopover, 2 in BaseThumbnail) replaced with user interactions or removed - **`vm.$emit()` on stubs**: Interactive stubs with `setup(_, { emit })` expose buttons or closure-based emit functions (QueueOverlayExpanded, NodeSearchBoxPopover, JobAssetsList) - **Removed**: 6 change-detector/redundant tests, 3 `@ts-expect-error` annotations, `PackVersionSelectorVM` interface, `getVM` helper - **BaseThumbnail**: Removed `useEventListener` mock — real event handler attaches, `fireEvent.error(img)` triggers error state ## Review Focus - Interactive stub patterns: `JobAssetsListStub` and `NodeSearchBoxStub` use closure-based emit functions to trigger parent event handlers without `vm.$emit` - SignInForm form submission test fills PrimeVue Form fields via `userEvent.type` and submits via button click (replaces `vm.onSubmit()` direct call) - CurrentUserButton Popover stub tracks open/close state reactively - JobAssetsList: file-level `eslint-disable` for `no-container`/`no-node-access`/`prefer-user-event` since stubs lack ARIA roles and hover tests need `fireEvent` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-10493-test-migrate-8-hard-case-component-tests-from-VTU-to-VTL-Phase-3-32e6d73d365081f88097df634606d7e3) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp <amp@ampcode.com>
161 lines
4.3 KiB
TypeScript
161 lines
4.3 KiB
TypeScript
import { createTestingPinia } from '@pinia/testing'
|
|
import { render, screen } from '@testing-library/vue'
|
|
import PrimeVue from 'primevue/config'
|
|
import { describe, expect, it, vi } from 'vitest'
|
|
import { computed, defineComponent, nextTick } 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'
|
|
|
|
vi.mock('@/platform/settings/settingStore', () => ({
|
|
useSettingStore: () => ({
|
|
get: vi.fn()
|
|
})
|
|
}))
|
|
|
|
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: {}
|
|
}
|
|
})
|
|
}))
|
|
|
|
type EmitAddFilter = (
|
|
filter: FuseFilterWithValue<ComfyNodeDefImpl, string>
|
|
) => void
|
|
|
|
function createFilter(
|
|
id: string,
|
|
value: string
|
|
): FuseFilterWithValue<ComfyNodeDefImpl, string> {
|
|
return {
|
|
filterDef: { id } as FuseFilter<ComfyNodeDefImpl, string>,
|
|
value
|
|
}
|
|
}
|
|
|
|
describe('NodeSearchBoxPopover', () => {
|
|
const i18n = createI18n({
|
|
legacy: false,
|
|
locale: 'en',
|
|
messages: { en: {} }
|
|
})
|
|
|
|
function renderComponent() {
|
|
let emitAddFilter: EmitAddFilter | null = null
|
|
|
|
const NodeSearchBoxStub = defineComponent({
|
|
name: 'NodeSearchBox',
|
|
props: {
|
|
filters: { type: Array, default: () => [] }
|
|
},
|
|
emits: ['addFilter'],
|
|
setup(props, { emit }) {
|
|
emitAddFilter = (filter) => emit('addFilter', filter)
|
|
const filterCount = computed(() => props.filters.length)
|
|
return { filterCount }
|
|
},
|
|
template: '<output aria-label="filter count">{{ filterCount }}</output>'
|
|
})
|
|
|
|
const pinia = createTestingPinia({
|
|
stubActions: false,
|
|
initialState: {
|
|
searchBox: { visible: false }
|
|
}
|
|
})
|
|
|
|
const result = render(NodeSearchBoxPopover, {
|
|
global: {
|
|
plugins: [i18n, PrimeVue, pinia],
|
|
stubs: {
|
|
NodeSearchBox: NodeSearchBoxStub,
|
|
Dialog: {
|
|
template: '<div><slot name="container" /></div>',
|
|
props: ['visible', 'modal', 'dismissableMask', 'pt']
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
if (!emitAddFilter) throw new Error('NodeSearchBox stub did not mount')
|
|
|
|
return { ...result, emitAddFilter: emitAddFilter as EmitAddFilter }
|
|
}
|
|
|
|
describe('addFilter duplicate prevention', () => {
|
|
it('should add a filter when no duplicates exist', async () => {
|
|
const { emitAddFilter } = renderComponent()
|
|
|
|
emitAddFilter(createFilter('outputType', 'IMAGE'))
|
|
await nextTick()
|
|
|
|
expect(screen.getByLabelText('filter count')).toHaveTextContent('1')
|
|
})
|
|
|
|
it('should not add a duplicate filter with same id and value', async () => {
|
|
const { emitAddFilter } = renderComponent()
|
|
|
|
emitAddFilter(createFilter('outputType', 'IMAGE'))
|
|
await nextTick()
|
|
emitAddFilter(createFilter('outputType', 'IMAGE'))
|
|
await nextTick()
|
|
|
|
expect(screen.getByLabelText('filter count')).toHaveTextContent('1')
|
|
})
|
|
|
|
it('should allow filters with same id but different values', async () => {
|
|
const { emitAddFilter } = renderComponent()
|
|
|
|
emitAddFilter(createFilter('outputType', 'IMAGE'))
|
|
await nextTick()
|
|
emitAddFilter(createFilter('outputType', 'MASK'))
|
|
await nextTick()
|
|
|
|
expect(screen.getByLabelText('filter count')).toHaveTextContent('2')
|
|
})
|
|
|
|
it('should allow filters with different ids but same value', async () => {
|
|
const { emitAddFilter } = renderComponent()
|
|
|
|
emitAddFilter(createFilter('outputType', 'IMAGE'))
|
|
await nextTick()
|
|
emitAddFilter(createFilter('inputType', 'IMAGE'))
|
|
await nextTick()
|
|
|
|
expect(screen.getByLabelText('filter count')).toHaveTextContent('2')
|
|
})
|
|
})
|
|
})
|