mirror of
https://github.com/langgenius/dify.git
synced 2026-05-04 01:18:05 +08:00
Merge remote-tracking branch 'origin/main' into feat/support-agent-sandbox
# Conflicts: # api/uv.lock # web/app/components/apps/__tests__/app-card.spec.tsx # web/app/components/apps/__tests__/list.spec.tsx # web/app/components/datasets/create/__tests__/index.spec.tsx # web/app/components/datasets/metadata/metadata-dataset/__tests__/dataset-metadata-drawer.spec.tsx # web/app/components/plugins/readme-panel/__tests__/index.spec.tsx # web/app/components/rag-pipeline/__tests__/index.spec.tsx # web/app/components/rag-pipeline/hooks/__tests__/index.spec.ts # web/eslint-suppressions.json
This commit is contained in:
@ -0,0 +1,67 @@
|
||||
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { AuthCategory } from '../../types'
|
||||
import AddApiKeyButton from '../add-api-key-button'
|
||||
|
||||
let _mockModalOpen = false
|
||||
vi.mock('../api-key-modal', () => ({
|
||||
default: ({ onClose, onUpdate }: { onClose: () => void, onUpdate?: () => void }) => {
|
||||
_mockModalOpen = true
|
||||
return (
|
||||
<div data-testid="api-key-modal">
|
||||
<button data-testid="modal-close" onClick={onClose}>Close</button>
|
||||
<button data-testid="modal-update" onClick={onUpdate}>Update</button>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}))
|
||||
|
||||
const defaultPayload = {
|
||||
category: AuthCategory.tool,
|
||||
provider: 'test-provider',
|
||||
}
|
||||
|
||||
describe('AddApiKeyButton', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
_mockModalOpen = false
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
cleanup()
|
||||
})
|
||||
|
||||
it('renders button with default text', () => {
|
||||
render(<AddApiKeyButton pluginPayload={defaultPayload} />)
|
||||
expect(screen.getByRole('button')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders button with custom text', () => {
|
||||
render(<AddApiKeyButton pluginPayload={defaultPayload} buttonText="Add Key" />)
|
||||
expect(screen.getByText('Add Key')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('opens modal when button is clicked', () => {
|
||||
render(<AddApiKeyButton pluginPayload={defaultPayload} />)
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
expect(screen.getByTestId('api-key-modal')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('respects disabled prop', () => {
|
||||
render(<AddApiKeyButton pluginPayload={defaultPayload} disabled />)
|
||||
expect(screen.getByRole('button')).toBeDisabled()
|
||||
})
|
||||
|
||||
it('closes modal when onClose is called', () => {
|
||||
render(<AddApiKeyButton pluginPayload={defaultPayload} />)
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
expect(screen.getByTestId('api-key-modal')).toBeInTheDocument()
|
||||
fireEvent.click(screen.getByTestId('modal-close'))
|
||||
expect(screen.queryByTestId('api-key-modal')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('applies custom button variant', () => {
|
||||
render(<AddApiKeyButton pluginPayload={defaultPayload} buttonVariant="primary" />)
|
||||
expect(screen.getByRole('button')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,102 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { AuthCategory } from '../../types'
|
||||
|
||||
const mockGetPluginOAuthUrl = vi.fn().mockResolvedValue({ authorization_url: 'https://auth.example.com' })
|
||||
const mockOpenOAuthPopup = vi.fn()
|
||||
|
||||
vi.mock('@/hooks/use-i18n', () => ({
|
||||
useRenderI18nObject: () => (obj: Record<string, string> | string) => typeof obj === 'string' ? obj : obj.en_US || '',
|
||||
}))
|
||||
|
||||
vi.mock('@/hooks/use-oauth', () => ({
|
||||
openOAuthPopup: (...args: unknown[]) => mockOpenOAuthPopup(...args),
|
||||
}))
|
||||
|
||||
vi.mock('../../hooks/use-credential', () => ({
|
||||
useGetPluginOAuthUrlHook: () => ({
|
||||
mutateAsync: mockGetPluginOAuthUrl,
|
||||
}),
|
||||
useGetPluginOAuthClientSchemaHook: () => ({
|
||||
data: {
|
||||
schema: [],
|
||||
is_oauth_custom_client_enabled: false,
|
||||
is_system_oauth_params_exists: true,
|
||||
client_params: {},
|
||||
redirect_uri: 'https://redirect.example.com',
|
||||
},
|
||||
isLoading: false,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('../oauth-client-settings', () => ({
|
||||
default: ({ onClose }: { onClose: () => void }) => (
|
||||
<div data-testid="oauth-settings-modal">
|
||||
<button data-testid="oauth-settings-close" onClick={onClose}>Close</button>
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/form/types', () => ({
|
||||
FormTypeEnum: { radio: 'radio' },
|
||||
}))
|
||||
|
||||
vi.mock('@/utils/classnames', () => ({
|
||||
cn: (...args: unknown[]) => args.filter(Boolean).join(' '),
|
||||
}))
|
||||
|
||||
const basePayload = {
|
||||
category: AuthCategory.tool,
|
||||
provider: 'test-provider',
|
||||
}
|
||||
|
||||
describe('AddOAuthButton', () => {
|
||||
let AddOAuthButton: (typeof import('../add-oauth-button'))['default']
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks()
|
||||
const mod = await import('../add-oauth-button')
|
||||
AddOAuthButton = mod.default
|
||||
})
|
||||
|
||||
it('should render OAuth button when configured (system params exist)', () => {
|
||||
render(<AddOAuthButton pluginPayload={basePayload} buttonText="Use OAuth" />)
|
||||
|
||||
expect(screen.getByText('Use OAuth')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should open OAuth settings modal when settings icon clicked', () => {
|
||||
render(<AddOAuthButton pluginPayload={basePayload} buttonText="Use OAuth" />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('oauth-settings-button'))
|
||||
|
||||
expect(screen.getByTestId('oauth-settings-modal')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should close OAuth settings modal', () => {
|
||||
render(<AddOAuthButton pluginPayload={basePayload} buttonText="Use OAuth" />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('oauth-settings-button'))
|
||||
fireEvent.click(screen.getByTestId('oauth-settings-close'))
|
||||
|
||||
expect(screen.queryByTestId('oauth-settings-modal')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should trigger OAuth flow on main button click', async () => {
|
||||
render(<AddOAuthButton pluginPayload={basePayload} buttonText="Use OAuth" />)
|
||||
|
||||
const button = screen.getByText('Use OAuth').closest('button')
|
||||
if (button)
|
||||
fireEvent.click(button)
|
||||
|
||||
expect(mockGetPluginOAuthUrl).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should be disabled when disabled prop is true', () => {
|
||||
render(<AddOAuthButton pluginPayload={basePayload} buttonText="Use OAuth" disabled />)
|
||||
|
||||
const button = screen.getByText('Use OAuth').closest('button')
|
||||
expect(button).toBeDisabled()
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,165 @@
|
||||
import type { ApiKeyModalProps } from '../api-key-modal'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { AuthCategory } from '../../types'
|
||||
|
||||
const mockNotify = vi.fn()
|
||||
const mockAddPluginCredential = vi.fn().mockResolvedValue({})
|
||||
const mockUpdatePluginCredential = vi.fn().mockResolvedValue({})
|
||||
const mockFormValues = { isCheckValidated: true, values: { __name__: 'My Key', api_key: 'sk-123' } }
|
||||
|
||||
vi.mock('@/app/components/base/toast', () => ({
|
||||
useToastContext: () => ({
|
||||
notify: mockNotify,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('../../hooks/use-credential', () => ({
|
||||
useAddPluginCredentialHook: () => ({
|
||||
mutateAsync: mockAddPluginCredential,
|
||||
}),
|
||||
useGetPluginCredentialSchemaHook: () => ({
|
||||
data: [
|
||||
{ name: 'api_key', label: 'API Key', type: 'secret-input', required: true },
|
||||
],
|
||||
isLoading: false,
|
||||
}),
|
||||
useUpdatePluginCredentialHook: () => ({
|
||||
mutateAsync: mockUpdatePluginCredential,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('../../../readme-panel/entrance', () => ({
|
||||
ReadmeEntrance: () => <div data-testid="readme-entrance" />,
|
||||
}))
|
||||
|
||||
vi.mock('../../../readme-panel/store', () => ({
|
||||
ReadmeShowType: { modal: 'modal' },
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/encrypted-bottom', () => ({
|
||||
EncryptedBottom: () => <div data-testid="encrypted-bottom" />,
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/modal/modal', () => ({
|
||||
default: ({ children, title, onClose, onConfirm, onExtraButtonClick, showExtraButton, disabled }: {
|
||||
children: React.ReactNode
|
||||
title: string
|
||||
onClose?: () => void
|
||||
onCancel?: () => void
|
||||
onConfirm?: () => void
|
||||
onExtraButtonClick?: () => void
|
||||
showExtraButton?: boolean
|
||||
disabled?: boolean
|
||||
[key: string]: unknown
|
||||
}) => (
|
||||
<div data-testid="modal">
|
||||
<div data-testid="modal-title">{title}</div>
|
||||
{children}
|
||||
<button data-testid="modal-confirm" onClick={onConfirm} disabled={disabled}>Confirm</button>
|
||||
<button data-testid="modal-close" onClick={onClose}>Close</button>
|
||||
{showExtraButton && <button data-testid="modal-extra" onClick={onExtraButtonClick}>Remove</button>}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/form/form-scenarios/auth', () => ({
|
||||
default: React.forwardRef((_props: Record<string, unknown>, ref: React.Ref<unknown>) => {
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
getFormValues: () => mockFormValues,
|
||||
}))
|
||||
return <div data-testid="auth-form" />
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/form/types', () => ({
|
||||
FormTypeEnum: { textInput: 'text-input' },
|
||||
}))
|
||||
|
||||
const basePayload = {
|
||||
category: AuthCategory.tool,
|
||||
provider: 'test-provider',
|
||||
}
|
||||
|
||||
describe('ApiKeyModal', () => {
|
||||
let ApiKeyModal: React.FC<ApiKeyModalProps>
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks()
|
||||
const mod = await import('../api-key-modal')
|
||||
ApiKeyModal = mod.default
|
||||
})
|
||||
|
||||
it('should render modal with correct title', () => {
|
||||
render(<ApiKeyModal pluginPayload={basePayload} />)
|
||||
|
||||
expect(screen.getByTestId('modal-title')).toHaveTextContent('plugin.auth.useApiAuth')
|
||||
})
|
||||
|
||||
it('should render auth form when data is loaded', () => {
|
||||
render(<ApiKeyModal pluginPayload={basePayload} />)
|
||||
|
||||
expect(screen.getByTestId('auth-form')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show remove button when editValues is provided', () => {
|
||||
render(<ApiKeyModal pluginPayload={basePayload} editValues={{ api_key: 'existing' }} />)
|
||||
|
||||
expect(screen.getByTestId('modal-extra')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not show remove button in add mode', () => {
|
||||
render(<ApiKeyModal pluginPayload={basePayload} />)
|
||||
|
||||
expect(screen.queryByTestId('modal-extra')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should call onClose when close button clicked', () => {
|
||||
const mockOnClose = vi.fn()
|
||||
render(<ApiKeyModal pluginPayload={basePayload} onClose={mockOnClose} />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('modal-close'))
|
||||
expect(mockOnClose).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should call addPluginCredential on confirm in add mode', async () => {
|
||||
const mockOnClose = vi.fn()
|
||||
const mockOnUpdate = vi.fn()
|
||||
render(<ApiKeyModal pluginPayload={basePayload} onClose={mockOnClose} onUpdate={mockOnUpdate} />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('modal-confirm'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockAddPluginCredential).toHaveBeenCalledWith(expect.objectContaining({
|
||||
type: 'api-key',
|
||||
name: 'My Key',
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
it('should call updatePluginCredential on confirm in edit mode', async () => {
|
||||
render(<ApiKeyModal pluginPayload={basePayload} editValues={{ api_key: 'existing', __credential_id__: 'cred-1' }} />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('modal-confirm'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUpdatePluginCredential).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
it('should call onRemove when remove button clicked', () => {
|
||||
const mockOnRemove = vi.fn()
|
||||
render(<ApiKeyModal pluginPayload={basePayload} editValues={{ api_key: 'existing' }} onRemove={mockOnRemove} />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('modal-extra'))
|
||||
expect(mockOnRemove).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should render readme entrance when detail is provided', () => {
|
||||
const payload = { ...basePayload, detail: { name: 'Test' } as never }
|
||||
render(<ApiKeyModal pluginPayload={payload} />)
|
||||
|
||||
expect(screen.getByTestId('readme-entrance')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@ -1,10 +1,10 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import type { PluginPayload } from '../types'
|
||||
import type { PluginPayload } from '../../types'
|
||||
import type { FormSchema } from '@/app/components/base/form/types'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { AuthCategory } from '../types'
|
||||
import { AuthCategory } from '../../types'
|
||||
|
||||
// Create a wrapper with QueryClientProvider
|
||||
const createTestQueryClient = () =>
|
||||
@ -36,7 +36,7 @@ const mockAddPluginCredential = vi.fn()
|
||||
const mockUpdatePluginCredential = vi.fn()
|
||||
const mockGetPluginCredentialSchema = vi.fn()
|
||||
|
||||
vi.mock('../hooks/use-credential', () => ({
|
||||
vi.mock('../../hooks/use-credential', () => ({
|
||||
useGetPluginOAuthUrlHook: () => ({
|
||||
mutateAsync: mockGetPluginOAuthUrl,
|
||||
}),
|
||||
@ -117,12 +117,12 @@ const createFormSchema = (overrides: Partial<FormSchema> = {}): FormSchema => ({
|
||||
|
||||
// ==================== AddApiKeyButton Tests ====================
|
||||
describe('AddApiKeyButton', () => {
|
||||
let AddApiKeyButton: typeof import('./add-api-key-button').default
|
||||
let AddApiKeyButton: typeof import('../add-api-key-button').default
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks()
|
||||
mockGetPluginCredentialSchema.mockReturnValue([])
|
||||
const importedAddApiKeyButton = await import('./add-api-key-button')
|
||||
const importedAddApiKeyButton = await import('../add-api-key-button')
|
||||
AddApiKeyButton = importedAddApiKeyButton.default
|
||||
})
|
||||
|
||||
@ -327,7 +327,7 @@ describe('AddApiKeyButton', () => {
|
||||
|
||||
describe('Memoization', () => {
|
||||
it('should be a memoized component', async () => {
|
||||
const AddApiKeyButtonDefault = (await import('./add-api-key-button')).default
|
||||
const AddApiKeyButtonDefault = (await import('../add-api-key-button')).default
|
||||
expect(typeof AddApiKeyButtonDefault).toBe('object')
|
||||
})
|
||||
})
|
||||
@ -335,7 +335,7 @@ describe('AddApiKeyButton', () => {
|
||||
|
||||
// ==================== AddOAuthButton Tests ====================
|
||||
describe('AddOAuthButton', () => {
|
||||
let AddOAuthButton: typeof import('./add-oauth-button').default
|
||||
let AddOAuthButton: typeof import('../add-oauth-button').default
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks()
|
||||
@ -347,7 +347,7 @@ describe('AddOAuthButton', () => {
|
||||
redirect_uri: 'https://example.com/callback',
|
||||
})
|
||||
mockGetPluginOAuthUrl.mockResolvedValue({ authorization_url: 'https://oauth.example.com/auth' })
|
||||
const importedAddOAuthButton = await import('./add-oauth-button')
|
||||
const importedAddOAuthButton = await import('../add-oauth-button')
|
||||
AddOAuthButton = importedAddOAuthButton.default
|
||||
})
|
||||
|
||||
@ -856,7 +856,7 @@ describe('AddOAuthButton', () => {
|
||||
|
||||
// ==================== ApiKeyModal Tests ====================
|
||||
describe('ApiKeyModal', () => {
|
||||
let ApiKeyModal: typeof import('./api-key-modal').default
|
||||
let ApiKeyModal: typeof import('../api-key-modal').default
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks()
|
||||
@ -870,7 +870,7 @@ describe('ApiKeyModal', () => {
|
||||
isCheckValidated: false,
|
||||
values: {},
|
||||
})
|
||||
const importedApiKeyModal = await import('./api-key-modal')
|
||||
const importedApiKeyModal = await import('../api-key-modal')
|
||||
ApiKeyModal = importedApiKeyModal.default
|
||||
})
|
||||
|
||||
@ -1272,13 +1272,13 @@ describe('ApiKeyModal', () => {
|
||||
|
||||
// ==================== OAuthClientSettings Tests ====================
|
||||
describe('OAuthClientSettings', () => {
|
||||
let OAuthClientSettings: typeof import('./oauth-client-settings').default
|
||||
let OAuthClientSettings: typeof import('../oauth-client-settings').default
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks()
|
||||
mockSetPluginOAuthCustomClient.mockResolvedValue({})
|
||||
mockDeletePluginOAuthCustomClient.mockResolvedValue({})
|
||||
const importedOAuthClientSettings = await import('./oauth-client-settings')
|
||||
const importedOAuthClientSettings = await import('../oauth-client-settings')
|
||||
OAuthClientSettings = importedOAuthClientSettings.default
|
||||
})
|
||||
|
||||
@ -2193,7 +2193,7 @@ describe('OAuthClientSettings', () => {
|
||||
|
||||
describe('Memoization', () => {
|
||||
it('should be a memoized component', async () => {
|
||||
const OAuthClientSettingsDefault = (await import('./oauth-client-settings')).default
|
||||
const OAuthClientSettingsDefault = (await import('../oauth-client-settings')).default
|
||||
expect(typeof OAuthClientSettingsDefault).toBe('object')
|
||||
})
|
||||
})
|
||||
@ -2216,7 +2216,7 @@ describe('Authorize Components Integration', () => {
|
||||
|
||||
describe('AddApiKeyButton -> ApiKeyModal Flow', () => {
|
||||
it('should open ApiKeyModal when AddApiKeyButton is clicked', async () => {
|
||||
const AddApiKeyButton = (await import('./add-api-key-button')).default
|
||||
const AddApiKeyButton = (await import('../add-api-key-button')).default
|
||||
const pluginPayload = createPluginPayload()
|
||||
|
||||
render(<AddApiKeyButton pluginPayload={pluginPayload} />, { wrapper: createWrapper() })
|
||||
@ -2231,7 +2231,7 @@ describe('Authorize Components Integration', () => {
|
||||
|
||||
describe('AddOAuthButton -> OAuthClientSettings Flow', () => {
|
||||
it('should open OAuthClientSettings when setup button is clicked', async () => {
|
||||
const AddOAuthButton = (await import('./add-oauth-button')).default
|
||||
const AddOAuthButton = (await import('../add-oauth-button')).default
|
||||
const pluginPayload = createPluginPayload()
|
||||
mockGetPluginOAuthClientSchema.mockReturnValue({
|
||||
schema: [createFormSchema({ name: 'client_id', label: 'Client ID' })],
|
||||
@ -1,10 +1,10 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import type { PluginPayload } from '../types'
|
||||
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'
|
||||
import { AuthCategory } from '../../types'
|
||||
import Authorize from '../index'
|
||||
|
||||
// Create a wrapper with QueryClientProvider for real component testing
|
||||
const createTestQueryClient = () =>
|
||||
@ -29,7 +29,7 @@ const createWrapper = () => {
|
||||
// Mock API hooks - only mock network-related hooks
|
||||
const mockGetPluginOAuthClientSchema = vi.fn()
|
||||
|
||||
vi.mock('../hooks/use-credential', () => ({
|
||||
vi.mock('../../hooks/use-credential', () => ({
|
||||
useGetPluginOAuthUrlHook: () => ({
|
||||
mutateAsync: vi.fn().mockResolvedValue({ authorization_url: '' }),
|
||||
}),
|
||||
@ -96,7 +96,7 @@ describe('Authorize', () => {
|
||||
it('should render nothing when canOAuth and canApiKey are both false/undefined', () => {
|
||||
const pluginPayload = createPluginPayload()
|
||||
|
||||
const { container } = render(
|
||||
render(
|
||||
<Authorize
|
||||
pluginPayload={pluginPayload}
|
||||
canOAuth={false}
|
||||
@ -105,10 +105,7 @@ describe('Authorize', () => {
|
||||
{ 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', () => {
|
||||
@ -225,7 +222,7 @@ describe('Authorize', () => {
|
||||
// ==================== Props Testing ====================
|
||||
describe('Props Testing', () => {
|
||||
describe('theme prop', () => {
|
||||
it('should render buttons with secondary theme variant when theme is secondary', () => {
|
||||
it('should render buttons when theme is secondary', () => {
|
||||
const pluginPayload = createPluginPayload()
|
||||
|
||||
render(
|
||||
@ -239,9 +236,7 @@ describe('Authorize', () => {
|
||||
)
|
||||
|
||||
const buttons = screen.getAllByRole('button')
|
||||
buttons.forEach((button) => {
|
||||
expect(button.className).toContain('btn-secondary')
|
||||
})
|
||||
expect(buttons).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
|
||||
@ -327,10 +322,10 @@ describe('Authorize', () => {
|
||||
expect(screen.getByRole('button')).toBeDisabled()
|
||||
})
|
||||
|
||||
it('should add opacity class when notAllowCustomCredential is true', () => {
|
||||
it('should disable all buttons when notAllowCustomCredential is true', () => {
|
||||
const pluginPayload = createPluginPayload()
|
||||
|
||||
const { container } = render(
|
||||
render(
|
||||
<Authorize
|
||||
pluginPayload={pluginPayload}
|
||||
canOAuth={true}
|
||||
@ -340,8 +335,8 @@ describe('Authorize', () => {
|
||||
{ wrapper: createWrapper() },
|
||||
)
|
||||
|
||||
const wrappers = container.querySelectorAll('.opacity-50')
|
||||
expect(wrappers.length).toBe(2) // Both OAuth and API Key wrappers
|
||||
const buttons = screen.getAllByRole('button')
|
||||
buttons.forEach(button => expect(button).toBeDisabled())
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -459,7 +454,7 @@ describe('Authorize', () => {
|
||||
expect(screen.getAllByRole('button').length).toBe(2)
|
||||
})
|
||||
|
||||
it('should update button variant when theme changes', () => {
|
||||
it('should change button styling when theme changes', () => {
|
||||
const pluginPayload = createPluginPayload()
|
||||
|
||||
const { rerender } = render(
|
||||
@ -471,9 +466,7 @@ describe('Authorize', () => {
|
||||
{ wrapper: createWrapper() },
|
||||
)
|
||||
|
||||
const buttonPrimary = screen.getByRole('button')
|
||||
// Primary theme with canOAuth=false should have primary variant
|
||||
expect(buttonPrimary.className).toContain('btn-primary')
|
||||
const primaryClassName = screen.getByRole('button').className
|
||||
|
||||
rerender(
|
||||
<Authorize
|
||||
@ -483,7 +476,8 @@ describe('Authorize', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByRole('button').className).toContain('btn-secondary')
|
||||
const secondaryClassName = screen.getByRole('button').className
|
||||
expect(primaryClassName).not.toBe(secondaryClassName)
|
||||
})
|
||||
})
|
||||
|
||||
@ -568,44 +562,16 @@ describe('Authorize', () => {
|
||||
// ==================== Component Memoization ====================
|
||||
describe('Component Memoization', () => {
|
||||
it('should be a memoized component (exported with memo)', async () => {
|
||||
const AuthorizeDefault = (await import('./index')).default
|
||||
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(
|
||||
<Authorize
|
||||
pluginPayload={pluginPayload}
|
||||
canOAuth={true}
|
||||
notAllowCustomCredential={false}
|
||||
onUpdate={onUpdate}
|
||||
/>,
|
||||
{ wrapper: createWrapper() },
|
||||
)
|
||||
|
||||
const initialOpacityElements = container.querySelectorAll('.opacity-50').length
|
||||
|
||||
rerender(
|
||||
<Authorize
|
||||
pluginPayload={pluginPayload}
|
||||
canOAuth={true}
|
||||
notAllowCustomCredential={false}
|
||||
onUpdate={onUpdate}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(container.querySelectorAll('.opacity-50').length).toBe(initialOpacityElements)
|
||||
})
|
||||
|
||||
it('should update wrapper when notAllowCustomCredential changes', () => {
|
||||
it('should reflect notAllowCustomCredential change via button disabled state', () => {
|
||||
const pluginPayload = createPluginPayload()
|
||||
|
||||
const { rerender, container } = render(
|
||||
const { rerender } = render(
|
||||
<Authorize
|
||||
pluginPayload={pluginPayload}
|
||||
canOAuth={true}
|
||||
@ -614,7 +580,7 @@ describe('Authorize', () => {
|
||||
{ wrapper: createWrapper() },
|
||||
)
|
||||
|
||||
expect(container.querySelectorAll('.opacity-50').length).toBe(0)
|
||||
expect(screen.getByRole('button')).not.toBeDisabled()
|
||||
|
||||
rerender(
|
||||
<Authorize
|
||||
@ -624,7 +590,7 @@ describe('Authorize', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(container.querySelectorAll('.opacity-50').length).toBe(1)
|
||||
expect(screen.getByRole('button')).toBeDisabled()
|
||||
})
|
||||
})
|
||||
|
||||
@ -0,0 +1,179 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { AuthCategory } from '../../types'
|
||||
|
||||
const mockNotify = vi.fn()
|
||||
const mockSetPluginOAuthCustomClient = vi.fn().mockResolvedValue({})
|
||||
const mockDeletePluginOAuthCustomClient = vi.fn().mockResolvedValue({})
|
||||
const mockInvalidPluginOAuthClientSchema = vi.fn()
|
||||
const mockFormValues = { isCheckValidated: true, values: { __oauth_client__: 'custom', client_id: 'test-id' } }
|
||||
|
||||
vi.mock('@/app/components/base/toast', () => ({
|
||||
useToastContext: () => ({
|
||||
notify: mockNotify,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('../../hooks/use-credential', () => ({
|
||||
useSetPluginOAuthCustomClientHook: () => ({
|
||||
mutateAsync: mockSetPluginOAuthCustomClient,
|
||||
}),
|
||||
useDeletePluginOAuthCustomClientHook: () => ({
|
||||
mutateAsync: mockDeletePluginOAuthCustomClient,
|
||||
}),
|
||||
useInvalidPluginOAuthClientSchemaHook: () => mockInvalidPluginOAuthClientSchema,
|
||||
}))
|
||||
|
||||
vi.mock('../../../readme-panel/entrance', () => ({
|
||||
ReadmeEntrance: () => <div data-testid="readme-entrance" />,
|
||||
}))
|
||||
|
||||
vi.mock('../../../readme-panel/store', () => ({
|
||||
ReadmeShowType: { modal: 'modal' },
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/modal/modal', () => ({
|
||||
default: ({ children, title, onClose: _onClose, onConfirm, onCancel, onExtraButtonClick, footerSlot }: {
|
||||
children: React.ReactNode
|
||||
title: string
|
||||
onClose?: () => void
|
||||
onConfirm?: () => void
|
||||
onCancel?: () => void
|
||||
onExtraButtonClick?: () => void
|
||||
footerSlot?: React.ReactNode
|
||||
[key: string]: unknown
|
||||
}) => (
|
||||
<div data-testid="modal">
|
||||
<div data-testid="modal-title">{title}</div>
|
||||
{children}
|
||||
<button data-testid="modal-confirm" onClick={onConfirm}>Save And Auth</button>
|
||||
<button data-testid="modal-cancel" onClick={onCancel}>Save Only</button>
|
||||
<button data-testid="modal-close" onClick={onExtraButtonClick}>Cancel</button>
|
||||
{!!footerSlot && <div data-testid="footer-slot">{footerSlot}</div>}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/form/form-scenarios/auth', () => ({
|
||||
default: React.forwardRef((_props: Record<string, unknown>, ref: React.Ref<unknown>) => {
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
getFormValues: () => mockFormValues,
|
||||
}))
|
||||
return <div data-testid="auth-form" />
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@tanstack/react-form', () => ({
|
||||
useForm: (config: Record<string, unknown>) => ({
|
||||
store: { subscribe: vi.fn(), getState: () => ({ values: config.defaultValues || {} }) },
|
||||
}),
|
||||
useStore: (_store: unknown, selector: (state: Record<string, unknown>) => unknown) => {
|
||||
return selector({ values: { __oauth_client__: 'custom' } })
|
||||
},
|
||||
}))
|
||||
|
||||
const basePayload = {
|
||||
category: AuthCategory.tool,
|
||||
provider: 'test-provider',
|
||||
}
|
||||
|
||||
const defaultSchemas = [
|
||||
{ name: 'client_id', label: 'Client ID', type: 'text-input', required: true },
|
||||
] as never
|
||||
|
||||
describe('OAuthClientSettings', () => {
|
||||
let OAuthClientSettings: (typeof import('../oauth-client-settings'))['default']
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks()
|
||||
const mod = await import('../oauth-client-settings')
|
||||
OAuthClientSettings = mod.default
|
||||
})
|
||||
|
||||
it('should render modal with correct title', () => {
|
||||
render(
|
||||
<OAuthClientSettings
|
||||
pluginPayload={basePayload}
|
||||
schemas={defaultSchemas}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('modal-title')).toHaveTextContent('plugin.auth.oauthClientSettings')
|
||||
})
|
||||
|
||||
it('should render auth form', () => {
|
||||
render(
|
||||
<OAuthClientSettings
|
||||
pluginPayload={basePayload}
|
||||
schemas={defaultSchemas}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('auth-form')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should call onClose when cancel clicked', () => {
|
||||
const mockOnClose = vi.fn()
|
||||
render(
|
||||
<OAuthClientSettings
|
||||
pluginPayload={basePayload}
|
||||
schemas={defaultSchemas}
|
||||
onClose={mockOnClose}
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByTestId('modal-close'))
|
||||
expect(mockOnClose).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should save settings on save only button click', async () => {
|
||||
const mockOnClose = vi.fn()
|
||||
const mockOnUpdate = vi.fn()
|
||||
render(
|
||||
<OAuthClientSettings
|
||||
pluginPayload={basePayload}
|
||||
schemas={defaultSchemas}
|
||||
onClose={mockOnClose}
|
||||
onUpdate={mockOnUpdate}
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByTestId('modal-cancel'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSetPluginOAuthCustomClient).toHaveBeenCalledWith(expect.objectContaining({
|
||||
enable_oauth_custom_client: true,
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
it('should save and authorize on confirm button click', async () => {
|
||||
const mockOnAuth = vi.fn().mockResolvedValue(undefined)
|
||||
render(
|
||||
<OAuthClientSettings
|
||||
pluginPayload={basePayload}
|
||||
schemas={defaultSchemas}
|
||||
onAuth={mockOnAuth}
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByTestId('modal-confirm'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSetPluginOAuthCustomClient).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
it('should render readme entrance when detail is provided', () => {
|
||||
const payload = { ...basePayload, detail: { name: 'Test' } as never }
|
||||
render(
|
||||
<OAuthClientSettings
|
||||
pluginPayload={payload}
|
||||
schemas={defaultSchemas}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('readme-entrance')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user