mirror of
https://github.com/langgenius/dify.git
synced 2026-05-07 02:46:32 +08:00
feat(web): empty list of snippet
This commit is contained in:
parent
b7fe45d800
commit
b5dc774093
@ -2,6 +2,8 @@ import { render, screen } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import Empty from '../empty'
|
||||
|
||||
const defaultMessage = 'workflow.tabs.noSnippetsFound'
|
||||
|
||||
describe('Empty', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
@ -9,32 +11,32 @@ describe('Empty', () => {
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('should render without crashing', () => {
|
||||
render(<Empty />)
|
||||
expect(screen.getByText('app.newApp.noAppsFound')).toBeInTheDocument()
|
||||
render(<Empty message={defaultMessage} />)
|
||||
expect(screen.getByText(defaultMessage)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render 36 placeholder cards', () => {
|
||||
const { container } = render(<Empty />)
|
||||
const { container } = render(<Empty message={defaultMessage} />)
|
||||
const placeholderCards = container.querySelectorAll('.bg-background-default-lighter')
|
||||
expect(placeholderCards).toHaveLength(36)
|
||||
})
|
||||
|
||||
it('should display the no apps found message', () => {
|
||||
render(<Empty />)
|
||||
it('should display the provided message', () => {
|
||||
render(<Empty message="app.newApp.noAppsFound" />)
|
||||
expect(screen.getByText('app.newApp.noAppsFound')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Styling', () => {
|
||||
it('should have correct container styling for overlay', () => {
|
||||
const { container } = render(<Empty />)
|
||||
const { container } = render(<Empty message={defaultMessage} />)
|
||||
const overlay = container.querySelector('.pointer-events-none')
|
||||
expect(overlay).toBeInTheDocument()
|
||||
expect(overlay).toHaveClass('absolute', 'inset-0', 'z-20')
|
||||
})
|
||||
|
||||
it('should have correct styling for placeholder cards', () => {
|
||||
const { container } = render(<Empty />)
|
||||
const { container } = render(<Empty message={defaultMessage} />)
|
||||
const card = container.querySelector('.bg-background-default-lighter')
|
||||
expect(card).toHaveClass('inline-flex', 'h-[160px]', 'rounded-xl')
|
||||
})
|
||||
@ -42,10 +44,10 @@ describe('Empty', () => {
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle multiple renders without issues', () => {
|
||||
const { rerender } = render(<Empty />)
|
||||
expect(screen.getByText('app.newApp.noAppsFound')).toBeInTheDocument()
|
||||
const { rerender } = render(<Empty message={defaultMessage} />)
|
||||
expect(screen.getByText(defaultMessage)).toBeInTheDocument()
|
||||
|
||||
rerender(<Empty />)
|
||||
rerender(<Empty message="app.newApp.noAppsFound" />)
|
||||
expect(screen.getByText('app.newApp.noAppsFound')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -258,8 +258,8 @@ vi.mock('../new-app-card', () => ({
|
||||
}))
|
||||
|
||||
vi.mock('../empty', () => ({
|
||||
default: () => {
|
||||
return React.createElement('div', { 'data-testid': 'empty-state', 'role': 'status' }, 'No apps found')
|
||||
default: ({ message }: { message: string }) => {
|
||||
return React.createElement('div', { 'data-testid': 'empty-state', 'role': 'status' }, message)
|
||||
},
|
||||
}))
|
||||
|
||||
@ -296,6 +296,26 @@ const renderList = (props: React.ComponentProps<typeof List> = {}, searchParams
|
||||
describe('List', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
defaultSnippetData.pages[0].data = [
|
||||
{
|
||||
id: 'snippet-1',
|
||||
name: 'Tone Rewriter',
|
||||
description: 'Rewrites rough drafts into a concise, professional tone for internal stakeholder updates.',
|
||||
type: 'node',
|
||||
is_published: false,
|
||||
use_count: 19,
|
||||
icon_info: {
|
||||
icon_type: 'emoji',
|
||||
icon: '🪄',
|
||||
icon_background: '#E0EAFF',
|
||||
icon_url: '',
|
||||
},
|
||||
created_at: 1704067200,
|
||||
updated_at: '2024-01-02 10:00',
|
||||
author: '',
|
||||
},
|
||||
]
|
||||
defaultSnippetData.pages[0].total = 1
|
||||
useTagStore.setState({
|
||||
tagList: [{ id: 'tag-1', name: 'Test Tag', type: 'app', binding_count: 0 }],
|
||||
showTagManagementModal: false,
|
||||
@ -444,5 +464,14 @@ describe('List', () => {
|
||||
|
||||
expect(mockFetchSnippetNextPage).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should reuse the shared empty state when no snippets are available', () => {
|
||||
defaultSnippetData.pages[0].data = []
|
||||
defaultSnippetData.pages[0].total = 0
|
||||
|
||||
renderList({ pageType: 'snippets' })
|
||||
|
||||
expect(screen.getByTestId('empty-state')).toHaveTextContent('workflow.tabs.noSnippetsFound')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const DefaultCards = React.memo(() => {
|
||||
const renderArray = Array.from({ length: 36 })
|
||||
@ -17,15 +16,17 @@ const DefaultCards = React.memo(() => {
|
||||
)
|
||||
})
|
||||
|
||||
const Empty = () => {
|
||||
const { t } = useTranslation()
|
||||
type Props = {
|
||||
message: string
|
||||
}
|
||||
|
||||
const Empty = ({ message }: Props) => {
|
||||
return (
|
||||
<>
|
||||
<DefaultCards />
|
||||
<div className="pointer-events-none absolute inset-0 z-20 flex items-center justify-center bg-linear-to-t from-background-body to-transparent">
|
||||
<span className="system-md-medium text-text-tertiary">
|
||||
{t('newApp.noAppsFound', { ns: 'app' })}
|
||||
{message}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@ -228,12 +228,16 @@ const List: FC<Props> = ({
|
||||
const hasAnyApp = (data?.pages?.[0]?.total ?? 0) > 0
|
||||
const hasAnySnippet = snippetItems.length > 0
|
||||
const currentKeywords = isAppsPage ? keywords : snippetKeywordsInput
|
||||
const showEmptyState = !showSkeleton && (isAppsPage ? !hasAnyApp : !hasAnySnippet)
|
||||
const emptyStateMessage = isAppsPage
|
||||
? t('newApp.noAppsFound', { ns: 'app' })
|
||||
: t('tabs.noSnippetsFound', { ns: 'workflow' })
|
||||
|
||||
return (
|
||||
<>
|
||||
<div ref={containerRef} className="relative flex h-0 shrink-0 grow flex-col overflow-y-auto bg-background-body">
|
||||
{dragging && (
|
||||
<div className="inset-0 absolute z-50 m-0.5 rounded-2xl border-2 border-dashed border-components-dropzone-border-accent bg-[rgba(21,90,239,0.14)] p-2" />
|
||||
<div className="absolute inset-0 z-50 m-0.5 rounded-2xl border-2 border-dashed border-components-dropzone-border-accent bg-[rgba(21,90,239,0.14)] p-2" />
|
||||
)}
|
||||
|
||||
<div className="sticky top-0 z-10 flex flex-wrap items-center justify-between gap-y-2 bg-background-body px-12 pt-7 pb-5">
|
||||
@ -273,7 +277,7 @@ const List: FC<Props> = ({
|
||||
|
||||
<div className={cn(
|
||||
'relative grid grow grid-cols-1 content-start gap-4 px-12 pt-2 2k:grid-cols-6 sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5',
|
||||
isAppsPage && !hasAnyApp && 'overflow-hidden',
|
||||
showEmptyState && 'overflow-hidden',
|
||||
)}
|
||||
>
|
||||
{(isCurrentWorkspaceEditor || isLoadingCurrentWorkspace) && (
|
||||
@ -300,13 +304,7 @@ const List: FC<Props> = ({
|
||||
<SnippetCard key={snippet.id} snippet={snippet} />
|
||||
))}
|
||||
|
||||
{!showSkeleton && isAppsPage && !hasAnyApp && <Empty />}
|
||||
|
||||
{!showSkeleton && !isAppsPage && !hasAnySnippet && (
|
||||
<div className="col-span-full flex min-h-[240px] items-center justify-center rounded-xl border border-dashed border-divider-regular bg-components-card-bg p-6 text-center text-sm text-text-tertiary">
|
||||
{t('tabs.noSnippetsFound', { ns: 'workflow' })}
|
||||
</div>
|
||||
)}
|
||||
{showEmptyState && <Empty message={emptyStateMessage} />}
|
||||
|
||||
{isAppsPage && isFetchingNextPage && (
|
||||
<AppCardSkeleton count={3} />
|
||||
|
||||
Loading…
Reference in New Issue
Block a user