import { screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { renderWithConsoleQuery } from '@/test/console/query-data' import { KnowledgeSpaceShell } from '../knowledge-space-shell' const queryMock = vi.hoisted(() => ({ data: undefined as | { id: string name: string } | undefined, error: null as unknown, isPending: false, refetch: vi.fn(), })) const queryOptionsMock = vi.hoisted(() => vi.fn(() => ({}))) const useQueryOptionsMock = vi.hoisted(() => vi.fn()) const pathnameMock = vi.hoisted(() => ({ value: '/datasets/new/space-1/sources' })) vi.mock('@/next/navigation', () => ({ usePathname: () => pathnameMock.value, })) vi.mock('@tanstack/react-query', async (importOriginal) => { const original = await importOriginal() return { ...original, useQuery: (options: unknown) => { useQueryOptionsMock(options) return queryMock }, } }) vi.mock('@/service/client', async (importOriginal) => { const original = await importOriginal() return { ...original, consoleQuery: { ...original.consoleQuery, systemFeatures: original.consoleQuery.systemFeatures, knowledgeFs: { ...original.consoleQuery.knowledgeFs, getKnowledgeSpacesById: { ...original.consoleQuery.knowledgeFs.getKnowledgeSpacesById, queryOptions: queryOptionsMock, }, }, }, } }) describe('KnowledgeSpaceShell', () => { beforeEach(() => { vi.clearAllMocks() document.title = '' queryMock.data = undefined queryMock.error = null queryMock.isPending = false pathnameMock.value = '/datasets/new/space-1/sources' }) it.each([ ['/datasets/new/space-1/sources', 'dataset.newKnowledge.sources'], ['/datasets/new/space-1/sources/new', 'dataset.newKnowledge.addSource'], ['/datasets/new/space-1/documents', 'dataset.newKnowledge.documents'], ])('identifies the current detail page for %s', async (pathname, pageTitle) => { pathnameMock.value = pathname queryMock.data = { id: 'space-1', name: 'Support knowledge' } renderWithConsoleQuery( content, ) await waitFor(() => { expect(document.title).toBe(`${pageTitle} ยท Support knowledge - Dify`) }) }) it('delegates a document detail title to the document page', () => { pathnameMock.value = '/datasets/new/space-1/documents/document-1' queryMock.data = { id: 'space-1', name: 'Support knowledge' } renderWithConsoleQuery( document content, ) expect(document.title).toBe('') }) it('loads the real knowledge space contract by route id', () => { queryMock.isPending = true renderWithConsoleQuery( content, ) expect(queryOptionsMock).toHaveBeenCalledWith({ input: { params: { id: 'space-1' } } }) expect(screen.getByRole('status')).toBeInTheDocument() }) it('renders a refresh-safe header and route navigation when loaded', () => { queryMock.data = { id: 'space-1', name: 'Support knowledge' } renderWithConsoleQuery( source content, ) expect(screen.getByRole('heading', { name: 'Support knowledge' })).toBeInTheDocument() expect(screen.getByRole('link', { name: 'dataset.newKnowledge.sources' })).toHaveAttribute( 'href', '/datasets/new/space-1/sources', ) expect(screen.getByRole('link', { name: 'dataset.newKnowledge.sources' })).toHaveAttribute( 'aria-current', 'page', ) expect(screen.getByRole('link', { name: 'dataset.newKnowledge.documents' })).toHaveAttribute( 'href', '/datasets/new/space-1/documents', ) expect(screen.getByText('source content')).toBeInTheDocument() }) it('shows a not-found state without rendering children', () => { queryMock.error = { status: 404 } renderWithConsoleQuery( source content, ) expect(screen.getByText('dataset.newKnowledge.notFoundTitle')).toBeInTheDocument() expect(screen.queryByText('source content')).not.toBeInTheDocument() }) it('recognizes the nested status shape returned by the ORPC client', () => { queryMock.error = { data: { status: 404 } } renderWithConsoleQuery( source content, ) expect(screen.getByText('dataset.newKnowledge.notFoundTitle')).toBeInTheDocument() }) it('treats forbidden detail responses as a terminal non-disclosing state', () => { queryMock.error = { data: { status: 403 } } renderWithConsoleQuery( source content, ) expect(screen.getByText('dataset.newKnowledge.notFoundTitle')).toBeInTheDocument() expect(screen.queryByRole('button', { name: 'common.operation.retry' })).not.toBeInTheDocument() }) it.each([{ status: 403 }, { data: { status: 404 } }])( 'does not automatically retry terminal detail errors shaped as $error', (error) => { queryMock.error = error renderWithConsoleQuery( source content, ) const options = useQueryOptionsMock.mock.lastCall?.[0] as { retry: (failureCount: number, queryError: unknown) => boolean } expect(options.retry(0, error)).toBe(false) expect(options.retry(2, new Error('temporary failure'))).toBe(true) expect(options.retry(3, new Error('temporary failure'))).toBe(false) }, ) it('marks Documents as the only current detail route', () => { pathnameMock.value = '/datasets/new/space-1/documents' queryMock.data = { id: 'space-1', name: 'Support knowledge' } renderWithConsoleQuery( document content, ) expect(screen.getByRole('link', { name: 'dataset.newKnowledge.sources' })).not.toHaveAttribute( 'aria-current', ) expect(screen.getByRole('link', { name: 'dataset.newKnowledge.documents' })).toHaveAttribute( 'aria-current', 'page', ) }) it('offers a real retry for recoverable loading errors', async () => { const user = userEvent.setup() queryMock.error = new Error('temporary failure') renderWithConsoleQuery( source content, ) await user.click(screen.getByRole('button', { name: 'common.operation.retry' })) expect(queryMock.refetch).toHaveBeenCalledOnce() }) })