import type { ReactNode } from 'react' import { fireEvent, render, screen, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import { createSystemFeaturesWrapper } from '@/__tests__/utils/mock-system-features' import PluginPage from '@/app/components/plugins/plugin-page' import { createNuqsTestWrapper } from '@/test/nuqs-testing' const mockFetchManifestFromMarketPlace = vi.fn() const { mockRouterReplace } = vi.hoisted(() => ({ mockRouterReplace: vi.fn(), })) vi.mock('@/utils', async (importOriginal) => { const actual = await importOriginal() return { ...actual, sleep: vi.fn(() => Promise.resolve()), } }) vi.mock('@/hooks/use-document-title', () => ({ __esModule: true, default: vi.fn(), })) vi.mock('@/next/navigation', () => ({ useRouter: () => ({ replace: mockRouterReplace, }), })) vi.mock('@/context/account-state', async (importOriginal) => { const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') return createAppContextStateAtomMock(importOriginal, () => ({ isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_env: 'CLOUD', current_version: '1.0.0', latest_version: '1.0.0', version: '1.0.0', release_date: '', release_notes: '', can_auto_update: false, }, workspacePermissionKeys: ['plugin.install', 'plugin.delete', 'plugin.plugin_preferences'], })) }) vi.mock('@/context/workspace-state', async (importOriginal) => { const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') return createAppContextStateAtomMock(importOriginal, () => ({ isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_env: 'CLOUD', current_version: '1.0.0', latest_version: '1.0.0', version: '1.0.0', release_date: '', release_notes: '', can_auto_update: false, }, workspacePermissionKeys: ['plugin.install', 'plugin.delete', 'plugin.plugin_preferences'], })) }) vi.mock('@/context/permission-state', async (importOriginal) => { const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') return createAppContextStateAtomMock(importOriginal, () => ({ isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_env: 'CLOUD', current_version: '1.0.0', latest_version: '1.0.0', version: '1.0.0', release_date: '', release_notes: '', can_auto_update: false, }, workspacePermissionKeys: ['plugin.install', 'plugin.delete', 'plugin.plugin_preferences'], })) }) vi.mock('@/context/version-state', async (importOriginal) => { const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') return createAppContextStateAtomMock(importOriginal, () => ({ isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_env: 'CLOUD', current_version: '1.0.0', latest_version: '1.0.0', version: '1.0.0', release_date: '', release_notes: '', can_auto_update: false, }, workspacePermissionKeys: ['plugin.install', 'plugin.delete', 'plugin.plugin_preferences'], })) }) vi.mock('@/context/system-features-state', async (importOriginal) => { const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state') return createAppContextStateAtomMock(importOriginal, () => ({ isCurrentWorkspaceManager: false, isCurrentWorkspaceOwner: false, langGeniusVersionInfo: { current_env: 'CLOUD', current_version: '1.0.0', latest_version: '1.0.0', version: '1.0.0', release_date: '', release_notes: '', can_auto_update: false, }, workspacePermissionKeys: ['plugin.install', 'plugin.delete', 'plugin.plugin_preferences'], })) }) vi.mock('jotai', async (importOriginal) => { const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state') return createAppContextStateJotaiMock(importOriginal) }) vi.mock('@/service/use-plugins', () => ({ hasPluginPermission: () => true, useReferenceSettings: () => ({ data: { permission: { install_permission: 'everyone', debug_permission: 'noOne', }, }, }), useMutationReferenceSettings: () => ({ mutate: vi.fn(), isPending: false, }), useInvalidateReferenceSettings: () => vi.fn(), usePluginPermissionSettings: () => ({ data: { install_permission: 'everyone', debug_permission: 'noOne', }, }), useMutationPluginPermissionSettings: () => ({ mutate: vi.fn(), isPending: false, }), usePluginAutoUpgradeSettings: () => ({ data: { auto_upgrade: false, strategy_setting: {}, exclude_plugins: [], include_plugins: [], }, }), useInstalledPluginList: () => ({ data: { total: 2, }, }), })) vi.mock('@/service/plugins', () => ({ fetchManifestFromMarketPlace: (...args: unknown[]) => mockFetchManifestFromMarketPlace(...args), fetchBundleInfoFromMarketPlace: vi.fn(), })) vi.mock('@/app/components/plugins/plugin-page/plugin-tasks', () => ({ default: () =>
plugin tasks
, })) vi.mock('@/app/components/plugins/plugin-page/debug-info', () => ({ default: () =>
debug info
, })) vi.mock('@/app/components/plugins/plugin-page/install-plugin-dropdown', () => ({ default: ({ onSwitchToMarketplaceTab }: { onSwitchToMarketplaceTab: () => void }) => ( ), })) vi.mock('@/app/components/plugins/install-plugin/install-from-marketplace', () => ({ default: ({ uniqueIdentifier, onClose }: { uniqueIdentifier: string; onClose: () => void }) => (
{uniqueIdentifier}
), })) const renderPluginPage = (searchParams = '') => { const { wrapper: SysWrapper } = createSystemFeaturesWrapper({ systemFeatures: { enable_marketplace: true, plugin_installation_permission: { restrict_to_marketplace_only: false, }, }, }) const { wrapper: NuqsWrapper, onUrlUpdate } = createNuqsTestWrapper({ searchParams }) const Wrapper = ({ children }: { children: ReactNode }) => ( {children} ) return { ...render( plugins view} marketplace={
marketplace view
} />, { wrapper: Wrapper }, ), onUrlUpdate, } } describe('Plugin Page Shell Flow', () => { beforeEach(() => { vi.clearAllMocks() mockFetchManifestFromMarketPlace.mockResolvedValue({ data: { plugin: { org: 'langgenius', name: 'plugin-demo', }, version: { version: '1.0.0', }, }, }) }) it('switches from installed plugins to marketplace and syncs the active tab into the URL', async () => { const { onUrlUpdate } = renderPluginPage() expect(screen.getByTestId('plugins-view'))!.toBeInTheDocument() expect(screen.queryByTestId('marketplace-view')).not.toBeInTheDocument() fireEvent.click(screen.getByTestId('tab-item-discover')) await waitFor(() => { expect(screen.getByTestId('marketplace-view'))!.toBeInTheDocument() }) const tabUpdate = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1]![0] expect(tabUpdate.searchParams.get('tab')).toBe('discover') }) it('hydrates marketplace installation from query params and clears the install state when closed', async () => { const { onUrlUpdate } = renderPluginPage('?package-ids=%5B%22langgenius%2Fplugin-demo%22%5D') await waitFor(() => { expect(mockFetchManifestFromMarketPlace).toHaveBeenCalledWith('langgenius%2Fplugin-demo') expect(screen.getByTestId('install-from-marketplace-modal'))!.toBeInTheDocument() }) fireEvent.click(screen.getByRole('button', { name: 'close-install-modal' })) await waitFor(() => { const clearUpdate = onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1]![0] expect(clearUpdate.searchParams.has('package-ids')).toBe(false) }) }) })