diff --git a/.tmp/proof/issue-130-empty-filters.png b/.tmp/proof/issue-130-empty-filters.png new file mode 100644 index 0000000..a329aaa Binary files /dev/null and b/.tmp/proof/issue-130-empty-filters.png differ diff --git a/.tmp/proof/issue-130-mobile-filters.png b/.tmp/proof/issue-130-mobile-filters.png new file mode 100644 index 0000000..7271c24 Binary files /dev/null and b/.tmp/proof/issue-130-mobile-filters.png differ diff --git a/.tmp/proof/issue-130-restored-filters.png b/.tmp/proof/issue-130-restored-filters.png new file mode 100644 index 0000000..efb3909 Binary files /dev/null and b/.tmp/proof/issue-130-restored-filters.png differ diff --git a/packages/app/src/components/showcase/ShowcasePage.test.tsx b/packages/app/src/components/showcase/ShowcasePage.test.tsx index b032828..ad149f6 100644 --- a/packages/app/src/components/showcase/ShowcasePage.test.tsx +++ b/packages/app/src/components/showcase/ShowcasePage.test.tsx @@ -4,6 +4,66 @@ import { ShowcasePage } from './ShowcasePage'; import { showcaseItems, countByCategory } from './showcaseConfig'; describe('ShowcasePage', () => { + it('labels search and exposes the active category and result count', async () => { + await renderInTestApp(); + + expect( + screen.getByRole('textbox', { name: 'Search showcase' }), + ).toBeInTheDocument(); + const categories = within( + screen.getByRole('group', { name: 'Showcase categories' }), + ); + expect( + categories.getByRole('button', { name: /^All \(/, pressed: true }), + ).toBeInTheDocument(); + + fireEvent.click(categories.getByRole('button', { name: /^GitHub \(/ })); + + expect( + categories.getByRole('button', { name: /^GitHub \(/, pressed: true }), + ).toBeInTheDocument(); + expect( + categories.getByRole('button', { name: /^All \(/, pressed: false }), + ).toBeInTheDocument(); + expect(screen.getByRole('status')).toHaveTextContent('3 items available'); + }); + + it('combines search and category filters and restores all cards from an empty result', async () => { + await renderInTestApp(); + const search = screen.getByRole('textbox', { name: 'Search showcase' }); + + fireEvent.change(search, { target: { value: 'mcp' } }); + expect(screen.getByRole('status')).toHaveTextContent('1 item available'); + expect( + screen.getByTestId('showcase-card-template-mcp-server'), + ).toBeInTheDocument(); + + fireEvent.click(screen.getByRole('button', { name: /^GitHub \(/ })); + + expect(screen.getByRole('status')).toHaveTextContent('0 items available'); + expect(screen.getByText(/No showcase items found/)).toHaveTextContent( + 'in GitHub', + ); + expect(screen.queryAllByTestId(/^showcase-card-/)).toHaveLength(0); + + fireEvent.click(screen.getByRole('button', { name: 'Clear filters' })); + + expect(search).toHaveValue(''); + expect(search).toHaveFocus(); + expect( + screen.getByRole('button', { name: /^All \(/, pressed: true }), + ).toBeInTheDocument(); + expect(screen.getByRole('status')).toHaveTextContent( + `${showcaseItems.length} items available`, + ); + expect(screen.getAllByTestId(/^showcase-card-/)).toHaveLength( + showcaseItems.length, + ); + expect( + screen.queryByRole('button', { name: 'Clear filters' }), + ).not.toBeInTheDocument(); + }); + it('distinguishes live keyword search from the answer-engine extension', async () => { await renderInTestApp(); @@ -28,7 +88,9 @@ describe('ShowcasePage', () => { expect( extension.getByText(/your own document index and AI Hub/), ).toBeInTheDocument(); - expect(extension.getByRole('link', { name: 'Use template' })).toHaveAttribute( + expect( + extension.getByRole('link', { name: 'Use template' }), + ).toHaveAttribute( 'href', '/self-service/templates/default/answer-engine-worker', ); @@ -71,7 +133,9 @@ describe('ShowcasePage', () => { ).not.toBeNull(); }); expect( - document.querySelector('[data-testid="showcase-card-template-mcp-server"]'), + document.querySelector( + '[data-testid="showcase-card-template-mcp-server"]', + ), ).toBeNull(); }); @@ -83,7 +147,9 @@ describe('ShowcasePage', () => { }); expect( - document.querySelector('[data-testid="showcase-card-template-mcp-server"]'), + document.querySelector( + '[data-testid="showcase-card-template-mcp-server"]', + ), ).not.toBeNull(); expect( document.querySelector( diff --git a/packages/app/src/components/showcase/ShowcasePage.tsx b/packages/app/src/components/showcase/ShowcasePage.tsx index c2e60bf..6451a65 100644 --- a/packages/app/src/components/showcase/ShowcasePage.tsx +++ b/packages/app/src/components/showcase/ShowcasePage.tsx @@ -1,4 +1,4 @@ -import { useMemo, useState } from 'react'; +import { useMemo, useRef, useState } from 'react'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; import TextField from '@mui/material/TextField'; @@ -6,6 +6,7 @@ import InputAdornment from '@mui/material/InputAdornment'; import Chip from '@mui/material/Chip'; import Grid from '@mui/material/Grid'; import Alert from '@mui/material/Alert'; +import Button from '@mui/material/Button'; import { Page, Header, Content } from '@backstage/core-components'; import SearchIcon from '@mui/icons-material/Search'; import { @@ -25,6 +26,7 @@ const categories: ShowcaseCategory[] = [ ]; export const ShowcasePage = () => { + const searchInputRef = useRef(null); const [searchTerm, setSearchTerm] = useState(''); const [selectedCategory, setSelectedCategory] = useState< ShowcaseCategory | 'all' @@ -57,8 +59,8 @@ export const ShowcasePage = () => { Catalog and TechDocs keyword search runs in this portal. Generated answers are an extension you can build with the Answer Engine Worker - template, your own document index and AI Hub. The demo does not generate - answers. + template, your own document index and AI Hub. The demo does not + generate answers. { }} > setSearchTerm(e.target.value)} size="small" - sx={{ minWidth: 300, maxWidth: 400 }} + sx={{ width: '100%', maxWidth: 400 }} InputProps={{ startAdornment: ( @@ -84,14 +88,19 @@ export const ShowcasePage = () => { ), }} /> - + {filtered.length} item{filtered.length !== 1 ? 's' : ''} available - + setSelectedCategory('all')} variant={selectedCategory === 'all' ? 'filled' : 'outlined'} color={selectedCategory === 'all' ? 'primary' : 'default'} @@ -101,6 +110,7 @@ export const ShowcasePage = () => { setSelectedCategory(category)} variant={selectedCategory === category ? 'filled' : 'outlined'} color={selectedCategory === category ? 'primary' : 'default'} @@ -126,6 +136,17 @@ export const ShowcasePage = () => { {selectedCategory !== 'all' && ` in ${showcaseCategoryLabels[selectedCategory]}`} + )}