diff --git a/src/components/sidebar/tabs/NodeLibrarySidebarTabV2.test.ts b/src/components/sidebar/tabs/NodeLibrarySidebarTabV2.test.ts index 2fcf1d9ef7..77360d4454 100644 --- a/src/components/sidebar/tabs/NodeLibrarySidebarTabV2.test.ts +++ b/src/components/sidebar/tabs/NodeLibrarySidebarTabV2.test.ts @@ -3,10 +3,20 @@ import { ref } from 'vue' import { beforeEach, describe, expect, it, vi } from 'vitest' import { createI18n } from 'vue-i18n' -import { render, screen } from '@testing-library/vue' +import { fireEvent, render, screen } from '@testing-library/vue' import NodeLibrarySidebarTabV2 from './NodeLibrarySidebarTabV2.vue' +const hoisted = vi.hoisted(() => ({ + mockSearchNode: vi.fn<(query: string) => unknown[]>(() => []) +})) + +vi.mock('@/services/nodeSearchService', () => ({ + NodeSearchService: class { + searchNode = hoisted.mockSearchNode + } +})) + vi.mock('@vueuse/core', async () => { const actual = await vi.importActual('@vueuse/core') return { @@ -72,8 +82,10 @@ vi.mock('./nodeLibrary/NodeDragPreview.vue', () => ({ vi.mock('@/components/ui/search-input/SearchInput.vue', () => ({ default: { name: 'SearchBox', - template: '', + template: + '', props: ['modelValue', 'placeholder'], + emits: ['update:modelValue', 'search'], setup() { return { focus: vi.fn() } }, @@ -84,12 +96,22 @@ vi.mock('@/components/ui/search-input/SearchInput.vue', () => ({ const i18n = createI18n({ legacy: false, locale: 'en', - messages: { en: {} } + messages: { + en: { + sideToolbar: { + nodeLibraryTab: { + noMatchingNodes: 'No nodes match "{query}"' + } + } + } + } }) describe('NodeLibrarySidebarTabV2', () => { beforeEach(() => { vi.clearAllMocks() + hoisted.mockSearchNode.mockReset() + hoisted.mockSearchNode.mockReturnValue([]) }) function renderComponent() { @@ -123,4 +145,49 @@ describe('NodeLibrarySidebarTabV2', () => { expect(screen.queryByTestId('all-panel')).not.toBeInTheDocument() expect(screen.queryByTestId('blueprints-panel')).not.toBeInTheDocument() }) + + describe('search empty state', () => { + it('does not render the empty state when search query is empty', () => { + renderComponent() + + expect(screen.queryByText(/No nodes match/)).not.toBeInTheDocument() + expect(screen.getByTestId('essential-panel')).toBeInTheDocument() + }) + + it('renders the empty state with the query when search has no matches', async () => { + hoisted.mockSearchNode.mockReturnValue([]) + renderComponent() + + await fireEvent.update(screen.getByTestId('search-box'), 'gibberish') + + expect(screen.getByText('No nodes match "gibberish"')).toBeInTheDocument() + expect(screen.queryByTestId('essential-panel')).not.toBeInTheDocument() + expect(screen.queryByTestId('all-panel')).not.toBeInTheDocument() + expect(screen.queryByTestId('blueprints-panel')).not.toBeInTheDocument() + }) + + it('hides the empty state when the search has matches', async () => { + hoisted.mockSearchNode.mockReturnValue([{ name: 'KSampler' }]) + renderComponent() + + await fireEvent.update(screen.getByTestId('search-box'), 'ksampler') + + expect(screen.queryByText(/No nodes match/)).not.toBeInTheDocument() + expect(screen.getByTestId('essential-panel')).toBeInTheDocument() + }) + + it('hides the empty state once the query is cleared', async () => { + hoisted.mockSearchNode.mockReturnValue([]) + renderComponent() + + const input = screen.getByTestId('search-box') + await fireEvent.update(input, 'gibberish') + expect(screen.getByText('No nodes match "gibberish"')).toBeInTheDocument() + + await fireEvent.update(input, '') + + expect(screen.queryByText(/No nodes match/)).not.toBeInTheDocument() + expect(screen.getByTestId('essential-panel')).toBeInTheDocument() + }) + }) })