mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 02:18:08 +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:
@ -4,20 +4,15 @@ import userEvent from '@testing-library/user-event'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
|
||||
// ============================================================================
|
||||
// Component Imports (after mocks)
|
||||
// ============================================================================
|
||||
|
||||
import ApiAccess from './api-access'
|
||||
import ApiAccessCard from './api-access/card'
|
||||
import ExtraInfo from './index'
|
||||
import Statistics from './statistics'
|
||||
import ApiAccess from '../api-access'
|
||||
import ApiAccessCard from '../api-access/card'
|
||||
import ExtraInfo from '../index'
|
||||
import Statistics from '../statistics'
|
||||
|
||||
// ============================================================================
|
||||
// Mock Setup
|
||||
// ============================================================================
|
||||
|
||||
// Mock next/navigation
|
||||
vi.mock('next/navigation', () => ({
|
||||
useRouter: () => ({
|
||||
push: vi.fn(),
|
||||
@ -69,7 +64,6 @@ vi.mock('@/context/app-context', () => ({
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock service hooks
|
||||
const mockEnableDatasetServiceApi = vi.fn(() => Promise.resolve({ result: 'success' }))
|
||||
const mockDisableDatasetServiceApi = vi.fn(() => Promise.resolve({ result: 'success' }))
|
||||
|
||||
@ -111,9 +105,7 @@ vi.mock('@/app/components/develop/secret-key/secret-key-modal', () => ({
|
||||
),
|
||||
}))
|
||||
|
||||
// ============================================================================
|
||||
// Test Data Factory
|
||||
// ============================================================================
|
||||
|
||||
const createMockRelatedApp = (overrides: Partial<RelatedApp> = {}): RelatedApp => ({
|
||||
id: 'app-1',
|
||||
@ -132,9 +124,7 @@ const createMockRelatedAppsResponse = (count: number = 2): RelatedAppResponse =>
|
||||
total: count,
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// Statistics Component Tests
|
||||
// ============================================================================
|
||||
|
||||
describe('Statistics', () => {
|
||||
beforeEach(() => {
|
||||
@ -372,9 +362,7 @@ describe('Statistics', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// ApiAccess Component Tests
|
||||
// ============================================================================
|
||||
|
||||
describe('ApiAccess', () => {
|
||||
beforeEach(() => {
|
||||
@ -528,9 +516,7 @@ describe('ApiAccess', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// ApiAccessCard Component Tests
|
||||
// ============================================================================
|
||||
|
||||
describe('ApiAccessCard', () => {
|
||||
beforeEach(() => {
|
||||
@ -745,9 +731,7 @@ describe('ApiAccessCard', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// ExtraInfo (Main Component) Tests
|
||||
// ============================================================================
|
||||
|
||||
describe('ExtraInfo', () => {
|
||||
beforeEach(() => {
|
||||
@ -1101,10 +1085,6 @@ describe('ExtraInfo', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// Integration Tests
|
||||
// ============================================================================
|
||||
|
||||
describe('ExtraInfo Integration', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
@ -1142,7 +1122,6 @@ describe('ExtraInfo Integration', () => {
|
||||
expect(screen.getByText('10')).toBeInTheDocument()
|
||||
expect(screen.getByText('3')).toBeInTheDocument()
|
||||
|
||||
// Click on ApiAccess to open the card
|
||||
const apiAccessTrigger = screen.getByText(/appMenus\.apiAccess/i).closest('[class*="cursor-pointer"]')
|
||||
if (apiAccessTrigger)
|
||||
await user.click(apiAccessTrigger)
|
||||
@ -2,14 +2,7 @@ import type { RelatedApp, RelatedAppResponse } from '@/models/datasets'
|
||||
import { cleanup, render, screen } from '@testing-library/react'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import Statistics from './statistics'
|
||||
|
||||
// Mock react-i18next
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => key,
|
||||
}),
|
||||
}))
|
||||
import Statistics from '../statistics'
|
||||
|
||||
// Mock useDocLink
|
||||
vi.mock('@/context/i18n', () => ({
|
||||
@ -43,7 +36,7 @@ describe('Statistics', () => {
|
||||
|
||||
it('should render document label', () => {
|
||||
render(<Statistics expand={true} documentCount={5} relatedApps={mockRelatedApps} />)
|
||||
expect(screen.getByText('datasetMenus.documents')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.datasetMenus.documents')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render related apps total', () => {
|
||||
@ -53,7 +46,7 @@ describe('Statistics', () => {
|
||||
|
||||
it('should render related app label', () => {
|
||||
render(<Statistics expand={true} documentCount={5} relatedApps={mockRelatedApps} />)
|
||||
expect(screen.getByText('datasetMenus.relatedApp')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.datasetMenus.relatedApp')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render -- for undefined document count', () => {
|
||||
@ -0,0 +1,186 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import Card from '../card'
|
||||
|
||||
// Shared mock state for context selectors
|
||||
let mockDatasetId: string | undefined = 'dataset-123'
|
||||
let mockMutateDatasetRes: ReturnType<typeof vi.fn> = vi.fn()
|
||||
let mockIsCurrentWorkspaceManager = true
|
||||
|
||||
vi.mock('@/context/dataset-detail', () => ({
|
||||
useDatasetDetailContextWithSelector: (selector: (state: Record<string, unknown>) => unknown) =>
|
||||
selector({
|
||||
dataset: { id: mockDatasetId },
|
||||
mutateDatasetRes: mockMutateDatasetRes,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useSelector: (selector: (state: Record<string, unknown>) => unknown) =>
|
||||
selector({ isCurrentWorkspaceManager: mockIsCurrentWorkspaceManager }),
|
||||
}))
|
||||
|
||||
const mockEnableApi = vi.fn()
|
||||
const mockDisableApi = vi.fn()
|
||||
|
||||
vi.mock('@/service/knowledge/use-dataset', () => ({
|
||||
useEnableDatasetServiceApi: () => ({
|
||||
mutateAsync: mockEnableApi,
|
||||
}),
|
||||
useDisableDatasetServiceApi: () => ({
|
||||
mutateAsync: mockDisableApi,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/hooks/use-api-access-url', () => ({
|
||||
useDatasetApiAccessUrl: () => 'https://docs.dify.ai/api-reference/datasets',
|
||||
}))
|
||||
|
||||
describe('Card (API Access)', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockDatasetId = 'dataset-123'
|
||||
mockMutateDatasetRes = vi.fn()
|
||||
mockIsCurrentWorkspaceManager = true
|
||||
})
|
||||
|
||||
// Rendering: verifies enabled/disabled states render correctly
|
||||
describe('Rendering', () => {
|
||||
it('should render without crashing when api is enabled', () => {
|
||||
render(<Card apiEnabled={true} />)
|
||||
expect(screen.getByText(/serviceApi\.enabled/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render without crashing when api is disabled', () => {
|
||||
render(<Card apiEnabled={false} />)
|
||||
expect(screen.getByText(/serviceApi\.disabled/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render API access tip text', () => {
|
||||
render(<Card apiEnabled={true} />)
|
||||
expect(screen.getByText(/appMenus\.apiAccessTip/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render API reference link', () => {
|
||||
render(<Card apiEnabled={true} />)
|
||||
const link = screen.getByRole('link')
|
||||
expect(link).toHaveAttribute('href', 'https://docs.dify.ai/api-reference/datasets')
|
||||
})
|
||||
|
||||
it('should render API doc text in link', () => {
|
||||
render(<Card apiEnabled={true} />)
|
||||
expect(screen.getByText(/apiInfo\.doc/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should open API reference link in new tab', () => {
|
||||
render(<Card apiEnabled={true} />)
|
||||
const link = screen.getByRole('link')
|
||||
expect(link).toHaveAttribute('target', '_blank')
|
||||
expect(link).toHaveAttribute('rel', 'noopener noreferrer')
|
||||
})
|
||||
})
|
||||
|
||||
// Props: tests enabled/disabled visual states
|
||||
describe('Props', () => {
|
||||
it('should show green indicator text when enabled', () => {
|
||||
render(<Card apiEnabled={true} />)
|
||||
const enabledText = screen.getByText(/serviceApi\.enabled/)
|
||||
expect(enabledText).toHaveClass('text-text-success')
|
||||
})
|
||||
|
||||
it('should show warning text when disabled', () => {
|
||||
render(<Card apiEnabled={false} />)
|
||||
const disabledText = screen.getByText(/serviceApi\.disabled/)
|
||||
expect(disabledText).toHaveClass('text-text-warning')
|
||||
})
|
||||
})
|
||||
|
||||
// User Interactions: tests toggle behavior
|
||||
describe('User Interactions', () => {
|
||||
it('should call enableDatasetServiceApi when toggling on', async () => {
|
||||
mockEnableApi.mockResolvedValue({ result: 'success' })
|
||||
render(<Card apiEnabled={false} />)
|
||||
|
||||
const switchButton = screen.getByRole('switch')
|
||||
fireEvent.click(switchButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockEnableApi).toHaveBeenCalledWith('dataset-123')
|
||||
})
|
||||
})
|
||||
|
||||
it('should call disableDatasetServiceApi when toggling off', async () => {
|
||||
mockDisableApi.mockResolvedValue({ result: 'success' })
|
||||
render(<Card apiEnabled={true} />)
|
||||
|
||||
const switchButton = screen.getByRole('switch')
|
||||
fireEvent.click(switchButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockDisableApi).toHaveBeenCalledWith('dataset-123')
|
||||
})
|
||||
})
|
||||
|
||||
it('should call mutateDatasetRes on successful toggle', async () => {
|
||||
mockEnableApi.mockResolvedValue({ result: 'success' })
|
||||
render(<Card apiEnabled={false} />)
|
||||
|
||||
const switchButton = screen.getByRole('switch')
|
||||
fireEvent.click(switchButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockMutateDatasetRes).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not call mutateDatasetRes when result is not success', async () => {
|
||||
mockEnableApi.mockResolvedValue({ result: 'fail' })
|
||||
render(<Card apiEnabled={false} />)
|
||||
|
||||
const switchButton = screen.getByRole('switch')
|
||||
fireEvent.click(switchButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockEnableApi).toHaveBeenCalled()
|
||||
})
|
||||
expect(mockMutateDatasetRes).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
// Switch disabled state
|
||||
describe('Switch State', () => {
|
||||
it('should disable switch when user is not workspace manager', () => {
|
||||
mockIsCurrentWorkspaceManager = false
|
||||
render(<Card apiEnabled={true} />)
|
||||
|
||||
const switchButton = screen.getByRole('switch')
|
||||
expect(switchButton).toHaveAttribute('aria-checked', 'true')
|
||||
// Headless UI Switch uses CSS classes for disabled state, not the disabled attribute
|
||||
expect(switchButton).toHaveClass('!cursor-not-allowed', '!opacity-50')
|
||||
})
|
||||
|
||||
it('should enable switch when user is workspace manager', () => {
|
||||
mockIsCurrentWorkspaceManager = true
|
||||
render(<Card apiEnabled={true} />)
|
||||
|
||||
const switchButton = screen.getByRole('switch')
|
||||
expect(switchButton).not.toBeDisabled()
|
||||
})
|
||||
})
|
||||
|
||||
// Edge Cases: tests boundary scenarios
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle undefined dataset id', async () => {
|
||||
mockDatasetId = undefined
|
||||
mockEnableApi.mockResolvedValue({ result: 'success' })
|
||||
render(<Card apiEnabled={false} />)
|
||||
|
||||
const switchButton = screen.getByRole('switch')
|
||||
fireEvent.click(switchButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockEnableApi).toHaveBeenCalledWith('')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1,13 +1,6 @@
|
||||
import { act, cleanup, fireEvent, render, screen } from '@testing-library/react'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import ApiAccess from './index'
|
||||
|
||||
// Mock react-i18next
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => key,
|
||||
}),
|
||||
}))
|
||||
import ApiAccess from '../index'
|
||||
|
||||
// Mock context and hooks for Card component
|
||||
vi.mock('@/context/dataset-detail', () => ({
|
||||
@ -34,27 +27,27 @@ afterEach(() => {
|
||||
describe('ApiAccess', () => {
|
||||
it('should render without crashing', () => {
|
||||
render(<ApiAccess expand={true} apiEnabled={true} />)
|
||||
expect(screen.getByText('appMenus.apiAccess')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.appMenus.apiAccess')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render API access text when expanded', () => {
|
||||
render(<ApiAccess expand={true} apiEnabled={true} />)
|
||||
expect(screen.getByText('appMenus.apiAccess')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.appMenus.apiAccess')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not render API access text when collapsed', () => {
|
||||
render(<ApiAccess expand={false} apiEnabled={true} />)
|
||||
expect(screen.queryByText('appMenus.apiAccess')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('common.appMenus.apiAccess')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render with apiEnabled=true', () => {
|
||||
render(<ApiAccess expand={true} apiEnabled={true} />)
|
||||
expect(screen.getByText('appMenus.apiAccess')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.appMenus.apiAccess')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render with apiEnabled=false', () => {
|
||||
render(<ApiAccess expand={true} apiEnabled={false} />)
|
||||
expect(screen.getByText('appMenus.apiAccess')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.appMenus.apiAccess')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should be wrapped with React.memo', () => {
|
||||
@ -67,7 +60,6 @@ describe('ApiAccess', () => {
|
||||
const trigger = container.querySelector('.cursor-pointer')
|
||||
expect(trigger).toBeInTheDocument()
|
||||
|
||||
// Click to open
|
||||
await act(async () => {
|
||||
fireEvent.click(trigger!)
|
||||
})
|
||||
@ -0,0 +1,168 @@
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import Card from '../card'
|
||||
|
||||
vi.mock('@/hooks/use-api-access-url', () => ({
|
||||
useDatasetApiAccessUrl: () => 'https://docs.dify.ai/api-reference/datasets',
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/develop/secret-key/secret-key-modal', () => ({
|
||||
default: ({ isShow, onClose }: { isShow: boolean, onClose: () => void }) =>
|
||||
isShow ? <div data-testid="secret-key-modal"><button onClick={onClose}>close</button></div> : null,
|
||||
}))
|
||||
|
||||
const createWrapper = () => {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: { queries: { retry: false } },
|
||||
})
|
||||
return ({ children }: { children: React.ReactNode }) => (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{children}
|
||||
</QueryClientProvider>
|
||||
)
|
||||
}
|
||||
|
||||
const renderWithProviders = (ui: React.ReactElement) => {
|
||||
return render(ui, { wrapper: createWrapper() })
|
||||
}
|
||||
|
||||
describe('Card (Service API)', () => {
|
||||
const defaultProps = {
|
||||
apiBaseUrl: 'https://api.dify.ai/v1',
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
// Rendering: verifies all key elements render
|
||||
describe('Rendering', () => {
|
||||
it('should render without crashing', () => {
|
||||
renderWithProviders(<Card {...defaultProps} />)
|
||||
expect(screen.getByText(/serviceApi\.card\.title/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render card title', () => {
|
||||
renderWithProviders(<Card {...defaultProps} />)
|
||||
expect(screen.getByText(/serviceApi\.card\.title/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render enabled status', () => {
|
||||
renderWithProviders(<Card {...defaultProps} />)
|
||||
expect(screen.getByText(/serviceApi\.enabled/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render endpoint label', () => {
|
||||
renderWithProviders(<Card {...defaultProps} />)
|
||||
expect(screen.getByText(/serviceApi\.card\.endpoint/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render the API base URL', () => {
|
||||
renderWithProviders(<Card {...defaultProps} />)
|
||||
expect(screen.getByText('https://api.dify.ai/v1')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render API key button', () => {
|
||||
renderWithProviders(<Card {...defaultProps} />)
|
||||
expect(screen.getByText(/serviceApi\.card\.apiKey/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render API reference button', () => {
|
||||
renderWithProviders(<Card {...defaultProps} />)
|
||||
expect(screen.getByText(/serviceApi\.card\.apiReference/)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// Props: tests different apiBaseUrl values
|
||||
describe('Props', () => {
|
||||
it('should display provided apiBaseUrl', () => {
|
||||
renderWithProviders(<Card apiBaseUrl="https://custom-api.example.com" />)
|
||||
expect(screen.getByText('https://custom-api.example.com')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show green indicator when apiBaseUrl is provided', () => {
|
||||
renderWithProviders(<Card apiBaseUrl="https://api.dify.ai" />)
|
||||
// The Indicator component receives color="green" when apiBaseUrl is truthy
|
||||
const statusText = screen.getByText(/serviceApi\.enabled/)
|
||||
expect(statusText).toHaveClass('text-text-success')
|
||||
})
|
||||
|
||||
it('should show yellow indicator when apiBaseUrl is empty', () => {
|
||||
renderWithProviders(<Card apiBaseUrl="" />)
|
||||
// Still shows "enabled" text but indicator color differs
|
||||
expect(screen.getByText(/serviceApi\.enabled/)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// User Interactions: tests button clicks and modal
|
||||
describe('User Interactions', () => {
|
||||
it('should open secret key modal when API key button is clicked', () => {
|
||||
renderWithProviders(<Card {...defaultProps} />)
|
||||
|
||||
// Modal should not be visible before clicking
|
||||
expect(screen.queryByTestId('secret-key-modal')).not.toBeInTheDocument()
|
||||
|
||||
const apiKeyButton = screen.getByText(/serviceApi\.card\.apiKey/).closest('button')
|
||||
fireEvent.click(apiKeyButton!)
|
||||
|
||||
// Modal should appear after clicking
|
||||
expect(screen.getByTestId('secret-key-modal')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should close secret key modal when onClose is called', () => {
|
||||
renderWithProviders(<Card {...defaultProps} />)
|
||||
|
||||
const apiKeyButton = screen.getByText(/serviceApi\.card\.apiKey/).closest('button')
|
||||
fireEvent.click(apiKeyButton!)
|
||||
expect(screen.getByTestId('secret-key-modal')).toBeInTheDocument()
|
||||
|
||||
fireEvent.click(screen.getByText('close'))
|
||||
expect(screen.queryByTestId('secret-key-modal')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render API reference as a link', () => {
|
||||
renderWithProviders(<Card {...defaultProps} />)
|
||||
const link = screen.getByRole('link')
|
||||
expect(link).toHaveAttribute('href', 'https://docs.dify.ai/api-reference/datasets')
|
||||
expect(link).toHaveAttribute('target', '_blank')
|
||||
expect(link).toHaveAttribute('rel', 'noopener noreferrer')
|
||||
})
|
||||
})
|
||||
|
||||
// Styles: verifies container structure
|
||||
describe('Styles', () => {
|
||||
it('should have correct container width', () => {
|
||||
const { container } = renderWithProviders(<Card {...defaultProps} />)
|
||||
const wrapper = container.firstChild as HTMLElement
|
||||
expect(wrapper).toHaveClass('w-[360px]')
|
||||
})
|
||||
|
||||
it('should have rounded corners', () => {
|
||||
const { container } = renderWithProviders(<Card {...defaultProps} />)
|
||||
const wrapper = container.firstChild as HTMLElement
|
||||
expect(wrapper).toHaveClass('rounded-xl')
|
||||
})
|
||||
})
|
||||
|
||||
// Edge Cases: tests empty/long URLs
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle empty apiBaseUrl', () => {
|
||||
renderWithProviders(<Card apiBaseUrl="" />)
|
||||
// Should still render the structure
|
||||
expect(screen.getByText(/serviceApi\.card\.endpoint/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle very long apiBaseUrl', () => {
|
||||
const longUrl = `https://api.dify.ai/${'path/'.repeat(50)}`
|
||||
renderWithProviders(<Card apiBaseUrl={longUrl} />)
|
||||
expect(screen.getByText(longUrl)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle apiBaseUrl with special characters', () => {
|
||||
const specialUrl = 'https://api.dify.ai/v1?key=value&foo=bar'
|
||||
renderWithProviders(<Card apiBaseUrl={specialUrl} />)
|
||||
expect(screen.getByText(specialUrl)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -2,18 +2,13 @@ import { render, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
// ============================================================================
|
||||
// Component Imports (after mocks)
|
||||
// ============================================================================
|
||||
|
||||
import Card from './card'
|
||||
import ServiceApi from './index'
|
||||
import Card from '../card'
|
||||
import ServiceApi from '../index'
|
||||
|
||||
// ============================================================================
|
||||
// Mock Setup
|
||||
// ============================================================================
|
||||
|
||||
// Mock next/navigation
|
||||
vi.mock('next/navigation', () => ({
|
||||
useRouter: () => ({
|
||||
push: vi.fn(),
|
||||
@ -48,18 +43,13 @@ vi.mock('@/app/components/develop/secret-key/secret-key-modal', () => ({
|
||||
),
|
||||
}))
|
||||
|
||||
// ============================================================================
|
||||
// ServiceApi Component Tests
|
||||
// ============================================================================
|
||||
|
||||
describe('ServiceApi', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Rendering Tests
|
||||
// --------------------------------------------------------------------------
|
||||
describe('Rendering', () => {
|
||||
it('should render without crashing', () => {
|
||||
render(<ServiceApi apiBaseUrl="https://api.example.com" />)
|
||||
@ -90,9 +80,7 @@ describe('ServiceApi', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Props Variations Tests
|
||||
// --------------------------------------------------------------------------
|
||||
describe('Props Variations', () => {
|
||||
it('should show green Indicator when apiBaseUrl is provided', () => {
|
||||
const { container } = render(<ServiceApi apiBaseUrl="https://api.example.com" />)
|
||||
@ -121,9 +109,6 @@ describe('ServiceApi', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// User Interactions Tests
|
||||
// --------------------------------------------------------------------------
|
||||
describe('User Interactions', () => {
|
||||
it('should toggle popup open state on click', async () => {
|
||||
const user = userEvent.setup()
|
||||
@ -188,9 +173,7 @@ describe('ServiceApi', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Portal and Card Integration Tests
|
||||
// --------------------------------------------------------------------------
|
||||
describe('Portal and Card Integration', () => {
|
||||
it('should render Card component inside portal when open', async () => {
|
||||
const user = userEvent.setup()
|
||||
@ -235,9 +218,6 @@ describe('ServiceApi', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Edge Cases Tests
|
||||
// --------------------------------------------------------------------------
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle rapid toggle clicks gracefully', async () => {
|
||||
const user = userEvent.setup()
|
||||
@ -279,9 +259,6 @@ describe('ServiceApi', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Memoization Tests
|
||||
// --------------------------------------------------------------------------
|
||||
describe('Memoization', () => {
|
||||
it('should be memoized with React.memo', () => {
|
||||
const { rerender } = render(<ServiceApi apiBaseUrl="https://api.example.com" />)
|
||||
@ -310,18 +287,13 @@ describe('ServiceApi', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// Card Component Tests
|
||||
// ============================================================================
|
||||
|
||||
describe('Card (service-api)', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Rendering Tests
|
||||
// --------------------------------------------------------------------------
|
||||
describe('Rendering', () => {
|
||||
it('should render without crashing', () => {
|
||||
render(<Card apiBaseUrl="https://api.example.com" />)
|
||||
@ -380,9 +352,7 @@ describe('Card (service-api)', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Props Variations Tests
|
||||
// --------------------------------------------------------------------------
|
||||
describe('Props Variations', () => {
|
||||
it('should show green Indicator when apiBaseUrl is provided', () => {
|
||||
const { container } = render(<Card apiBaseUrl="https://api.example.com" />)
|
||||
@ -423,9 +393,6 @@ describe('Card (service-api)', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// User Interactions Tests
|
||||
// --------------------------------------------------------------------------
|
||||
describe('User Interactions', () => {
|
||||
it('should open SecretKeyModal when API Key button is clicked', async () => {
|
||||
const user = userEvent.setup()
|
||||
@ -448,7 +415,6 @@ describe('Card (service-api)', () => {
|
||||
|
||||
render(<Card apiBaseUrl="https://api.example.com" />)
|
||||
|
||||
// Open modal
|
||||
const apiKeyButton = screen.getByText(/serviceApi\.card\.apiKey/i).closest('button')
|
||||
if (apiKeyButton)
|
||||
await user.click(apiKeyButton)
|
||||
@ -457,7 +423,6 @@ describe('Card (service-api)', () => {
|
||||
expect(screen.getByTestId('secret-key-modal')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Close modal
|
||||
const closeButton = screen.getByTestId('close-modal-btn')
|
||||
await user.click(closeButton)
|
||||
|
||||
@ -489,7 +454,6 @@ describe('Card (service-api)', () => {
|
||||
// Initially modal should not be visible
|
||||
expect(screen.queryByTestId('secret-key-modal')).not.toBeInTheDocument()
|
||||
|
||||
// Open modal
|
||||
const apiKeyButton = screen.getByText(/serviceApi\.card\.apiKey/i).closest('button')
|
||||
if (apiKeyButton)
|
||||
await user.click(apiKeyButton)
|
||||
@ -499,7 +463,6 @@ describe('Card (service-api)', () => {
|
||||
expect(screen.getByTestId('secret-key-modal')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Close modal
|
||||
const closeButton = screen.getByTestId('close-modal-btn')
|
||||
await user.click(closeButton)
|
||||
|
||||
@ -510,9 +473,7 @@ describe('Card (service-api)', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Modal State Tests
|
||||
// --------------------------------------------------------------------------
|
||||
describe('Modal State', () => {
|
||||
it('should initialize with modal closed', () => {
|
||||
render(<Card apiBaseUrl="https://api.example.com" />)
|
||||
@ -547,7 +508,6 @@ describe('Card (service-api)', () => {
|
||||
expect(screen.getByTestId('secret-key-modal')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Close modal
|
||||
const closeButton = screen.getByTestId('close-modal-btn')
|
||||
await user.click(closeButton)
|
||||
|
||||
@ -587,9 +547,6 @@ describe('Card (service-api)', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Edge Cases Tests
|
||||
// --------------------------------------------------------------------------
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle empty apiBaseUrl gracefully', () => {
|
||||
render(<Card apiBaseUrl="" />)
|
||||
@ -614,12 +571,10 @@ describe('Card (service-api)', () => {
|
||||
|
||||
render(<Card apiBaseUrl="https://api.example.com" />)
|
||||
|
||||
// Click API Key button
|
||||
const apiKeyButton = screen.getByText(/serviceApi\.card\.apiKey/i).closest('button')
|
||||
if (apiKeyButton)
|
||||
await user.click(apiKeyButton)
|
||||
|
||||
// Close modal
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('secret-key-modal')).toBeInTheDocument()
|
||||
})
|
||||
@ -635,9 +590,6 @@ describe('Card (service-api)', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Memoization Tests
|
||||
// --------------------------------------------------------------------------
|
||||
describe('Memoization', () => {
|
||||
it('should be memoized with React.memo', () => {
|
||||
const { rerender } = render(<Card apiBaseUrl="https://api.example.com" />)
|
||||
@ -667,9 +619,7 @@ describe('Card (service-api)', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Copy Functionality Tests
|
||||
// --------------------------------------------------------------------------
|
||||
describe('Copy Functionality', () => {
|
||||
it('should render CopyFeedback component for apiBaseUrl', () => {
|
||||
const { container } = render(<Card apiBaseUrl="https://api.example.com" />)
|
||||
@ -686,10 +636,6 @@ describe('Card (service-api)', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// Integration Tests
|
||||
// ============================================================================
|
||||
|
||||
describe('ServiceApi Integration', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
Reference in New Issue
Block a user