import type { ReactNode } from 'react' import type { PluginPayload } from '../types' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { render, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import { AuthCategory } from '../types' import Authorize from './index' // Create a wrapper with QueryClientProvider for real component testing const createTestQueryClient = () => new QueryClient({ defaultOptions: { queries: { retry: false, gcTime: 0, }, }, }) const createWrapper = () => { const testQueryClient = createTestQueryClient() return ({ children }: { children: ReactNode }) => ( {children} ) } // Mock API hooks - only mock network-related hooks const mockGetPluginOAuthClientSchema = vi.fn() vi.mock('../hooks/use-credential', () => ({ useGetPluginOAuthUrlHook: () => ({ mutateAsync: vi.fn().mockResolvedValue({ authorization_url: '' }), }), useGetPluginOAuthClientSchemaHook: () => ({ data: mockGetPluginOAuthClientSchema(), isLoading: false, }), useSetPluginOAuthCustomClientHook: () => ({ mutateAsync: vi.fn().mockResolvedValue({}), }), useDeletePluginOAuthCustomClientHook: () => ({ mutateAsync: vi.fn().mockResolvedValue({}), }), useInvalidPluginOAuthClientSchemaHook: () => vi.fn(), useAddPluginCredentialHook: () => ({ mutateAsync: vi.fn().mockResolvedValue({}), }), useUpdatePluginCredentialHook: () => ({ mutateAsync: vi.fn().mockResolvedValue({}), }), useGetPluginCredentialSchemaHook: () => ({ data: [], isLoading: false, }), })) // Mock openOAuthPopup - window operations vi.mock('@/hooks/use-oauth', () => ({ openOAuthPopup: vi.fn(), })) // Mock service/use-triggers - API service vi.mock('@/service/use-triggers', () => ({ useTriggerPluginDynamicOptions: () => ({ data: { options: [] }, isLoading: false, }), useTriggerPluginDynamicOptionsInfo: () => ({ data: null, isLoading: false, }), useInvalidTriggerDynamicOptions: () => vi.fn(), })) // Factory function for creating test PluginPayload const createPluginPayload = (overrides: Partial = {}): PluginPayload => ({ category: AuthCategory.tool, provider: 'test-provider', ...overrides, }) describe('Authorize', () => { beforeEach(() => { vi.clearAllMocks() mockGetPluginOAuthClientSchema.mockReturnValue({ schema: [], is_oauth_custom_client_enabled: false, is_system_oauth_params_exists: false, }) }) // ==================== Rendering Tests ==================== describe('Rendering', () => { it('should render nothing when canOAuth and canApiKey are both false/undefined', () => { const pluginPayload = createPluginPayload() const { container } = render( , { wrapper: createWrapper() }, ) // No buttons should be rendered expect(screen.queryByRole('button')).not.toBeInTheDocument() // Container should only have wrapper element expect(container.querySelector('.flex')).toBeInTheDocument() }) it('should render only OAuth button when canOAuth is true and canApiKey is false', () => { const pluginPayload = createPluginPayload() render( , { wrapper: createWrapper() }, ) // OAuth button should exist (either configured or setup button) expect(screen.getByRole('button')).toBeInTheDocument() }) it('should render only API Key button when canApiKey is true and canOAuth is false', () => { const pluginPayload = createPluginPayload() render( , { wrapper: createWrapper() }, ) expect(screen.getByRole('button')).toBeInTheDocument() }) it('should render both OAuth and API Key buttons when both are true', () => { const pluginPayload = createPluginPayload() render( , { wrapper: createWrapper() }, ) const buttons = screen.getAllByRole('button') expect(buttons.length).toBe(2) }) it('should render divider when showDivider is true and both buttons are shown', () => { const pluginPayload = createPluginPayload() render( , { wrapper: createWrapper() }, ) expect(screen.getByText('or')).toBeInTheDocument() }) it('should not render divider when showDivider is false', () => { const pluginPayload = createPluginPayload() render( , { wrapper: createWrapper() }, ) expect(screen.queryByText('or')).not.toBeInTheDocument() }) it('should not render divider when only one button type is shown', () => { const pluginPayload = createPluginPayload() render( , { wrapper: createWrapper() }, ) expect(screen.queryByText('or')).not.toBeInTheDocument() }) it('should render divider by default (showDivider defaults to true)', () => { const pluginPayload = createPluginPayload() render( , { wrapper: createWrapper() }, ) expect(screen.getByText('or')).toBeInTheDocument() }) }) // ==================== Props Testing ==================== describe('Props Testing', () => { describe('theme prop', () => { it('should render buttons with secondary theme variant when theme is secondary', () => { const pluginPayload = createPluginPayload() render( , { wrapper: createWrapper() }, ) const buttons = screen.getAllByRole('button') buttons.forEach((button) => { expect(button.className).toContain('btn-secondary') }) }) }) describe('disabled prop', () => { it('should disable OAuth button when disabled is true', () => { const pluginPayload = createPluginPayload() render( , { wrapper: createWrapper() }, ) expect(screen.getByRole('button')).toBeDisabled() }) it('should disable API Key button when disabled is true', () => { const pluginPayload = createPluginPayload() render( , { wrapper: createWrapper() }, ) expect(screen.getByRole('button')).toBeDisabled() }) it('should not disable buttons when disabled is false', () => { const pluginPayload = createPluginPayload() render( , { wrapper: createWrapper() }, ) const buttons = screen.getAllByRole('button') buttons.forEach((button) => { expect(button).not.toBeDisabled() }) }) }) describe('notAllowCustomCredential prop', () => { it('should disable OAuth button when notAllowCustomCredential is true', () => { const pluginPayload = createPluginPayload() render( , { wrapper: createWrapper() }, ) expect(screen.getByRole('button')).toBeDisabled() }) it('should disable API Key button when notAllowCustomCredential is true', () => { const pluginPayload = createPluginPayload() render( , { wrapper: createWrapper() }, ) expect(screen.getByRole('button')).toBeDisabled() }) it('should add opacity class when notAllowCustomCredential is true', () => { const pluginPayload = createPluginPayload() const { container } = render( , { wrapper: createWrapper() }, ) const wrappers = container.querySelectorAll('.opacity-50') expect(wrappers.length).toBe(2) // Both OAuth and API Key wrappers }) }) }) // ==================== Button Text Variations ==================== describe('Button Text Variations', () => { it('should show correct OAuth text based on canApiKey', () => { const pluginPayload = createPluginPayload() // When canApiKey is false, should show "useOAuthAuth" const { rerender } = render( , { wrapper: createWrapper() }, ) expect(screen.getByRole('button')).toHaveTextContent('plugin.auth') // When canApiKey is true, button text changes rerender( , ) const buttons = screen.getAllByRole('button') expect(buttons.length).toBe(2) }) }) // ==================== Memoization Dependencies ==================== describe('Memoization and Re-rendering', () => { it('should maintain stable props across re-renders with same dependencies', () => { const pluginPayload = createPluginPayload() const onUpdate = vi.fn() const { rerender } = render( , { wrapper: createWrapper() }, ) const initialButtonCount = screen.getAllByRole('button').length rerender( , ) expect(screen.getAllByRole('button').length).toBe(initialButtonCount) }) it('should update when canApiKey changes', () => { const pluginPayload = createPluginPayload() const { rerender } = render( , { wrapper: createWrapper() }, ) expect(screen.getAllByRole('button').length).toBe(1) rerender( , ) expect(screen.getAllByRole('button').length).toBe(2) }) it('should update when canOAuth changes', () => { const pluginPayload = createPluginPayload() const { rerender } = render( , { wrapper: createWrapper() }, ) expect(screen.getAllByRole('button').length).toBe(1) rerender( , ) expect(screen.getAllByRole('button').length).toBe(2) }) it('should update button variant when theme changes', () => { const pluginPayload = createPluginPayload() const { rerender } = render( , { wrapper: createWrapper() }, ) const buttonPrimary = screen.getByRole('button') // Primary theme with canOAuth=false should have primary variant expect(buttonPrimary.className).toContain('btn-primary') rerender( , ) expect(screen.getByRole('button').className).toContain('btn-secondary') }) }) // ==================== Edge Cases ==================== describe('Edge Cases', () => { it('should handle undefined pluginPayload properties gracefully', () => { const pluginPayload: PluginPayload = { category: AuthCategory.tool, provider: 'test-provider', providerType: undefined, detail: undefined, } expect(() => { render( , { wrapper: createWrapper() }, ) }).not.toThrow() }) it('should handle all auth categories', () => { const categories = [AuthCategory.tool, AuthCategory.datasource, AuthCategory.model, AuthCategory.trigger] categories.forEach((category) => { const pluginPayload = createPluginPayload({ category }) const { unmount } = render( , { wrapper: createWrapper() }, ) expect(screen.getAllByRole('button').length).toBe(2) unmount() }) }) it('should handle empty string provider', () => { const pluginPayload = createPluginPayload({ provider: '' }) expect(() => { render( , { wrapper: createWrapper() }, ) }).not.toThrow() }) it('should handle both disabled and notAllowCustomCredential together', () => { const pluginPayload = createPluginPayload() render( , { wrapper: createWrapper() }, ) const buttons = screen.getAllByRole('button') buttons.forEach((button) => { expect(button).toBeDisabled() }) }) }) // ==================== Component Memoization ==================== describe('Component Memoization', () => { it('should be a memoized component (exported with memo)', async () => { const AuthorizeDefault = (await import('./index')).default expect(AuthorizeDefault).toBeDefined() // memo wrapped components are React elements with $$typeof expect(typeof AuthorizeDefault).toBe('object') }) it('should not re-render wrapper when notAllowCustomCredential stays the same', () => { const pluginPayload = createPluginPayload() const onUpdate = vi.fn() const { rerender, container } = render( , { wrapper: createWrapper() }, ) const initialOpacityElements = container.querySelectorAll('.opacity-50').length rerender( , ) expect(container.querySelectorAll('.opacity-50').length).toBe(initialOpacityElements) }) it('should update wrapper when notAllowCustomCredential changes', () => { const pluginPayload = createPluginPayload() const { rerender, container } = render( , { wrapper: createWrapper() }, ) expect(container.querySelectorAll('.opacity-50').length).toBe(0) rerender( , ) expect(container.querySelectorAll('.opacity-50').length).toBe(1) }) }) // ==================== Integration with pluginPayload ==================== describe('pluginPayload Integration', () => { it('should pass pluginPayload to OAuth button', () => { const pluginPayload = createPluginPayload({ provider: 'special-provider', category: AuthCategory.model, }) render( , { wrapper: createWrapper() }, ) expect(screen.getByRole('button')).toBeInTheDocument() }) it('should pass pluginPayload to API Key button', () => { const pluginPayload = createPluginPayload({ provider: 'another-provider', category: AuthCategory.datasource, }) render( , { wrapper: createWrapper() }, ) expect(screen.getByRole('button')).toBeInTheDocument() }) it('should handle pluginPayload with detail property', () => { const pluginPayload = createPluginPayload({ detail: { plugin_id: 'test-plugin', name: 'Test Plugin', } as PluginPayload['detail'], }) expect(() => { render( , { wrapper: createWrapper() }, ) }).not.toThrow() }) }) // ==================== Conditional Rendering Scenarios ==================== describe('Conditional Rendering Scenarios', () => { it('should handle rapid prop changes', () => { const pluginPayload = createPluginPayload() const { rerender } = render( , { wrapper: createWrapper() }, ) expect(screen.getAllByRole('button').length).toBe(2) rerender() expect(screen.getAllByRole('button').length).toBe(1) rerender() expect(screen.getAllByRole('button').length).toBe(1) rerender() expect(screen.queryByRole('button')).not.toBeInTheDocument() }) it('should correctly toggle divider visibility based on button combinations', () => { const pluginPayload = createPluginPayload() const { rerender } = render( , { wrapper: createWrapper() }, ) expect(screen.getByText('or')).toBeInTheDocument() rerender( , ) expect(screen.queryByText('or')).not.toBeInTheDocument() rerender( , ) expect(screen.queryByText('or')).not.toBeInTheDocument() }) }) // ==================== Accessibility ==================== describe('Accessibility', () => { it('should have accessible button elements', () => { const pluginPayload = createPluginPayload() render( , { wrapper: createWrapper() }, ) const buttons = screen.getAllByRole('button') expect(buttons.length).toBe(2) }) it('should indicate disabled state for accessibility', () => { const pluginPayload = createPluginPayload() render( , { wrapper: createWrapper() }, ) const buttons = screen.getAllByRole('button') buttons.forEach((button) => { expect(button).toBeDisabled() }) }) }) })