import { fireEvent, screen, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import ProviderList from '@/app/components/tools/provider-list' import { CollectionType } from '@/app/components/tools/types' import { renderWithNuqs } from '@/test/nuqs-testing' const mockInvalidateInstalledPluginList = vi.fn() vi.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key: string, options?: { ns?: string }) => options?.ns ? `${options.ns}.${key}` : key, }), })) vi.mock('@/context/global-public-context', () => ({ useGlobalPublicStore: (selector: (state: Record) => unknown) => selector({ systemFeatures: { enable_marketplace: true, }, }), })) vi.mock('@/app/components/plugins/hooks', () => ({ useTags: () => ({ getTagLabel: (name: string) => name, }), })) vi.mock('@/service/use-tools', () => ({ useAllToolProviders: () => ({ data: [ { id: 'builtin-plugin', name: 'plugin-tool', author: 'Dify', description: { en_US: 'Plugin Tool' }, icon: 'icon-plugin', label: { en_US: 'Plugin Tool' }, type: CollectionType.builtIn, team_credentials: {}, is_team_authorization: false, allow_delete: false, labels: ['search'], plugin_id: 'langgenius/plugin-tool', }, { id: 'builtin-basic', name: 'basic-tool', author: 'Dify', description: { en_US: 'Basic Tool' }, icon: 'icon-basic', label: { en_US: 'Basic Tool' }, type: CollectionType.builtIn, team_credentials: {}, is_team_authorization: false, allow_delete: false, labels: ['utility'], }, ], refetch: vi.fn(), }), })) vi.mock('@/service/use-plugins', () => ({ useCheckInstalled: ({ enabled }: { enabled: boolean }) => ({ data: enabled ? { plugins: [{ plugin_id: 'langgenius/plugin-tool', declaration: { category: 'tool', }, }], } : null, }), useInvalidateInstalledPluginList: () => mockInvalidateInstalledPluginList, })) vi.mock('@/app/components/tools/labels/filter', () => ({ default: ({ onChange }: { onChange: (value: string[]) => void }) => (
), })) vi.mock('@/app/components/plugins/card', () => ({ default: ({ payload, className }: { payload: { name: string }, className?: string }) => (
{payload.name}
), })) vi.mock('@/app/components/plugins/card/card-more-info', () => ({ default: ({ tags }: { tags: string[] }) =>
{tags.join(',')}
, })) vi.mock('@/app/components/tools/provider/detail', () => ({ default: ({ collection, onHide }: { collection: { name: string }, onHide: () => void }) => (
{collection.name}
), })) vi.mock('@/app/components/plugins/plugin-detail-panel', () => ({ default: ({ detail, onHide, onUpdate, }: { detail?: { plugin_id: string } onHide: () => void onUpdate: () => void }) => detail ? (
{detail.plugin_id}
) : null, })) vi.mock('@/app/components/tools/provider/empty', () => ({ default: () =>
workflow empty
, })) vi.mock('@/app/components/plugins/marketplace/empty', () => ({ default: ({ text }: { text: string }) =>
{text}
, })) vi.mock('@/app/components/tools/marketplace', () => ({ default: ({ isMarketplaceArrowVisible, showMarketplacePanel, }: { isMarketplaceArrowVisible: boolean showMarketplacePanel: () => void }) => ( ), })) vi.mock('@/app/components/tools/marketplace/hooks', () => ({ useMarketplace: () => ({ handleScroll: vi.fn(), }), })) vi.mock('@/app/components/tools/mcp', () => ({ default: ({ searchText }: { searchText: string }) =>
{searchText}
, })) const renderProviderList = (searchParams = '') => { return renderWithNuqs(, { searchParams }) } describe('Tool Provider List Shell Flow', () => { beforeEach(() => { vi.clearAllMocks() Element.prototype.scrollTo = vi.fn() }) it('opens a plugin-backed provider detail panel and invalidates installed plugins on update', async () => { renderProviderList('?category=builtin') fireEvent.click(screen.getByTestId('tool-card-plugin-tool')) await waitFor(() => { expect(screen.getByTestId('tool-plugin-detail-panel')).toBeInTheDocument() }) fireEvent.click(screen.getByRole('button', { name: 'update-plugin-detail' })) expect(mockInvalidateInstalledPluginList).toHaveBeenCalledTimes(1) fireEvent.click(screen.getByRole('button', { name: 'close-plugin-detail' })) await waitFor(() => { expect(screen.queryByTestId('tool-plugin-detail-panel')).not.toBeInTheDocument() }) }) it('scrolls to the marketplace section and syncs workflow tab selection into the URL', async () => { const { onUrlUpdate } = renderProviderList('?category=builtin') fireEvent.click(screen.getByTestId('marketplace-arrow')) expect(Element.prototype.scrollTo).toHaveBeenCalled() fireEvent.click(screen.getByTestId('tab-item-workflow')) await waitFor(() => { expect(screen.getByTestId('workflow-empty')).toBeInTheDocument() }) const update = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1][0] expect(update.searchParams.get('category')).toBe('workflow') }) })