expand and simplify tests

This commit is contained in:
pythongosssss
2026-04-15 05:03:06 -07:00
parent 5796c6d464
commit e4059303d4
6 changed files with 648 additions and 304 deletions

View File

@@ -1,7 +1,6 @@
import { render, screen, waitFor } from '@testing-library/vue'
import userEvent from '@testing-library/user-event'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { nextTick } from 'vue'
import NodeSearchCategorySidebar from '@/components/searchbox/v2/NodeSearchCategorySidebar.vue'
import {
@@ -11,16 +10,12 @@ import {
} from '@/components/searchbox/v2/__test__/testUtils'
import { useNodeDefStore } from '@/stores/nodeDefStore'
vi.mock('@/platform/settings/settingStore', () => ({
useSettingStore: vi.fn(() => ({
get: vi.fn((key: string) => {
if (key === 'Comfy.NodeLibrary.Bookmarks.V2') return []
if (key === 'Comfy.NodeLibrary.BookmarksCustomization') return {}
return undefined
}),
set: vi.fn()
}))
}))
type SidebarProps = Partial<{
selectedCategory: string
hidePresets: boolean
rootLabel: string
rootKey: string
}>
describe('NodeSearchCategorySidebar', () => {
beforeEach(() => {
@@ -28,35 +23,30 @@ describe('NodeSearchCategorySidebar', () => {
setupTestPinia()
})
async function createRender(props = {}) {
function createRender(props: SidebarProps = {}) {
const user = userEvent.setup()
const onUpdateSelectedCategory = vi.fn()
const baseProps = { selectedCategory: 'most-relevant', ...props }
let currentProps = { ...baseProps }
let rerenderFn: (
p: typeof baseProps & Record<string, unknown>
) => void = () => {}
function makeProps(overrides = {}) {
const merged = { ...currentProps, ...overrides }
return {
...merged,
'onUpdate:selectedCategory': (val: string) => {
onUpdateSelectedCategory(val)
currentProps = { ...currentProps, selectedCategory: val }
rerenderFn(makeProps())
}
}
const onUpdateSelectedCategory = vi.fn<(value: string) => void>()
const initialProps: SidebarProps & { selectedCategory: string } = {
selectedCategory: 'most-relevant',
...props
}
const result = render(NodeSearchCategorySidebar, {
props: makeProps(),
props: {
...initialProps,
'onUpdate:selectedCategory': onUpdateSelectedCategory
},
global: { plugins: [testI18n] }
})
rerenderFn = (p) => result.rerender(p)
await nextTick()
return { user, onUpdateSelectedCategory }
const rerender = (overrides: SidebarProps) =>
result.rerender({
...initialProps,
...overrides,
'onUpdate:selectedCategory': onUpdateSelectedCategory
})
return { user, onUpdateSelectedCategory, rerender }
}
async function clickCategory(
@@ -73,18 +63,17 @@ describe('NodeSearchCategorySidebar', () => {
)
expect(btn, `Expected to find a button with text "${text}"`).toBeDefined()
await user.click(btn!)
await nextTick()
}
describe('preset categories', () => {
it('should render Most relevant preset category', async () => {
await createRender()
it('should render Most relevant preset category', () => {
createRender()
expect(screen.getByText('Most relevant')).toBeInTheDocument()
})
it('should mark the selected preset category as selected', async () => {
await createRender({ selectedCategory: 'most-relevant' })
it('should mark the selected preset category as selected', () => {
createRender({ selectedCategory: 'most-relevant' })
expect(screen.getByTestId('category-most-relevant')).toHaveAttribute(
'aria-current',
@@ -93,15 +82,15 @@ describe('NodeSearchCategorySidebar', () => {
})
it('should emit update:selectedCategory when preset is clicked', async () => {
const { user, onUpdateSelectedCategory } = await createRender({
const { user, onUpdateSelectedCategory } = createRender({
selectedCategory: 'most-relevant'
})
useNodeDefStore().updateNodeDefs([
createMockNodeDef({ name: 'Node1', category: 'sampling' })
])
await nextTick()
await screen.findByText('sampling')
await clickCategory(user, 'sampling')
expect(onUpdateSelectedCategory).toHaveBeenCalledWith('sampling')
@@ -109,15 +98,14 @@ describe('NodeSearchCategorySidebar', () => {
})
describe('category tree', () => {
it('should render top-level categories from node definitions', async () => {
it('should render top-level categories from node definitions', () => {
useNodeDefStore().updateNodeDefs([
createMockNodeDef({ name: 'Node1', category: 'sampling' }),
createMockNodeDef({ name: 'Node2', category: 'loaders' }),
createMockNodeDef({ name: 'Node3', category: 'conditioning' })
])
await nextTick()
await createRender()
createRender()
expect(screen.getByText('sampling')).toBeInTheDocument()
expect(screen.getByText('loaders')).toBeInTheDocument()
@@ -128,9 +116,8 @@ describe('NodeSearchCategorySidebar', () => {
useNodeDefStore().updateNodeDefs([
createMockNodeDef({ name: 'Node1', category: 'sampling' })
])
await nextTick()
const { user, onUpdateSelectedCategory } = await createRender()
const { user, onUpdateSelectedCategory } = createRender()
await clickCategory(user, 'sampling')
@@ -146,18 +133,15 @@ describe('NodeSearchCategorySidebar', () => {
createMockNodeDef({ name: 'Node3', category: 'sampling/basic' }),
createMockNodeDef({ name: 'Node4', category: 'loaders' })
])
await nextTick()
const { user } = await createRender()
const { user } = createRender()
expect(screen.queryByText('advanced')).not.toBeInTheDocument()
await clickCategory(user, 'sampling')
await waitFor(() => {
expect(screen.getByText('advanced')).toBeInTheDocument()
expect(screen.getByText('basic')).toBeInTheDocument()
})
await screen.findByText('advanced')
expect(screen.getByText('basic')).toBeInTheDocument()
})
it('should collapse sibling category when another is expanded', async () => {
@@ -167,15 +151,12 @@ describe('NodeSearchCategorySidebar', () => {
createMockNodeDef({ name: 'Node3', category: 'image' }),
createMockNodeDef({ name: 'Node4', category: 'image/upscale' })
])
await nextTick()
const { user } = await createRender()
const { user } = createRender()
// Expand sampling
await clickCategory(user, 'sampling', true)
await waitFor(() => {
expect(screen.getByText('advanced')).toBeInTheDocument()
})
await screen.findByText('advanced')
// Expand image — sampling should collapse
await clickCategory(user, 'image', true)
@@ -192,15 +173,12 @@ describe('NodeSearchCategorySidebar', () => {
createMockNodeDef({ name: 'Node2', category: 'sampling/advanced' }),
createMockNodeDef({ name: 'Node3', category: 'loaders' })
])
await nextTick()
const { user, onUpdateSelectedCategory } = await createRender()
const { user, onUpdateSelectedCategory } = createRender()
// Expand sampling category
await clickCategory(user, 'sampling', true)
await waitFor(() => {
expect(screen.getByText('advanced')).toBeInTheDocument()
})
await screen.findByText('advanced')
// Click on advanced subcategory
await clickCategory(user, 'advanced')
@@ -211,13 +189,12 @@ describe('NodeSearchCategorySidebar', () => {
})
describe('category selection highlighting', () => {
it('should mark selected top-level category as selected', async () => {
it('should mark selected top-level category as selected', () => {
useNodeDefStore().updateNodeDefs([
createMockNodeDef({ name: 'Node1', category: 'sampling' })
])
await nextTick()
await createRender({ selectedCategory: 'sampling' })
createRender({ selectedCategory: 'sampling' })
expect(screen.getByTestId('category-sampling')).toHaveAttribute(
'aria-current',
@@ -231,17 +208,14 @@ describe('NodeSearchCategorySidebar', () => {
createMockNodeDef({ name: 'Node2', category: 'sampling/advanced' }),
createMockNodeDef({ name: 'Node3', category: 'loaders' })
])
await nextTick()
const { user, onUpdateSelectedCategory } = await createRender({
const { user, onUpdateSelectedCategory } = createRender({
selectedCategory: 'most-relevant'
})
// Expand and click subcategory
await clickCategory(user, 'sampling', true)
await waitFor(() => {
expect(screen.getByText('advanced')).toBeInTheDocument()
})
await screen.findByText('advanced')
await clickCategory(user, 'advanced')
const calls = onUpdateSelectedCategory.mock.calls
@@ -250,8 +224,8 @@ describe('NodeSearchCategorySidebar', () => {
})
describe('hidePresets prop', () => {
it('should hide preset categories when hidePresets is true', async () => {
await createRender({ hidePresets: true })
it('should hide preset categories when hidePresets is true', () => {
createRender({ hidePresets: true })
expect(screen.queryByText('Most relevant')).not.toBeInTheDocument()
})
@@ -261,21 +235,88 @@ describe('NodeSearchCategorySidebar', () => {
useNodeDefStore().updateNodeDefs([
createMockNodeDef({ name: 'Node1', category: 'sampling' })
])
await nextTick()
const { user, onUpdateSelectedCategory } = await createRender()
const { user, onUpdateSelectedCategory } = createRender()
await clickCategory(user, 'sampling')
expect(onUpdateSelectedCategory).toHaveBeenCalledWith('sampling')
})
it('should emit autoExpand when there is a single root category', async () => {
describe('rootLabel wrapping', () => {
it('should wrap multiple top-level categories under rootLabel key', async () => {
useNodeDefStore().updateNodeDefs([
createMockNodeDef({ name: 'N1', category: 'sampling' }),
createMockNodeDef({ name: 'N2', category: 'loaders' })
])
const { user, onUpdateSelectedCategory } = createRender({
rootLabel: 'Extensions',
rootKey: 'custom'
})
expect(screen.getByText('Extensions')).toBeInTheDocument()
// Expand the wrapper root
const customBtn = screen.getByTestId('category-custom')
expect(customBtn).toBeInTheDocument()
await user.click(customBtn)
await waitFor(() => {
expect(screen.getByText('sampling')).toBeInTheDocument()
expect(screen.getByText('loaders')).toBeInTheDocument()
})
// Subcategories should be prefixed with the root key
expect(screen.getByTestId('category-custom/sampling')).toBeInTheDocument()
await user.click(screen.getByTestId('category-custom/sampling'))
const calls = onUpdateSelectedCategory.mock.calls
expect(calls[calls.length - 1]).toEqual(['custom/sampling'])
})
it('should derive root key from rootLabel when rootKey is not provided', async () => {
useNodeDefStore().updateNodeDefs([
createMockNodeDef({ name: 'N1', category: 'sampling' }),
createMockNodeDef({ name: 'N2', category: 'loaders' })
])
const { user, onUpdateSelectedCategory } = createRender({
rootLabel: 'Custom'
})
await user.click(screen.getByTestId('category-custom'))
await user.click(await screen.findByTestId('category-custom/sampling'))
const calls = onUpdateSelectedCategory.mock.calls
expect(calls[calls.length - 1]).toEqual(['custom/sampling'])
})
})
describe('external selectedCategory updates', () => {
it('should update expanded state when selectedCategory changes externally', async () => {
useNodeDefStore().updateNodeDefs([
createMockNodeDef({ name: 'Node1', category: 'sampling' }),
createMockNodeDef({ name: 'Node2', category: 'sampling/advanced' }),
createMockNodeDef({ name: 'Node3', category: 'loaders' })
])
const { rerender } = createRender({
selectedCategory: 'most-relevant'
})
expect(screen.queryByText('advanced')).not.toBeInTheDocument()
await rerender({ selectedCategory: 'sampling' })
await screen.findByText('advanced')
})
})
it('should emit autoExpand when there is a single root category', () => {
useNodeDefStore().updateNodeDefs([
createMockNodeDef({ name: 'Node1', category: 'api' }),
createMockNodeDef({ name: 'Node2', category: 'api/image' })
])
await nextTick()
const onAutoExpand = vi.fn()
render(NodeSearchCategorySidebar, {
@@ -285,7 +326,6 @@ describe('NodeSearchCategorySidebar', () => {
},
global: { plugins: [testI18n] }
})
await nextTick()
expect(onAutoExpand).toHaveBeenCalledWith('api')
})
@@ -296,9 +336,8 @@ describe('NodeSearchCategorySidebar', () => {
createMockNodeDef({ name: 'Node2', category: 'api/image' }),
createMockNodeDef({ name: 'Node3', category: 'api/image/BFL' })
])
await nextTick()
const { user, onUpdateSelectedCategory } = await createRender()
const { user, onUpdateSelectedCategory } = createRender()
// Only top-level visible initially
expect(screen.getByText('api')).toBeInTheDocument()
@@ -307,16 +346,12 @@ describe('NodeSearchCategorySidebar', () => {
// Expand api
await clickCategory(user, 'api', true)
await waitFor(() => {
expect(screen.getByText('image')).toBeInTheDocument()
})
await screen.findByText('image')
expect(screen.queryByText('BFL')).not.toBeInTheDocument()
// Expand image
await clickCategory(user, 'image', true)
await waitFor(() => {
expect(screen.getByText('BFL')).toBeInTheDocument()
})
await screen.findByText('BFL')
// Click BFL and verify emission
await clickCategory(user, 'BFL', true)
@@ -332,16 +367,14 @@ describe('NodeSearchCategorySidebar', () => {
createMockNodeDef({ name: 'Node2', category: 'sampling/advanced' }),
createMockNodeDef({ name: 'Node3', category: 'loaders' })
])
await nextTick()
const { user, onUpdateSelectedCategory } = await createRender()
const { user, onUpdateSelectedCategory } = createRender()
expect(screen.queryByText('advanced')).not.toBeInTheDocument()
const samplingBtn = screen.getByTestId('category-sampling')
samplingBtn.focus()
await user.keyboard('{ArrowRight}')
await nextTick()
// Should have emitted select for sampling, expanding it
expect(onUpdateSelectedCategory).toHaveBeenCalledWith('sampling')
@@ -353,23 +386,21 @@ describe('NodeSearchCategorySidebar', () => {
createMockNodeDef({ name: 'Node2', category: 'sampling/advanced' }),
createMockNodeDef({ name: 'Node3', category: 'loaders' })
])
await nextTick()
const { user } = await createRender()
const { user } = createRender()
// First expand sampling by clicking
await clickCategory(user, 'sampling', true)
await waitFor(() => {
expect(screen.getByText('advanced')).toBeInTheDocument()
})
await screen.findByText('advanced')
const samplingBtn = screen.getByTestId('category-sampling')
samplingBtn.focus()
await user.keyboard('{ArrowLeft}')
await nextTick()
// Collapse toggles internal state; children should be hidden
expect(screen.queryByText('advanced')).not.toBeInTheDocument()
await waitFor(() => {
expect(screen.queryByText('advanced')).not.toBeInTheDocument()
})
})
it('should focus first child on ArrowRight when already expanded', async () => {
@@ -378,20 +409,18 @@ describe('NodeSearchCategorySidebar', () => {
createMockNodeDef({ name: 'Node2', category: 'sampling/advanced' }),
createMockNodeDef({ name: 'Node3', category: 'loaders' })
])
await nextTick()
const { user } = await createRender()
const { user } = createRender()
await clickCategory(user, 'sampling', true)
await waitFor(() => {
expect(screen.getByText('advanced')).toBeInTheDocument()
})
await screen.findByText('advanced')
const samplingBtn = screen.getByTestId('category-sampling')
samplingBtn.focus()
await user.keyboard('{ArrowRight}')
await nextTick()
expect(screen.getByTestId('category-sampling/advanced')).toHaveFocus()
await waitFor(() => {
expect(screen.getByTestId('category-sampling/advanced')).toHaveFocus()
})
})
it('should focus parent on ArrowLeft from a leaf or collapsed node', async () => {
@@ -400,19 +429,17 @@ describe('NodeSearchCategorySidebar', () => {
createMockNodeDef({ name: 'Node2', category: 'sampling/advanced' }),
createMockNodeDef({ name: 'Node3', category: 'loaders' })
])
await nextTick()
const { user } = await createRender()
const { user } = createRender()
await clickCategory(user, 'sampling', true)
await waitFor(() => {
expect(screen.getByText('advanced')).toBeInTheDocument()
})
await screen.findByText('advanced')
screen.getByTestId('category-sampling/advanced').focus()
await user.keyboard('{ArrowLeft}')
await nextTick()
expect(screen.getByTestId('category-sampling')).toHaveFocus()
await waitFor(() => {
expect(screen.getByTestId('category-sampling')).toHaveFocus()
})
})
it('should collapse sampling on ArrowLeft, not just its expanded child', async () => {
@@ -428,33 +455,28 @@ describe('NodeSearchCategorySidebar', () => {
}),
createMockNodeDef({ name: 'Node4', category: 'loaders' })
])
await nextTick()
const { user } = await createRender()
const { user } = createRender()
// Step 1: Expand sampling
await clickCategory(user, 'sampling', true)
await waitFor(() => {
expect(screen.getByText('custom_sampling')).toBeInTheDocument()
})
await screen.findByText('custom_sampling')
// Step 2: Expand custom_sampling
await clickCategory(user, 'custom_sampling', true)
await waitFor(() => {
expect(screen.getByText('child')).toBeInTheDocument()
})
await screen.findByText('child')
// Step 3: Navigate back to sampling (keyboard focus only)
const samplingBtn = screen.getByTestId('category-sampling')
samplingBtn.focus()
await nextTick()
// Step 4: Press left on sampling
await user.keyboard('{ArrowLeft}')
await nextTick()
// Sampling should collapse entirely — custom_sampling should not be visible
expect(screen.queryByText('custom_sampling')).not.toBeInTheDocument()
await waitFor(() => {
expect(screen.queryByText('custom_sampling')).not.toBeInTheDocument()
})
})
it('should collapse 4-deep tree to parent of level 2 on ArrowLeft', async () => {
@@ -465,50 +487,42 @@ describe('NodeSearchCategorySidebar', () => {
createMockNodeDef({ name: 'N4', category: 'a/b/c/d' }),
createMockNodeDef({ name: 'N5', category: 'other' })
])
await nextTick()
const { user } = await createRender()
const { user } = createRender()
// Expand a → a/b → a/b/c
await clickCategory(user, 'a', true)
await waitFor(() => {
expect(screen.getByText('b')).toBeInTheDocument()
})
await screen.findByText('b')
await clickCategory(user, 'b', true)
await waitFor(() => {
expect(screen.getByText('c')).toBeInTheDocument()
})
await screen.findByText('c')
await clickCategory(user, 'c', true)
await waitFor(() => {
expect(screen.getByText('d')).toBeInTheDocument()
})
await screen.findByText('d')
// Focus level 2 (a/b) and press ArrowLeft
const bBtn = screen.getByTestId('category-a/b')
bBtn.focus()
await nextTick()
await user.keyboard('{ArrowLeft}')
await nextTick()
// Level 2 and below should collapse, but level 1 (a) stays expanded
// so 'b' is still visible but 'c' and 'd' are not
await waitFor(() => {
expect(screen.queryByText('c')).not.toBeInTheDocument()
})
expect(screen.getByText('b')).toBeInTheDocument()
expect(screen.queryByText('c')).not.toBeInTheDocument()
expect(screen.queryByText('d')).not.toBeInTheDocument()
})
it('should set aria-expanded on tree nodes with children', async () => {
it('should set aria-expanded on tree nodes with children', () => {
useNodeDefStore().updateNodeDefs([
createMockNodeDef({ name: 'Node1', category: 'sampling' }),
createMockNodeDef({ name: 'Node2', category: 'sampling/advanced' }),
createMockNodeDef({ name: 'Node3', category: 'loaders' })
])
await nextTick()
await createRender()
createRender()
expect(screen.getByTestId('category-sampling')).toHaveAttribute(
'aria-expanded',

View File

@@ -1,7 +1,6 @@
import { render, screen } from '@testing-library/vue'
import { render, screen, waitFor } from '@testing-library/vue'
import userEvent from '@testing-library/user-event'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { nextTick } from 'vue'
import NodeSearchContent from '@/components/searchbox/v2/NodeSearchContent.vue'
import {
@@ -9,33 +8,28 @@ import {
setupTestPinia,
testI18n
} from '@/components/searchbox/v2/__test__/testUtils'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useNodeBookmarkStore } from '@/stores/nodeBookmarkStore'
import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
import { useNodeDefStore, useNodeFrequencyStore } from '@/stores/nodeDefStore'
import { NodeSourceType } from '@/types/nodeSource'
vi.mock('@/platform/settings/settingStore', () => ({
useSettingStore: vi.fn(() => ({
get: vi.fn((key: string) => {
if (key === 'Comfy.NodeLibrary.Bookmarks.V2') return []
if (key === 'Comfy.NodeLibrary.BookmarksCustomization') return {}
return undefined
}),
set: vi.fn()
}))
}))
import type { FuseFilterWithValue } from '@/utils/fuseUtil'
describe('NodeSearchContent', () => {
beforeEach(() => {
setupTestPinia()
vi.restoreAllMocks()
const settings = useSettingStore()
settings.settingValues['Comfy.NodeLibrary.Bookmarks.V2'] = []
settings.settingValues['Comfy.NodeLibrary.BookmarksCustomization'] = {}
})
async function renderComponent(props = {}) {
function renderComponent(props = {}) {
const user = userEvent.setup()
const onAddNode = vi.fn()
const onHoverNode = vi.fn()
const onRemoveFilter = vi.fn()
const onRemoveFilter =
vi.fn<(f: FuseFilterWithValue<ComfyNodeDefImpl, string>) => void>()
const onAddFilter = vi.fn()
render(NodeSearchContent, {
props: {
@@ -63,7 +57,6 @@ describe('NodeSearchContent', () => {
}
}
})
await nextTick()
return { user, onAddNode, onHoverNode, onRemoveFilter, onAddFilter }
}
@@ -96,9 +89,8 @@ describe('NodeSearchContent', () => {
) {
useNodeDefStore().updateNodeDefs(nodes.map(createMockNodeDef))
mockBookmarks(true, ['placeholder'])
const result = await renderComponent()
const result = renderComponent()
await clickFilterBarButton(result.user, 'Bookmarked')
await nextTick()
return result
}
@@ -116,11 +108,13 @@ describe('NodeSearchContent', () => {
useNodeDefStore().nodeDefsByName['FrequentNode']
])
await renderComponent()
renderComponent()
const items = screen.getAllByTestId('node-item')
expect(items).toHaveLength(1)
expect(items[0]).toHaveTextContent('Frequent Node')
await waitFor(() => {
const items = screen.getAllByTestId('node-item')
expect(items).toHaveLength(1)
expect(items[0]).toHaveTextContent('Frequent Node')
})
})
it('should show only bookmarked nodes when Favorites is selected', async () => {
@@ -139,13 +133,14 @@ describe('NodeSearchContent', () => {
['BookmarkedNode']
)
const { user } = await renderComponent()
const { user } = renderComponent()
await clickFilterBarButton(user, 'Bookmarked')
await nextTick()
const items = screen.getAllByTestId('node-item')
expect(items).toHaveLength(1)
expect(items[0]).toHaveTextContent('Bookmarked')
await waitFor(() => {
const items = screen.getAllByTestId('node-item')
expect(items).toHaveLength(1)
expect(items[0]).toHaveTextContent('Bookmarked')
})
})
it('should show empty state when no bookmarks exist', async () => {
@@ -154,11 +149,10 @@ describe('NodeSearchContent', () => {
])
mockBookmarks(false, ['placeholder'])
const { user } = await renderComponent()
const { user } = renderComponent()
await clickFilterBarButton(user, 'Bookmarked')
await nextTick()
expect(screen.getByText('No results')).toBeInTheDocument()
expect(await screen.findByText('No Results')).toBeInTheDocument()
})
it('should show only CustomNodes when Extensions is selected', async () => {
@@ -174,7 +168,6 @@ describe('NodeSearchContent', () => {
python_module: 'custom_nodes.my_extension'
})
])
await nextTick()
expect(useNodeDefStore().nodeDefsByName['CoreNode'].nodeSource.type).toBe(
NodeSourceType.Core
@@ -183,16 +176,17 @@ describe('NodeSearchContent', () => {
useNodeDefStore().nodeDefsByName['CustomNode'].nodeSource.type
).toBe(NodeSourceType.CustomNodes)
const { user } = await renderComponent()
const { user } = renderComponent()
await clickFilterBarButton(user, 'Extensions')
await nextTick()
const items = screen.getAllByTestId('node-item')
expect(items).toHaveLength(1)
expect(items[0]).toHaveTextContent('Custom Node')
await waitFor(() => {
const items = screen.getAllByTestId('node-item')
expect(items).toHaveLength(1)
expect(items[0]).toHaveTextContent('Custom Node')
})
})
it('should hide Essentials filter button when no essential nodes exist', async () => {
it('should hide Essentials filter button when no essential nodes exist', () => {
useNodeDefStore().updateNodeDefs([
createMockNodeDef({
name: 'RegularNode',
@@ -200,7 +194,7 @@ describe('NodeSearchContent', () => {
})
])
await renderComponent()
renderComponent()
const texts = screen
.getAllByRole('button')
.map((b) => b.textContent?.trim())
@@ -219,15 +213,15 @@ describe('NodeSearchContent', () => {
display_name: 'Regular Node'
})
])
await nextTick()
const { user } = await renderComponent()
const { user } = renderComponent()
await clickFilterBarButton(user, 'Essentials')
await nextTick()
const items = screen.getAllByTestId('node-item')
expect(items).toHaveLength(1)
expect(items[0]).toHaveTextContent('Essential Node')
await waitFor(() => {
const items = screen.getAllByTestId('node-item')
expect(items).toHaveLength(1)
expect(items[0]).toHaveTextContent('Essential Node')
})
})
it('should show only API nodes when Partner Nodes filter is active', async () => {
@@ -243,13 +237,14 @@ describe('NodeSearchContent', () => {
})
])
const { user } = await renderComponent()
const { user } = renderComponent()
await clickFilterBarButton(user, 'Partner')
await nextTick()
const items = screen.getAllByTestId('node-item')
expect(items).toHaveLength(1)
expect(items[0]).toHaveTextContent('API Node')
await waitFor(() => {
const items = screen.getAllByTestId('node-item')
expect(items).toHaveLength(1)
expect(items[0]).toHaveTextContent('API Node')
})
})
it('should toggle filter off when clicking the active filter button again', async () => {
@@ -265,22 +260,23 @@ describe('NodeSearchContent', () => {
python_module: 'custom_nodes.my_extension'
})
])
await nextTick()
vi.spyOn(useNodeFrequencyStore(), 'topNodeDefs', 'get').mockReturnValue([
useNodeDefStore().nodeDefsByName['CoreNode'],
useNodeDefStore().nodeDefsByName['CustomNode']
])
const { user } = await renderComponent()
const { user } = renderComponent()
await clickFilterBarButton(user, 'Extensions')
await nextTick()
expect(screen.getAllByTestId('node-item')).toHaveLength(1)
await waitFor(() => {
expect(screen.getAllByTestId('node-item')).toHaveLength(1)
})
await clickFilterBarButton(user, 'Extensions')
await nextTick()
expect(screen.getAllByTestId('node-item')).toHaveLength(2)
await waitFor(() => {
expect(screen.getAllByTestId('node-item')).toHaveLength(2)
})
})
it('should include subcategory nodes when parent category is selected', async () => {
@@ -302,12 +298,13 @@ describe('NodeSearchContent', () => {
})
])
const { user } = await renderComponent()
await user.click(screen.getByTestId('category-sampling'))
await nextTick()
const { user } = renderComponent()
await user.click(await screen.findByTestId('category-sampling'))
await waitFor(() => {
expect(screen.getAllByTestId('node-item')).toHaveLength(2)
})
const texts = screen.getAllByTestId('node-item').map((i) => i.textContent)
expect(texts).toHaveLength(2)
expect(texts).toContain('KSampler')
expect(texts).toContain('KSampler Advanced')
})
@@ -328,20 +325,22 @@ describe('NodeSearchContent', () => {
})
])
const { user } = await renderComponent()
await user.click(screen.getByTestId('category-sampling'))
await nextTick()
const { user } = renderComponent()
await user.click(await screen.findByTestId('category-sampling'))
expect(screen.getAllByTestId('node-item')).toHaveLength(1)
await waitFor(() => {
expect(screen.getAllByTestId('node-item')).toHaveLength(1)
})
const input = screen.getByRole('combobox')
await user.type(input, 'Load')
await nextTick()
const texts = screen
.queryAllByTestId('node-item')
.map((i) => i.textContent)
expect(texts.some((t) => t?.includes('Load Checkpoint'))).toBe(false)
await waitFor(() => {
const texts = screen
.queryAllByTestId('node-item')
.map((i) => i.textContent)
expect(texts.some((t) => t?.includes('Load Checkpoint'))).toBe(false)
})
})
it('should preserve search query when category changes', async () => {
@@ -350,15 +349,13 @@ describe('NodeSearchContent', () => {
])
mockBookmarks(true, ['placeholder'])
const { user } = await renderComponent()
const { user } = renderComponent()
const input = screen.getByRole('combobox')
await user.type(input, 'test query')
await nextTick()
expect(input).toHaveValue('test query')
await clickFilterBarButton(user, 'Bookmarked')
await nextTick()
expect(input).toHaveValue('test query')
})
@@ -371,18 +368,20 @@ describe('NodeSearchContent', () => {
const input = screen.getByRole('combobox')
await user.click(input)
await user.keyboard('{ArrowDown}')
await nextTick()
expect(screen.getAllByTestId('result-item')[1]).toHaveAttribute(
'aria-selected',
'true'
)
await waitFor(() => {
expect(screen.getAllByTestId('result-item')[1]).toHaveAttribute(
'aria-selected',
'true'
)
})
await user.type(input, 'Node')
await nextTick()
expect(screen.getAllByTestId('result-item')[0]).toHaveAttribute(
'aria-selected',
'true'
)
await waitFor(() => {
expect(screen.getAllByTestId('result-item')[0]).toHaveAttribute(
'aria-selected',
'true'
)
})
})
it('should reset selected index when category changes', async () => {
@@ -393,17 +392,16 @@ describe('NodeSearchContent', () => {
await user.click(screen.getByRole('combobox'))
await user.keyboard('{ArrowDown}')
await nextTick()
await clickFilterBarButton(user, 'Bookmarked')
await nextTick()
await clickFilterBarButton(user, 'Bookmarked')
await nextTick()
expect(screen.getAllByTestId('result-item')[0]).toHaveAttribute(
'aria-selected',
'true'
)
await waitFor(() => {
expect(screen.getAllByTestId('result-item')[0]).toHaveAttribute(
'aria-selected',
'true'
)
})
})
})
@@ -424,24 +422,19 @@ describe('NodeSearchContent', () => {
expect(selectedIndex()).toBe(0)
await user.keyboard('{ArrowDown}')
await nextTick()
expect(selectedIndex()).toBe(1)
await waitFor(() => expect(selectedIndex()).toBe(1))
await user.keyboard('{ArrowDown}')
await nextTick()
expect(selectedIndex()).toBe(2)
await waitFor(() => expect(selectedIndex()).toBe(2))
await user.keyboard('{ArrowUp}')
await nextTick()
expect(selectedIndex()).toBe(1)
await waitFor(() => expect(selectedIndex()).toBe(1))
// Navigate to first, then try going above — should clamp
await user.keyboard('{ArrowUp}')
await nextTick()
expect(selectedIndex()).toBe(0)
await waitFor(() => expect(selectedIndex()).toBe(0))
await user.keyboard('{ArrowUp}')
await nextTick()
expect(selectedIndex()).toBe(0)
})
@@ -452,7 +445,6 @@ describe('NodeSearchContent', () => {
await user.click(screen.getByRole('combobox'))
await user.keyboard('{Enter}')
await nextTick()
expect(onAddNode).toHaveBeenCalledWith(
expect.objectContaining({ name: 'TestNode' })
@@ -467,9 +459,10 @@ describe('NodeSearchContent', () => {
const results = screen.getAllByTestId('result-item')
await user.hover(results[1])
await nextTick()
expect(results[1]).toHaveAttribute('aria-selected', 'true')
await waitFor(() => {
expect(results[1]).toHaveAttribute('aria-selected', 'true')
})
})
it('should add node on click', async () => {
@@ -478,7 +471,6 @@ describe('NodeSearchContent', () => {
])
await user.click(screen.getAllByTestId('result-item')[0])
await nextTick()
expect(onAddNode).toHaveBeenCalledWith(
expect.objectContaining({ name: 'TestNode' }),
@@ -496,21 +488,23 @@ describe('NodeSearchContent', () => {
const results = screen.getAllByTestId('result-item')
results[0].focus()
await user.keyboard('{ArrowDown}')
await nextTick()
expect(screen.getAllByTestId('result-item')[1]).toHaveAttribute(
'aria-selected',
'true'
)
await waitFor(() => {
expect(screen.getAllByTestId('result-item')[1]).toHaveAttribute(
'aria-selected',
'true'
)
})
screen.getAllByTestId('result-item')[1].focus()
await user.keyboard('{ArrowDown}')
await nextTick()
expect(screen.getAllByTestId('result-item')[2]).toHaveAttribute(
'aria-selected',
'true'
)
await waitFor(() => {
expect(screen.getAllByTestId('result-item')[2]).toHaveAttribute(
'aria-selected',
'true'
)
})
})
it('should select node with Enter from a focused result item', async () => {
@@ -520,7 +514,6 @@ describe('NodeSearchContent', () => {
screen.getAllByTestId('result-item')[0].focus()
await user.keyboard('{Enter}')
await nextTick()
expect(onAddNode).toHaveBeenCalledWith(
expect.objectContaining({ name: 'TestNode' })
@@ -534,26 +527,27 @@ describe('NodeSearchContent', () => {
{ name: 'HoverNode', display_name: 'Hover Node' }
])
const calls = onHoverNode.mock.calls
expect(calls[calls.length - 1][0]).toMatchObject({
name: 'HoverNode'
await waitFor(() => {
const calls = onHoverNode.mock.calls
expect(calls[calls.length - 1][0]).toMatchObject({ name: 'HoverNode' })
})
})
it('should emit null hoverNode when no results', async () => {
mockBookmarks(false, ['placeholder'])
const { user, onHoverNode } = await renderComponent()
const { user, onHoverNode } = renderComponent()
await clickFilterBarButton(user, 'Bookmarked')
await nextTick()
const calls = onHoverNode.mock.calls
expect(calls[calls.length - 1][0]).toBeNull()
await waitFor(() => {
const calls = onHoverNode.mock.calls
expect(calls[calls.length - 1][0]).toBeNull()
})
})
})
describe('filter integration', () => {
it('should display active filters in the input area', async () => {
it('should display active filters in the input area', () => {
useNodeDefStore().updateNodeDefs([
createMockNodeDef({
name: 'ImageNode',
@@ -562,7 +556,7 @@ describe('NodeSearchContent', () => {
})
])
await renderComponent({
renderComponent({
filters: [
{
filterDef: useNodeDefStore().nodeSearchService.inputTypeFilter,
@@ -597,13 +591,11 @@ describe('NodeSearchContent', () => {
it('should emit removeFilter on backspace', async () => {
const filters = createFilters(1)
const { user, onRemoveFilter } = await renderComponent({ filters })
const { user, onRemoveFilter } = renderComponent({ filters })
await user.click(screen.getByRole('combobox'))
await user.keyboard('{Backspace}')
await nextTick()
await user.keyboard('{Backspace}')
await nextTick()
expect(onRemoveFilter).toHaveBeenCalledTimes(1)
expect(onRemoveFilter).toHaveBeenCalledWith(
@@ -612,26 +604,102 @@ describe('NodeSearchContent', () => {
})
it('should not interact with chips when no filters exist', async () => {
const { user, onRemoveFilter } = await renderComponent({ filters: [] })
const { user, onRemoveFilter } = renderComponent({ filters: [] })
await user.click(screen.getByRole('combobox'))
await user.keyboard('{Backspace}')
await nextTick()
expect(onRemoveFilter).not.toHaveBeenCalled()
})
it('should remove chip when clicking its delete button', async () => {
const filters = createFilters(1)
const { user, onRemoveFilter } = await renderComponent({ filters })
const { user, onRemoveFilter } = renderComponent({ filters })
await user.click(screen.getByTestId('chip-delete'))
await nextTick()
expect(onRemoveFilter).toHaveBeenCalledTimes(1)
expect(onRemoveFilter).toHaveBeenCalledWith(
expect.objectContaining({ value: 'IMAGE' })
)
})
it('should emit removeFilter for every filter in a group when cleared', async () => {
useNodeDefStore().updateNodeDefs([
createMockNodeDef({
name: 'ImageNode',
display_name: 'Image Node',
input: { required: { image: ['IMAGE', {}] } }
}),
createMockNodeDef({
name: 'LatentNode',
display_name: 'Latent Node',
input: { required: { latent: ['LATENT', {}] } }
})
])
const inputFilter = useNodeDefStore().nodeSearchService.inputTypeFilter
const filters = [
{ filterDef: inputFilter, value: 'IMAGE' },
{ filterDef: inputFilter, value: 'LATENT' }
]
const { user, onRemoveFilter } = renderComponent({ filters })
const inputBtn = screen.getByRole('button', { name: /Input/ })
await user.click(inputBtn)
const clearBtn = await screen.findByRole('button', { name: 'Clear all' })
await user.click(clearBtn)
await waitFor(() => {
expect(onRemoveFilter).toHaveBeenCalledTimes(2)
})
const removedValues = onRemoveFilter.mock.calls.map(([f]) => f.value)
expect(removedValues).toEqual(expect.arrayContaining(['IMAGE', 'LATENT']))
})
})
describe('rootFilter + category + search combination', () => {
it('should intersect rootFilter, selected category, and search query', async () => {
useNodeDefStore().updateNodeDefs([
createMockNodeDef({
name: 'CustomSampler',
display_name: 'Custom Sampler',
category: 'sampling',
python_module: 'custom_nodes.my_extension'
}),
createMockNodeDef({
name: 'CustomLoader',
display_name: 'Custom Loader',
category: 'loaders',
python_module: 'custom_nodes.my_extension'
}),
createMockNodeDef({
name: 'CoreSampler',
display_name: 'Core Sampler',
category: 'sampling',
python_module: 'nodes'
})
])
const { user } = renderComponent()
await clickFilterBarButton(user, 'Extensions')
const samplingBtn = await screen.findByTestId('category-custom/sampling')
await user.click(samplingBtn)
const input = screen.getByRole('combobox')
await user.type(input, 'Custom')
await waitFor(() => {
expect(screen.queryAllByTestId('node-item')).toHaveLength(1)
})
const texts = screen
.queryAllByTestId('node-item')
.map((i) => i.textContent)
expect(texts).toContain('Custom Sampler')
expect(texts).not.toContain('Core Sampler')
expect(texts).not.toContain('Custom Loader')
})
})
})

View File

@@ -0,0 +1,269 @@
import { render, screen } from '@testing-library/vue'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { ComponentProps } from 'vue-component-type-helpers'
import NodeSearchListItem from '@/components/searchbox/v2/NodeSearchListItem.vue'
import {
createMockNodeDef,
setupTestPinia,
testI18n
} from '@/components/searchbox/v2/__test__/testUtils'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useNodeFrequencyStore } from '@/stores/nodeDefStore'
function renderItem(
props: Partial<ComponentProps<typeof NodeSearchListItem>> = {}
) {
return render(NodeSearchListItem, {
props: { nodeDef: createMockNodeDef(), currentQuery: '', ...props },
global: {
plugins: [testI18n],
stubs: {
NodePricingBadge: {
template: '<div data-testid="pricing-badge" />',
props: ['nodeDef']
},
ComfyLogo: { template: '<div data-testid="comfy-logo" />' }
}
}
})
}
describe('NodeSearchListItem', () => {
beforeEach(() => {
setupTestPinia()
vi.restoreAllMocks()
})
describe('id name badge', () => {
it('shows id name when ShowIdName setting is enabled', () => {
useSettingStore().settingValues['Comfy.NodeSearchBoxImpl.ShowIdName'] =
true
renderItem({
nodeDef: createMockNodeDef({
name: 'KSamplerNode',
display_name: 'KSampler'
})
})
expect(screen.getByText('KSamplerNode')).toBeInTheDocument()
})
it('hides id name by default', () => {
renderItem({
nodeDef: createMockNodeDef({
name: 'KSamplerNode',
display_name: 'KSampler'
})
})
expect(screen.queryByText('KSamplerNode')).not.toBeInTheDocument()
})
})
describe('showDescription mode', () => {
it('renders description text', () => {
renderItem({
nodeDef: createMockNodeDef({ description: 'A sampler node' }),
showDescription: true
})
expect(screen.getByText('A sampler node')).toBeInTheDocument()
})
it('renders category when ShowCategory setting is enabled', () => {
useSettingStore().settingValues['Comfy.NodeSearchBoxImpl.ShowCategory'] =
true
renderItem({
nodeDef: createMockNodeDef({ category: 'sampling/advanced' }),
showDescription: true
})
expect(screen.getByText('sampling / advanced')).toBeInTheDocument()
})
it('hides category by default', () => {
renderItem({
nodeDef: createMockNodeDef({ category: 'sampling' }),
showDescription: true
})
expect(screen.queryByText('sampling')).not.toBeInTheDocument()
})
})
describe('source badge', () => {
it('renders core comfy badge for non-custom node when showSourceBadge is true', () => {
renderItem({
nodeDef: createMockNodeDef({ python_module: 'nodes' }),
showDescription: true,
showSourceBadge: true
})
expect(screen.getByTestId('comfy-logo')).toBeInTheDocument()
})
it('renders custom node badge for custom node when showSourceBadge is true', () => {
renderItem({
nodeDef: createMockNodeDef({
python_module: 'custom_nodes.my_extension',
display_name: 'CustomNode'
}),
showDescription: true,
showSourceBadge: true
})
expect(screen.getByText('my_extension')).toBeInTheDocument()
})
it('does not render source badge when showSourceBadge is false', () => {
renderItem({
nodeDef: createMockNodeDef({ python_module: 'nodes' }),
showDescription: true,
showSourceBadge: false
})
expect(screen.queryByTestId('comfy-logo')).not.toBeInTheDocument()
})
})
describe('API node provider badge', () => {
it('renders provider badge only when nodeDef.api_node is true', () => {
renderItem({
nodeDef: createMockNodeDef({
api_node: true,
category: 'api/image/BFL'
})
})
expect(screen.getByText('BFL')).toBeInTheDocument()
})
it('does not render provider badge when nodeDef.api_node is false', () => {
renderItem({
nodeDef: createMockNodeDef({
api_node: false,
category: 'api/image/BFL'
})
})
expect(screen.queryByText('BFL')).not.toBeInTheDocument()
})
})
describe('status flags', () => {
it('shows deprecated label when deprecated', () => {
renderItem({ nodeDef: createMockNodeDef({ deprecated: true }) })
expect(screen.getByText('DEPR')).toBeInTheDocument()
})
it('shows experimental label when experimental', () => {
renderItem({ nodeDef: createMockNodeDef({ experimental: true }) })
expect(screen.getByText('BETA')).toBeInTheDocument()
})
it('shows devOnly label when dev_only is set', () => {
renderItem({ nodeDef: createMockNodeDef({ dev_only: true }) })
expect(screen.getByText('DEV')).toBeInTheDocument()
})
it('does not show flags in description mode', () => {
renderItem({
nodeDef: createMockNodeDef({ deprecated: true, experimental: true }),
showDescription: true
})
expect(screen.queryByText('DEPR')).not.toBeInTheDocument()
expect(screen.queryByText('BETA')).not.toBeInTheDocument()
})
})
describe('node frequency badge', () => {
it('shows frequency when ShowNodeFrequency is enabled and frequency > 0', () => {
useSettingStore().settingValues[
'Comfy.NodeSearchBoxImpl.ShowNodeFrequency'
] = true
vi.spyOn(useNodeFrequencyStore(), 'getNodeFrequency').mockReturnValue(
1500
)
renderItem({ nodeDef: createMockNodeDef() })
const badge = screen.getByTestId('frequency-badge')
expect(badge).toBeInTheDocument()
expect(badge.textContent).toMatch(/1\.5k/i)
})
it('hides frequency when frequency is 0 even if setting is enabled', () => {
useSettingStore().settingValues[
'Comfy.NodeSearchBoxImpl.ShowNodeFrequency'
] = true
vi.spyOn(useNodeFrequencyStore(), 'getNodeFrequency').mockReturnValue(0)
renderItem({ nodeDef: createMockNodeDef() })
expect(screen.queryByTestId('frequency-badge')).not.toBeInTheDocument()
})
it('hides frequency when setting is disabled even if frequency > 0', () => {
useSettingStore().settingValues[
'Comfy.NodeSearchBoxImpl.ShowNodeFrequency'
] = false
vi.spyOn(useNodeFrequencyStore(), 'getNodeFrequency').mockReturnValue(
9999
)
renderItem({ nodeDef: createMockNodeDef() })
expect(screen.queryByTestId('frequency-badge')).not.toBeInTheDocument()
})
})
describe('bookmark icon', () => {
it('shows bookmark icon when node is bookmarked', () => {
useSettingStore().settingValues['Comfy.NodeLibrary.Bookmarks.V2'] = [
'TestNode'
]
renderItem({ nodeDef: createMockNodeDef({ name: 'TestNode' }) })
expect(
screen.getByRole('img', { name: 'Bookmarked' })
).toBeInTheDocument()
})
it('does not show bookmark icon when node is not bookmarked', () => {
renderItem({ nodeDef: createMockNodeDef({ name: 'TestNode' }) })
expect(
screen.queryByRole('img', { name: 'Bookmarked' })
).not.toBeInTheDocument()
})
it('hides bookmark icon when hideBookmarkIcon prop is true', () => {
useSettingStore().settingValues['Comfy.NodeLibrary.Bookmarks.V2'] = [
'TestNode'
]
renderItem({
nodeDef: createMockNodeDef({ name: 'TestNode' }),
hideBookmarkIcon: true
})
expect(
screen.queryByRole('img', { name: 'Bookmarked' })
).not.toBeInTheDocument()
})
})
describe('query highlighting', () => {
it('wraps matching portion of display_name in a highlight span', () => {
renderItem({
nodeDef: createMockNodeDef({ display_name: 'KSampler Advanced' }),
currentQuery: 'Sampler'
})
expect(
screen.getByText('Sampler', { selector: 'span.highlight' })
).toBeInTheDocument()
})
it('does not wrap anything when currentQuery is empty', () => {
renderItem({
nodeDef: createMockNodeDef({ display_name: 'KSampler' }),
currentQuery: ''
})
expect(
screen.queryByText('KSampler', { selector: 'span.highlight' })
).not.toBeInTheDocument()
})
})
describe('node source display text', () => {
it('shows custom node source displayText in non-description mode', () => {
renderItem({
nodeDef: createMockNodeDef({
python_module: 'custom_nodes.my_extension'
})
})
expect(screen.getByText('my_extension')).toBeInTheDocument()
})
})
})

View File

@@ -5,8 +5,12 @@
<div class="flex min-w-0 flex-1 flex-col gap-1 overflow-hidden">
<!-- Row 1: Name (left) + badges (right) -->
<div class="text-foreground flex items-center gap-2 text-sm">
<span v-if="isBookmarked && !hideBookmarkIcon">
<i class="pi pi-bookmark-fill mr-1 text-sm" />
<span
v-if="isBookmarked && !hideBookmarkIcon"
role="img"
:aria-label="$t('g.bookmarked')"
>
<i aria-hidden="true" class="pi pi-bookmark-fill mr-1 text-sm" />
</span>
<span
class="truncate"
@@ -95,6 +99,7 @@
</span>
<span
v-if="showNodeFrequency && nodeFrequency > 0"
data-testid="frequency-badge"
class="rounded-sm bg-secondary-background px-1.5 py-0.5 text-xs text-muted-foreground"
>
{{ formatNumberWithSuffix(nodeFrequency, { roundToInt: true }) }}

View File

@@ -1,4 +1,4 @@
import { render, screen } from '@testing-library/vue'
import { render, screen, waitFor } from '@testing-library/vue'
import userEvent from '@testing-library/user-event'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { nextTick } from 'vue'
@@ -40,19 +40,21 @@ describe(NodeSearchTypeFilterPopover, () => {
const user = userEvent.setup()
const onToggle = vi.fn()
const onClear = vi.fn()
const onEscapeClose = vi.fn()
render(NodeSearchTypeFilterPopover, {
props: {
chip: props.chip ?? createMockChip(),
selectedValues: props.selectedValues ?? [],
onToggle,
onClear
onClear,
onEscapeClose
},
slots: {
default: '<button data-testid="trigger">Input</button>'
},
global: { plugins: [testI18n] }
})
return { user, onToggle, onClear }
return { user, onToggle, onClear, onEscapeClose }
}
async function openPopover(user: ReturnType<typeof userEvent.setup>) {
@@ -156,6 +158,19 @@ describe(NodeSearchTypeFilterPopover, () => {
await nextTick()
expect(screen.queryAllByRole('option')).toHaveLength(0)
expect(screen.getByText('No results')).toBeInTheDocument()
expect(screen.getByText('No Results')).toBeInTheDocument()
})
it('should emit escapeClose and close the popover when Escape is pressed', async () => {
const { user, onEscapeClose } = createRender()
await openPopover(user)
expect(screen.getByRole('listbox')).toBeInTheDocument()
await user.keyboard('{Escape}')
await waitFor(() => {
expect(screen.queryByRole('listbox')).not.toBeInTheDocument()
})
expect(onEscapeClose).toHaveBeenCalled()
})
})

View File

@@ -2,12 +2,14 @@ import { createTestingPinia } from '@pinia/testing'
import { setActivePinia } from 'pinia'
import { createI18n } from 'vue-i18n'
import enMessages from '@/locales/en/main.json' with { type: 'json' }
import type { ComfyNodeDef } from '@/schemas/nodeDefSchema'
import { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
export function createMockNodeDef(
overrides: Partial<ComfyNodeDef> = {}
): ComfyNodeDef {
return {
): ComfyNodeDefImpl {
return new ComfyNodeDefImpl({
name: 'TestNode',
display_name: 'Test Node',
category: 'test',
@@ -21,7 +23,7 @@ export function createMockNodeDef(
deprecated: false,
experimental: false,
...overrides
}
})
}
export function setupTestPinia() {
@@ -31,34 +33,5 @@ export function setupTestPinia() {
export const testI18n = createI18n({
legacy: false,
locale: 'en',
messages: {
en: {
g: {
addNode: 'Add a node...',
filterBy: 'Filter by:',
mostRelevant: 'Most relevant',
recents: 'Recents',
favorites: 'Favorites',
bookmarked: 'Bookmarked',
essentials: 'Essentials',
category: 'Category',
custom: 'Custom',
comfy: 'Comfy',
partner: 'Partner',
extensions: 'Extensions',
noResults: 'No results',
filterByType: 'Filter by {type}...',
input: 'Input',
output: 'Output',
source: 'Source',
search: 'Search',
blueprints: 'Blueprints',
partnerNodes: 'Partner Nodes',
remove: 'Remove',
itemsSelected:
'No items selected | {count} item selected | {count} items selected',
clearAll: 'Clear all'
}
}
}
messages: { en: enMessages }
})