fix(search): show default discovery list on empty query (#312)

This commit is contained in:
XiaoSeS 2026-04-15 16:58:21 +08:00 • committed by GitHub
parent 2cfd4a730b
commit 4619e546bf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 67 additions and 15 deletions

View file

@ -39,12 +39,11 @@ test.describe('Search Input (Real API)', () => {
await expect(getSearchCards(page).first()).toBeVisible({ timeout: 10_000 })
})
// TC_SEARCH_INPUT_003 P0 - empty search guidance
test('TC_SEARCH_INPUT_003: empty search shows keyword guidance instead of a default list', async ({ page }) => {
// TC_SEARCH_INPUT_003 P0 - empty search shows the default discovery list
test('TC_SEARCH_INPUT_003: empty search shows the default discovery list', async ({ page }) => {
await page.goto(searchUrl(''))
await expect(page).toHaveURL(/\/search/)
await expect(page.getByRole('heading', { name: 'No results found' })).toBeVisible()
await expect(page.getByText('Please enter a search keyword')).toBeVisible()
await expect(getSearchCards(page).first()).toBeVisible({ timeout: 10_000 })
})
// TC_SEARCH_INPUT_004 P0 - Enter key triggers search

View file

@ -46,7 +46,13 @@ vi.mock('@/shared/components/skeleton-loader', () => ({
}))
vi.mock('@/shared/components/empty-state', () => ({
EmptyState: () => <div>empty-state</div>,
EmptyState: ({ title, description }: { title: string; description?: string }) => (
<div>
empty-state
<span>{title}</span>
{description ? <span>{description}</span> : null}
</div>
),
}))
vi.mock('@/shared/components/pagination', () => ({
@ -202,4 +208,55 @@ describe('SearchPage', () => {
},
})
})
it('renders the default skill list when the empty query still returns items', () => {
useSearchMock.mockReturnValue({
q: '',
label: '',
sort: 'newest',
page: 0,
starredOnly: false,
})
useSearchSkillsMock.mockReturnValue({
data: {
items: [{ id: 1, displayName: 'Demo Skill', summary: 'summary', namespace: 'global', slug: 'demo', downloadCount: 1, starCount: 1, ratingCount: 0, updatedAt: '2026-03-20T00:00:00Z', canSubmitPromotion: false }],
total: 1,
page: 0,
size: 12,
},
isLoading: false,
isFetching: false,
})
const html = renderToStaticMarkup(<SearchPage />)
expect(html).toContain('skill-card')
expect(html).not.toContain('empty-state')
})
it('shows a generic empty state when the default discovery list is empty', () => {
useSearchMock.mockReturnValue({
q: '',
label: '',
sort: 'newest',
page: 0,
starredOnly: false,
})
useSearchSkillsMock.mockReturnValue({
data: {
items: [],
total: 0,
page: 0,
size: 12,
},
isLoading: false,
isFetching: false,
})
const html = renderToStaticMarkup(<SearchPage />)
expect(html).toContain('empty-state')
expect(html).toContain('search.noResults')
expect(html).not.toContain('search.enterKeyword')
})
})

View file

@ -125,8 +125,6 @@ export function SearchPage() {
isLoading: isLoadingStarred,
isFetching: isFetchingStarred,
} = useMyStars(starredOnly && isAuthenticated)
const shouldShowGuidance = !starredOnly && !q && !selectedLabel
useEffect(() => {
// Debounce URL updates while the user is typing so query state stays shareable without
// triggering a navigation on every keystroke.
@ -202,10 +200,10 @@ export function SearchPage() {
: data
? Math.ceil(data.total / data.size)
: 0
const displayItems = shouldShowGuidance ? [] : (starredOnly ? starredPageItems : (data?.items ?? []))
const isPageLoading = shouldShowGuidance ? false : (starredOnly ? isLoadingStarred : isLoading)
const isUpdatingResults = shouldShowGuidance ? false : (starredOnly ? isFetchingStarred && !isLoadingStarred : isFetching && !isLoading)
const resultCount = shouldShowGuidance ? 0 : (starredOnly ? filteredStarredSkills.length : (data?.total ?? 0))
const displayItems = starredOnly ? starredPageItems : (data?.items ?? [])
const isPageLoading = starredOnly ? isLoadingStarred : isLoading
const isUpdatingResults = starredOnly ? isFetchingStarred && !isLoadingStarred : isFetching && !isLoading
const resultCount = starredOnly ? filteredStarredSkills.length : (data?.total ?? 0)
return (
<div className={APP_SHELL_PAGE_CLASS_NAME}>
@ -313,11 +311,9 @@ export function SearchPage() {
<EmptyState
title={starredOnly ? t('search.noStarredResults') : t('search.noResults')}
description={
shouldShowGuidance
? t('search.enterKeyword')
: starredOnly
starredOnly
? (q ? t('search.noStarredResultsFor', { q }) : t('search.noStarredSkills'))
: (q ? t('search.noResultsFor', { q }) : t('search.enterKeyword'))
: (q ? t('search.noResultsFor', { q }) : undefined)
}
/>
)}