From 7b4fef5ecaeb8ddf8bb1f4c1ff5bb018dd6aceda Mon Sep 17 00:00:00 2001 From: Robin Huang Date: Fri, 22 May 2026 03:15:26 -0700 Subject: [PATCH] fix: show empty state when node library search has no matches (#12254) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The left-sidebar node library search fell back to rendering all visible node defs whenever the filter returned zero hits, so gibberish queries looked like the filter wasn't applied. Gates the fallback on the query string and renders a "No nodes match" empty state across all tabs (Essentials/All/Blueprints) when the active query has no results. Before: https://github.com/user-attachments/assets/ab11ef5e-c757-41f1-9e07-3427942b9929 After: https://github.com/user-attachments/assets/a724aaab-95a2-4832-a694-3d8e543fdabf ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12254-fix-show-empty-state-when-node-library-search-has-no-matches-3606d73d365081d19eaaff1095355072) by [Unito](https://www.unito.io) --- .../tabs/NodeLibrarySidebarTabV2.test.ts | 73 ++++++++++++++++++- .../sidebar/tabs/NodeLibrarySidebarTabV2.vue | 22 +++++- src/locales/en/main.json | 1 + 3 files changed, 89 insertions(+), 7 deletions(-) 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() + }) + }) }) diff --git a/src/components/sidebar/tabs/NodeLibrarySidebarTabV2.vue b/src/components/sidebar/tabs/NodeLibrarySidebarTabV2.vue index 15ee01671f..c13ba6f093 100644 --- a/src/components/sidebar/tabs/NodeLibrarySidebarTabV2.vue +++ b/src/components/sidebar/tabs/NodeLibrarySidebarTabV2.vue @@ -117,7 +117,17 @@