mirror of
https://github.com/langgenius/dify.git
synced 2026-03-05 23:57:06 +08:00
test(web): update tests for credential panel refactoring and new ModelAuthDropdown components
Rewrite credential-panel.spec.tsx to match the new discriminated union state model and variant-driven rendering. Add new test files for useCredentialPanelState hook, SystemQuotaCard Label enhancement, and all ModelAuthDropdown sub-components.
This commit is contained in:
@ -2,74 +2,57 @@ import type { ModelProvider } from '../declarations'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { changeModelProviderPriority } from '@/service/common'
|
||||
import { ConfigurationMethodEnum } from '../declarations'
|
||||
import {
|
||||
ConfigurationMethodEnum,
|
||||
CustomConfigurationStatusEnum,
|
||||
PreferredProviderTypeEnum,
|
||||
} from '../declarations'
|
||||
import CredentialPanel from './credential-panel'
|
||||
|
||||
const mockEventEmitter = { emit: vi.fn() }
|
||||
const mockNotify = vi.fn()
|
||||
const mockUpdateModelList = vi.fn()
|
||||
const mockUpdateModelProviders = vi.fn()
|
||||
const mockCredentialStatus = {
|
||||
hasCredential: true,
|
||||
authorized: true,
|
||||
authRemoved: false,
|
||||
current_credential_name: 'test-credential',
|
||||
notAllowedToUse: false,
|
||||
}
|
||||
const mockTrialCredits = { credits: 100, isExhausted: false, isLoading: false, nextCreditResetDate: undefined }
|
||||
|
||||
vi.mock('@/config', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/config')>()
|
||||
return {
|
||||
...actual,
|
||||
IS_CLOUD_EDITION: true,
|
||||
}
|
||||
return { ...actual, IS_CLOUD_EDITION: true }
|
||||
})
|
||||
|
||||
vi.mock('@/app/components/base/toast', () => ({
|
||||
useToastContext: () => ({
|
||||
notify: mockNotify,
|
||||
}),
|
||||
useToastContext: () => ({ notify: mockNotify }),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/event-emitter', () => ({
|
||||
useEventEmitterContextContext: () => ({
|
||||
eventEmitter: mockEventEmitter,
|
||||
}),
|
||||
useEventEmitterContextContext: () => ({ eventEmitter: mockEventEmitter }),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/common', () => ({
|
||||
changeModelProviderPriority: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/header/account-setting/model-provider-page/model-auth', () => ({
|
||||
ConfigProvider: () => <div data-testid="config-provider" />,
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/header/account-setting/model-provider-page/model-auth/hooks', () => ({
|
||||
useCredentialStatus: () => mockCredentialStatus,
|
||||
}))
|
||||
|
||||
vi.mock('../hooks', () => ({
|
||||
useUpdateModelList: () => mockUpdateModelList,
|
||||
useUpdateModelProviders: () => mockUpdateModelProviders,
|
||||
}))
|
||||
|
||||
vi.mock('./priority-selector', () => ({
|
||||
default: ({ value, onSelect }: { value: string, onSelect: (key: string) => void }) => (
|
||||
<button data-testid="priority-selector" onClick={() => onSelect('custom')}>
|
||||
Priority Selector
|
||||
{' '}
|
||||
{value}
|
||||
</button>
|
||||
vi.mock('./use-trial-credits', () => ({
|
||||
useTrialCredits: () => mockTrialCredits,
|
||||
}))
|
||||
|
||||
vi.mock('./model-auth-dropdown', () => ({
|
||||
default: ({ state, onChangePriority }: { state: { variant: string, hasCredentials: boolean }, onChangePriority: (key: string) => void }) => (
|
||||
<div data-testid="model-auth-dropdown" data-variant={state.variant}>
|
||||
<button data-testid="change-priority-btn" onClick={() => onChangePriority('custom')}>
|
||||
Change Priority
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('./priority-use-tip', () => ({
|
||||
default: () => <div data-testid="priority-use-tip">Priority Tip</div>,
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/header/indicator', () => ({
|
||||
default: ({ color }: { color: string }) => <div data-testid="indicator">{color}</div>,
|
||||
default: ({ color }: { color: string }) => <div data-testid="indicator" data-color={color} />,
|
||||
}))
|
||||
|
||||
const createTestQueryClient = () => new QueryClient({
|
||||
@ -78,6 +61,22 @@ const createTestQueryClient = () => new QueryClient({
|
||||
},
|
||||
})
|
||||
|
||||
const createProvider = (overrides: Partial<ModelProvider> = {}): ModelProvider => ({
|
||||
provider: 'test-provider',
|
||||
provider_credential_schema: { credential_form_schemas: [] },
|
||||
custom_configuration: {
|
||||
status: CustomConfigurationStatusEnum.active,
|
||||
current_credential_id: 'cred-1',
|
||||
current_credential_name: 'test-credential',
|
||||
available_credentials: [{ credential_id: 'cred-1', credential_name: 'test-credential' }],
|
||||
},
|
||||
system_configuration: { enabled: true, current_quota_type: 'trial', quota_configurations: [] },
|
||||
preferred_provider_type: PreferredProviderTypeEnum.system,
|
||||
configurate_methods: [ConfigurationMethodEnum.predefinedModel],
|
||||
supported_model_types: ['llm'],
|
||||
...overrides,
|
||||
} as unknown as ModelProvider)
|
||||
|
||||
const renderWithQueryClient = (provider: ModelProvider) => {
|
||||
const queryClient = createTestQueryClient()
|
||||
return render(
|
||||
@ -88,74 +87,146 @@ const renderWithQueryClient = (provider: ModelProvider) => {
|
||||
}
|
||||
|
||||
describe('CredentialPanel', () => {
|
||||
const mockProvider: ModelProvider = {
|
||||
provider: 'test-provider',
|
||||
provider_credential_schema: true,
|
||||
custom_configuration: { status: 'active' },
|
||||
system_configuration: { enabled: true },
|
||||
preferred_provider_type: 'system',
|
||||
configurate_methods: [ConfigurationMethodEnum.predefinedModel],
|
||||
supported_model_types: ['gpt-4'],
|
||||
} as unknown as ModelProvider
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
Object.assign(mockCredentialStatus, {
|
||||
hasCredential: true,
|
||||
authorized: true,
|
||||
authRemoved: false,
|
||||
current_credential_name: 'test-credential',
|
||||
notAllowedToUse: false,
|
||||
Object.assign(mockTrialCredits, { credits: 100, isExhausted: false, isLoading: false })
|
||||
})
|
||||
|
||||
// Text label variants
|
||||
describe('Text label variants', () => {
|
||||
it('should show "AI credits in use" for credits-active variant', () => {
|
||||
renderWithQueryClient(createProvider())
|
||||
|
||||
expect(screen.getByText(/aiCreditsInUse/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show "Credits exhausted" for credits-exhausted variant', () => {
|
||||
mockTrialCredits.isExhausted = true
|
||||
mockTrialCredits.credits = 0
|
||||
|
||||
renderWithQueryClient(createProvider({
|
||||
custom_configuration: {
|
||||
status: CustomConfigurationStatusEnum.noConfigure,
|
||||
available_credentials: [],
|
||||
},
|
||||
}))
|
||||
|
||||
expect(screen.getByText(/quotaExhausted/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show "No available usage" for no-usage variant', () => {
|
||||
mockTrialCredits.isExhausted = true
|
||||
|
||||
renderWithQueryClient(createProvider({
|
||||
custom_configuration: {
|
||||
status: CustomConfigurationStatusEnum.active,
|
||||
current_credential_id: undefined,
|
||||
current_credential_name: undefined,
|
||||
available_credentials: [{ credential_id: 'cred-1' }],
|
||||
},
|
||||
}))
|
||||
|
||||
expect(screen.getByText(/noAvailableUsage/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show "API key required" for api-required-add variant', () => {
|
||||
renderWithQueryClient(createProvider({
|
||||
preferred_provider_type: PreferredProviderTypeEnum.custom,
|
||||
custom_configuration: {
|
||||
status: CustomConfigurationStatusEnum.noConfigure,
|
||||
available_credentials: [],
|
||||
},
|
||||
}))
|
||||
|
||||
expect(screen.getByText(/apiKeyRequired/)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should show credential name and configuration actions', () => {
|
||||
renderWithQueryClient(mockProvider)
|
||||
// Status label variants (dot + credential name)
|
||||
describe('Status label variants', () => {
|
||||
it('should show green indicator and credential name for api-fallback', () => {
|
||||
mockTrialCredits.isExhausted = true
|
||||
|
||||
expect(screen.getByText('test-credential')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('config-provider')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('priority-selector')).toBeInTheDocument()
|
||||
})
|
||||
renderWithQueryClient(createProvider())
|
||||
|
||||
it('should show unauthorized status label when credential is missing', () => {
|
||||
mockCredentialStatus.hasCredential = false
|
||||
renderWithQueryClient(mockProvider)
|
||||
expect(screen.getByTestId('indicator')).toHaveAttribute('data-color', 'green')
|
||||
expect(screen.getByText('test-credential')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
expect(screen.getByText(/modelProvider\.auth\.unAuthorized/)).toBeInTheDocument()
|
||||
})
|
||||
it('should show green indicator for api-active', () => {
|
||||
renderWithQueryClient(createProvider({
|
||||
preferred_provider_type: PreferredProviderTypeEnum.custom,
|
||||
}))
|
||||
|
||||
it('should show removed credential label and priority tip for custom preference', () => {
|
||||
mockCredentialStatus.authorized = false
|
||||
mockCredentialStatus.authRemoved = true
|
||||
renderWithQueryClient({ ...mockProvider, preferred_provider_type: 'custom' } as ModelProvider)
|
||||
expect(screen.getByTestId('indicator')).toHaveAttribute('data-color', 'green')
|
||||
})
|
||||
|
||||
expect(screen.getByText(/modelProvider\.auth\.authRemoved/)).toBeInTheDocument()
|
||||
expect(screen.getByTestId('priority-use-tip')).toBeInTheDocument()
|
||||
})
|
||||
it('should show red indicator and "Unavailable" for api-unavailable', () => {
|
||||
renderWithQueryClient(createProvider({
|
||||
preferred_provider_type: PreferredProviderTypeEnum.custom,
|
||||
custom_configuration: {
|
||||
status: CustomConfigurationStatusEnum.active,
|
||||
current_credential_id: undefined,
|
||||
current_credential_name: undefined,
|
||||
available_credentials: [{ credential_id: 'cred-1' }],
|
||||
},
|
||||
}))
|
||||
|
||||
it('should change priority and refresh related data after success', async () => {
|
||||
const mockChangePriority = changeModelProviderPriority as ReturnType<typeof vi.fn>
|
||||
mockChangePriority.mockResolvedValue({ result: 'success' })
|
||||
renderWithQueryClient(mockProvider)
|
||||
|
||||
fireEvent.click(screen.getByTestId('priority-selector'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockChangePriority).toHaveBeenCalled()
|
||||
expect(mockNotify).toHaveBeenCalled()
|
||||
expect(mockUpdateModelProviders).toHaveBeenCalled()
|
||||
expect(mockUpdateModelList).toHaveBeenCalledWith('gpt-4')
|
||||
expect(mockEventEmitter.emit).toHaveBeenCalled()
|
||||
expect(screen.getByTestId('indicator')).toHaveAttribute('data-color', 'red')
|
||||
expect(screen.getByText(/unavailable/i)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should render standalone priority selector without provider schema', () => {
|
||||
const providerNoSchema = {
|
||||
...mockProvider,
|
||||
provider_credential_schema: null,
|
||||
} as unknown as ModelProvider
|
||||
renderWithQueryClient(providerNoSchema)
|
||||
expect(screen.getByTestId('priority-selector')).toBeInTheDocument()
|
||||
expect(screen.queryByTestId('config-provider')).not.toBeInTheDocument()
|
||||
// Destructive styling
|
||||
describe('Destructive styling', () => {
|
||||
it('should apply destructive container for credits-exhausted', () => {
|
||||
mockTrialCredits.isExhausted = true
|
||||
|
||||
const { container } = renderWithQueryClient(createProvider({
|
||||
custom_configuration: {
|
||||
status: CustomConfigurationStatusEnum.noConfigure,
|
||||
available_credentials: [],
|
||||
},
|
||||
}))
|
||||
|
||||
const card = container.querySelector('[class*="border-state-destructive"]')
|
||||
expect(card).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should apply default container for credits-active', () => {
|
||||
const { container } = renderWithQueryClient(createProvider())
|
||||
|
||||
const card = container.querySelector('[class*="bg-white"]')
|
||||
expect(card).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
// Priority change
|
||||
describe('Priority change', () => {
|
||||
it('should change priority and refresh data after success', async () => {
|
||||
const mockChangePriority = changeModelProviderPriority as ReturnType<typeof vi.fn>
|
||||
mockChangePriority.mockResolvedValue({ result: 'success' })
|
||||
|
||||
renderWithQueryClient(createProvider())
|
||||
|
||||
fireEvent.click(screen.getByTestId('change-priority-btn'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockChangePriority).toHaveBeenCalled()
|
||||
expect(mockNotify).toHaveBeenCalled()
|
||||
expect(mockUpdateModelProviders).toHaveBeenCalled()
|
||||
expect(mockUpdateModelList).toHaveBeenCalledWith('llm')
|
||||
expect(mockEventEmitter.emit).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// ModelAuthDropdown integration
|
||||
describe('ModelAuthDropdown integration', () => {
|
||||
it('should pass state variant to ModelAuthDropdown', () => {
|
||||
renderWithQueryClient(createProvider())
|
||||
|
||||
expect(screen.getByTestId('model-auth-dropdown')).toHaveAttribute('data-variant', 'credits-active')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -0,0 +1,142 @@
|
||||
import type { Credential, ModelProvider } from '../../declarations'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { CustomConfigurationStatusEnum, PreferredProviderTypeEnum } from '../../declarations'
|
||||
import ApiKeySection from './api-key-section'
|
||||
|
||||
const createCredential = (overrides: Partial<Credential> = {}): Credential => ({
|
||||
credential_id: 'cred-1',
|
||||
credential_name: 'Test API Key',
|
||||
...overrides,
|
||||
})
|
||||
|
||||
const createProvider = (overrides: Partial<ModelProvider> = {}): ModelProvider => ({
|
||||
provider: 'test-provider',
|
||||
allow_custom_token: true,
|
||||
custom_configuration: {
|
||||
status: CustomConfigurationStatusEnum.active,
|
||||
available_credentials: [],
|
||||
},
|
||||
system_configuration: { enabled: true, current_quota_type: 'trial', quota_configurations: [] },
|
||||
preferred_provider_type: PreferredProviderTypeEnum.system,
|
||||
...overrides,
|
||||
} as unknown as ModelProvider)
|
||||
|
||||
describe('ApiKeySection', () => {
|
||||
const handlers = {
|
||||
onItemClick: vi.fn(),
|
||||
onEdit: vi.fn(),
|
||||
onDelete: vi.fn(),
|
||||
onAdd: vi.fn(),
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
// Empty state
|
||||
describe('Empty state (no credentials)', () => {
|
||||
it('should show empty state message', () => {
|
||||
render(
|
||||
<ApiKeySection
|
||||
provider={createProvider()}
|
||||
credentials={[]}
|
||||
selectedCredentialId={undefined}
|
||||
{...handlers}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText(/noApiKeysTitle/)).toBeInTheDocument()
|
||||
expect(screen.getByText(/noApiKeysDescription/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show Add API Key button', () => {
|
||||
render(
|
||||
<ApiKeySection
|
||||
provider={createProvider()}
|
||||
credentials={[]}
|
||||
selectedCredentialId={undefined}
|
||||
{...handlers}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByRole('button', { name: /addApiKey/ })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should call onAdd when Add API Key is clicked', () => {
|
||||
render(
|
||||
<ApiKeySection
|
||||
provider={createProvider()}
|
||||
credentials={[]}
|
||||
selectedCredentialId={undefined}
|
||||
{...handlers}
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /addApiKey/ }))
|
||||
|
||||
expect(handlers.onAdd).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should hide Add API Key button when allow_custom_token is false', () => {
|
||||
render(
|
||||
<ApiKeySection
|
||||
provider={createProvider({ allow_custom_token: false })}
|
||||
credentials={[]}
|
||||
selectedCredentialId={undefined}
|
||||
{...handlers}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.queryByRole('button', { name: /addApiKey/ })).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// With credentials
|
||||
describe('With credentials', () => {
|
||||
const credentials = [
|
||||
createCredential({ credential_id: 'cred-1', credential_name: 'Key Alpha' }),
|
||||
createCredential({ credential_id: 'cred-2', credential_name: 'Key Beta' }),
|
||||
]
|
||||
|
||||
it('should render credential list with header', () => {
|
||||
render(
|
||||
<ApiKeySection
|
||||
provider={createProvider()}
|
||||
credentials={credentials}
|
||||
selectedCredentialId="cred-1"
|
||||
{...handlers}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText(/apiKeys/)).toBeInTheDocument()
|
||||
expect(screen.getByText('Key Alpha')).toBeInTheDocument()
|
||||
expect(screen.getByText('Key Beta')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show Add API Key button in footer', () => {
|
||||
render(
|
||||
<ApiKeySection
|
||||
provider={createProvider()}
|
||||
credentials={credentials}
|
||||
selectedCredentialId="cred-1"
|
||||
{...handlers}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByRole('button', { name: /addApiKey/ })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should hide Add API Key when allow_custom_token is false', () => {
|
||||
render(
|
||||
<ApiKeySection
|
||||
provider={createProvider({ allow_custom_token: false })}
|
||||
credentials={credentials}
|
||||
selectedCredentialId="cred-1"
|
||||
{...handlers}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.queryByRole('button', { name: /addApiKey/ })).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,63 @@
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import CreditsExhaustedAlert from './credits-exhausted-alert'
|
||||
|
||||
const mockTrialCredits = { credits: 0, isExhausted: true, isLoading: false, nextCreditResetDate: undefined }
|
||||
|
||||
vi.mock('../use-trial-credits', () => ({
|
||||
useTrialCredits: () => mockTrialCredits,
|
||||
}))
|
||||
|
||||
describe('CreditsExhaustedAlert', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
Object.assign(mockTrialCredits, { credits: 0 })
|
||||
})
|
||||
|
||||
// Without API key fallback
|
||||
describe('Without API key fallback', () => {
|
||||
it('should show exhausted message', () => {
|
||||
render(<CreditsExhaustedAlert hasApiKeyFallback={false} />)
|
||||
|
||||
expect(screen.getByText(/creditsExhaustedMessage/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show description with upgrade link', () => {
|
||||
render(<CreditsExhaustedAlert hasApiKeyFallback={false} />)
|
||||
|
||||
expect(screen.getByText(/creditsExhaustedDescription/)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// With API key fallback
|
||||
describe('With API key fallback', () => {
|
||||
it('should show fallback message', () => {
|
||||
render(<CreditsExhaustedAlert hasApiKeyFallback />)
|
||||
|
||||
expect(screen.getByText(/creditsExhaustedFallback(?!Description)/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show fallback description', () => {
|
||||
render(<CreditsExhaustedAlert hasApiKeyFallback />)
|
||||
|
||||
expect(screen.getByText(/creditsExhaustedFallbackDescription/)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// Usage display
|
||||
describe('Usage display', () => {
|
||||
it('should show usage label', () => {
|
||||
render(<CreditsExhaustedAlert hasApiKeyFallback={false} />)
|
||||
|
||||
expect(screen.getByText(/usageLabel/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show usage amounts', () => {
|
||||
mockTrialCredits.credits = 200
|
||||
|
||||
render(<CreditsExhaustedAlert hasApiKeyFallback={false} />)
|
||||
|
||||
expect(screen.getByText(/9,800/)).toBeInTheDocument()
|
||||
expect(screen.getByText(/10,000/)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,199 @@
|
||||
import type { CredentialPanelState } from '../use-credential-panel-state'
|
||||
import type { ModelProvider } from '../../declarations'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { CustomConfigurationStatusEnum, PreferredProviderTypeEnum } from '../../declarations'
|
||||
import DropdownContent from './dropdown-content'
|
||||
|
||||
const mockHandleOpenModal = vi.fn()
|
||||
const mockHandleActiveCredential = vi.fn()
|
||||
const mockOpenConfirmDelete = vi.fn()
|
||||
const mockCloseConfirmDelete = vi.fn()
|
||||
const mockHandleConfirmDelete = vi.fn()
|
||||
let mockDeleteCredentialId: string | null = null
|
||||
|
||||
vi.mock('../use-trial-credits', () => ({
|
||||
useTrialCredits: () => ({ credits: 0, isExhausted: true, isLoading: false }),
|
||||
}))
|
||||
|
||||
vi.mock('../../model-auth/hooks', () => ({
|
||||
useAuth: () => ({
|
||||
openConfirmDelete: mockOpenConfirmDelete,
|
||||
closeConfirmDelete: mockCloseConfirmDelete,
|
||||
doingAction: false,
|
||||
handleActiveCredential: mockHandleActiveCredential,
|
||||
handleConfirmDelete: mockHandleConfirmDelete,
|
||||
deleteCredentialId: mockDeleteCredentialId,
|
||||
handleOpenModal: mockHandleOpenModal,
|
||||
}),
|
||||
}))
|
||||
|
||||
const createProvider = (overrides: Partial<ModelProvider> = {}): ModelProvider => ({
|
||||
provider: 'test',
|
||||
custom_configuration: {
|
||||
status: CustomConfigurationStatusEnum.active,
|
||||
current_credential_id: 'cred-1',
|
||||
current_credential_name: 'My Key',
|
||||
available_credentials: [
|
||||
{ credential_id: 'cred-1', credential_name: 'My Key' },
|
||||
{ credential_id: 'cred-2', credential_name: 'Other Key' },
|
||||
],
|
||||
},
|
||||
system_configuration: { enabled: true, current_quota_type: 'trial', quota_configurations: [] },
|
||||
preferred_provider_type: PreferredProviderTypeEnum.system,
|
||||
configurate_methods: ['predefined-model'],
|
||||
supported_model_types: ['llm'],
|
||||
...overrides,
|
||||
} as unknown as ModelProvider)
|
||||
|
||||
const createState = (overrides: Partial<CredentialPanelState> = {}): CredentialPanelState => ({
|
||||
variant: 'api-active',
|
||||
priority: 'apiKey',
|
||||
supportsCredits: true,
|
||||
showPrioritySwitcher: true,
|
||||
hasCredentials: true,
|
||||
isCreditsExhausted: false,
|
||||
credentialName: 'My Key',
|
||||
credits: 100,
|
||||
...overrides,
|
||||
})
|
||||
|
||||
describe('DropdownContent', () => {
|
||||
const onChangePriority = vi.fn()
|
||||
const onClose = vi.fn()
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockDeleteCredentialId = null
|
||||
})
|
||||
|
||||
// Conditional sections rendering
|
||||
describe('Conditional sections', () => {
|
||||
it('should show UsagePrioritySection when showPrioritySwitcher is true', () => {
|
||||
render(
|
||||
<DropdownContent
|
||||
provider={createProvider()}
|
||||
state={createState({ showPrioritySwitcher: true })}
|
||||
onChangePriority={onChangePriority}
|
||||
onClose={onClose}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText(/usagePriority/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should hide UsagePrioritySection when showPrioritySwitcher is false', () => {
|
||||
render(
|
||||
<DropdownContent
|
||||
provider={createProvider()}
|
||||
state={createState({ showPrioritySwitcher: false })}
|
||||
onChangePriority={onChangePriority}
|
||||
onClose={onClose}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.queryByText(/usagePriority/)).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show CreditsExhaustedAlert when credits exhausted and supports credits', () => {
|
||||
render(
|
||||
<DropdownContent
|
||||
provider={createProvider()}
|
||||
state={createState({ isCreditsExhausted: true, supportsCredits: true })}
|
||||
onChangePriority={onChangePriority}
|
||||
onClose={onClose}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getAllByText(/creditsExhausted/).length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('should hide CreditsExhaustedAlert when credits not exhausted', () => {
|
||||
render(
|
||||
<DropdownContent
|
||||
provider={createProvider()}
|
||||
state={createState({ isCreditsExhausted: false })}
|
||||
onChangePriority={onChangePriority}
|
||||
onClose={onClose}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.queryByText(/creditsExhausted/)).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// API key section
|
||||
describe('API key section', () => {
|
||||
it('should render credential items', () => {
|
||||
render(
|
||||
<DropdownContent
|
||||
provider={createProvider()}
|
||||
state={createState()}
|
||||
onChangePriority={onChangePriority}
|
||||
onClose={onClose}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText('My Key')).toBeInTheDocument()
|
||||
expect(screen.getByText('Other Key')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show empty state when no credentials', () => {
|
||||
render(
|
||||
<DropdownContent
|
||||
provider={createProvider({
|
||||
custom_configuration: {
|
||||
status: CustomConfigurationStatusEnum.noConfigure,
|
||||
available_credentials: [],
|
||||
},
|
||||
})}
|
||||
state={createState({ hasCredentials: false })}
|
||||
onChangePriority={onChangePriority}
|
||||
onClose={onClose}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText(/noApiKeysTitle/)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// Add API Key action
|
||||
describe('Add API Key', () => {
|
||||
it('should call handleOpenModal and onClose when adding API key', () => {
|
||||
render(
|
||||
<DropdownContent
|
||||
provider={createProvider({
|
||||
custom_configuration: {
|
||||
status: CustomConfigurationStatusEnum.noConfigure,
|
||||
available_credentials: [],
|
||||
},
|
||||
})}
|
||||
state={createState({ hasCredentials: false })}
|
||||
onChangePriority={onChangePriority}
|
||||
onClose={onClose}
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /addApiKey/ }))
|
||||
|
||||
expect(mockHandleOpenModal).toHaveBeenCalledWith()
|
||||
expect(onClose).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
// Width constraint
|
||||
describe('Layout', () => {
|
||||
it('should have 320px width container', () => {
|
||||
const { container } = render(
|
||||
<DropdownContent
|
||||
provider={createProvider()}
|
||||
state={createState()}
|
||||
onChangePriority={onChangePriority}
|
||||
onClose={onClose}
|
||||
/>,
|
||||
)
|
||||
|
||||
const widthContainer = container.querySelector('.w-\\[320px\\]')
|
||||
expect(widthContainer).toBeTruthy()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,99 @@
|
||||
import type { CredentialPanelState } from '../use-credential-panel-state'
|
||||
import type { ModelProvider } from '../../declarations'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { CustomConfigurationStatusEnum, PreferredProviderTypeEnum } from '../../declarations'
|
||||
import ModelAuthDropdown from './index'
|
||||
|
||||
vi.mock('../../model-auth/hooks', () => ({
|
||||
useAuth: () => ({
|
||||
openConfirmDelete: vi.fn(),
|
||||
closeConfirmDelete: vi.fn(),
|
||||
doingAction: false,
|
||||
handleActiveCredential: vi.fn(),
|
||||
handleConfirmDelete: vi.fn(),
|
||||
deleteCredentialId: null,
|
||||
handleOpenModal: vi.fn(),
|
||||
}),
|
||||
}))
|
||||
|
||||
const createProvider = (): ModelProvider => ({
|
||||
provider: 'test',
|
||||
custom_configuration: {
|
||||
status: CustomConfigurationStatusEnum.active,
|
||||
available_credentials: [],
|
||||
},
|
||||
system_configuration: { enabled: true, current_quota_type: 'trial', quota_configurations: [] },
|
||||
preferred_provider_type: PreferredProviderTypeEnum.system,
|
||||
} as unknown as ModelProvider)
|
||||
|
||||
const createState = (overrides: Partial<CredentialPanelState> = {}): CredentialPanelState => ({
|
||||
variant: 'credits-active',
|
||||
priority: 'credits',
|
||||
supportsCredits: true,
|
||||
showPrioritySwitcher: false,
|
||||
hasCredentials: false,
|
||||
isCreditsExhausted: false,
|
||||
credentialName: undefined,
|
||||
credits: 100,
|
||||
...overrides,
|
||||
})
|
||||
|
||||
describe('ModelAuthDropdown', () => {
|
||||
const onChangePriority = vi.fn()
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
// Button text based on variant
|
||||
describe('Button configuration', () => {
|
||||
it('should show "Add API Key" when no credentials and non-accent variant', () => {
|
||||
render(
|
||||
<ModelAuthDropdown
|
||||
provider={createProvider()}
|
||||
state={createState({ hasCredentials: false, variant: 'credits-active' })}
|
||||
onChangePriority={onChangePriority}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByRole('button', { name: /addApiKey/ })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show "Configure" when has credentials and non-accent variant', () => {
|
||||
render(
|
||||
<ModelAuthDropdown
|
||||
provider={createProvider()}
|
||||
state={createState({ hasCredentials: true, variant: 'api-active' })}
|
||||
onChangePriority={onChangePriority}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByRole('button', { name: /config/ })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show "Add API Key" for api-required-add variant with accent style', () => {
|
||||
render(
|
||||
<ModelAuthDropdown
|
||||
provider={createProvider()}
|
||||
state={createState({ variant: 'api-required-add', hasCredentials: false })}
|
||||
onChangePriority={onChangePriority}
|
||||
/>,
|
||||
)
|
||||
|
||||
const button = screen.getByRole('button', { name: /addApiKey/ })
|
||||
expect(button).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show "Configure" for api-required-configure variant with accent style', () => {
|
||||
render(
|
||||
<ModelAuthDropdown
|
||||
provider={createProvider()}
|
||||
state={createState({ variant: 'api-required-configure', hasCredentials: true })}
|
||||
onChangePriority={onChangePriority}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByRole('button', { name: /config/ })).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,66 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { PreferredProviderTypeEnum } from '../../declarations'
|
||||
import UsagePrioritySection from './usage-priority-section'
|
||||
|
||||
describe('UsagePrioritySection', () => {
|
||||
const onSelect = vi.fn()
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
// Rendering
|
||||
describe('Rendering', () => {
|
||||
it('should render title and both option buttons', () => {
|
||||
render(<UsagePrioritySection value="credits" onSelect={onSelect} />)
|
||||
|
||||
expect(screen.getByText(/usagePriority/)).toBeInTheDocument()
|
||||
expect(screen.getAllByRole('button')).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
|
||||
// Selection state
|
||||
describe('Selection state', () => {
|
||||
it('should highlight AI credits option when value is credits', () => {
|
||||
render(<UsagePrioritySection value="credits" onSelect={onSelect} />)
|
||||
|
||||
const buttons = screen.getAllByRole('button')
|
||||
expect(buttons[0].className).toContain('border-components-option-card-option-selected-border')
|
||||
expect(buttons[1].className).not.toContain('border-components-option-card-option-selected-border')
|
||||
})
|
||||
|
||||
it('should highlight API key option when value is apiKey', () => {
|
||||
render(<UsagePrioritySection value="apiKey" onSelect={onSelect} />)
|
||||
|
||||
const buttons = screen.getAllByRole('button')
|
||||
expect(buttons[0].className).not.toContain('border-components-option-card-option-selected-border')
|
||||
expect(buttons[1].className).toContain('border-components-option-card-option-selected-border')
|
||||
})
|
||||
|
||||
it('should highlight API key option when value is apiKeyOnly', () => {
|
||||
render(<UsagePrioritySection value="apiKeyOnly" onSelect={onSelect} />)
|
||||
|
||||
const buttons = screen.getAllByRole('button')
|
||||
expect(buttons[1].className).toContain('border-components-option-card-option-selected-border')
|
||||
})
|
||||
})
|
||||
|
||||
// User interactions
|
||||
describe('User interactions', () => {
|
||||
it('should call onSelect with system when clicking AI credits option', () => {
|
||||
render(<UsagePrioritySection value="apiKey" onSelect={onSelect} />)
|
||||
|
||||
fireEvent.click(screen.getAllByRole('button')[0])
|
||||
|
||||
expect(onSelect).toHaveBeenCalledWith(PreferredProviderTypeEnum.system)
|
||||
})
|
||||
|
||||
it('should call onSelect with custom when clicking API key option', () => {
|
||||
render(<UsagePrioritySection value="credits" onSelect={onSelect} />)
|
||||
|
||||
fireEvent.click(screen.getAllByRole('button')[1])
|
||||
|
||||
expect(onSelect).toHaveBeenCalledWith(PreferredProviderTypeEnum.custom)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,89 @@
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import SystemQuotaCard from './system-quota-card'
|
||||
|
||||
describe('SystemQuotaCard', () => {
|
||||
// Renders container with children
|
||||
describe('Rendering', () => {
|
||||
it('should render children', () => {
|
||||
render(
|
||||
<SystemQuotaCard>
|
||||
<span>content</span>
|
||||
</SystemQuotaCard>,
|
||||
)
|
||||
|
||||
expect(screen.getByText('content')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should apply default variant styles', () => {
|
||||
const { container } = render(
|
||||
<SystemQuotaCard>
|
||||
<span>test</span>
|
||||
</SystemQuotaCard>,
|
||||
)
|
||||
|
||||
const card = container.firstElementChild!
|
||||
expect(card.className).toContain('bg-white')
|
||||
})
|
||||
|
||||
it('should apply destructive variant styles', () => {
|
||||
const { container } = render(
|
||||
<SystemQuotaCard variant="destructive">
|
||||
<span>test</span>
|
||||
</SystemQuotaCard>,
|
||||
)
|
||||
|
||||
const card = container.firstElementChild!
|
||||
expect(card.className).toContain('border-state-destructive-border')
|
||||
})
|
||||
})
|
||||
|
||||
// Label sub-component
|
||||
describe('Label', () => {
|
||||
it('should apply default variant text color when no className provided', () => {
|
||||
render(
|
||||
<SystemQuotaCard>
|
||||
<SystemQuotaCard.Label>Default label</SystemQuotaCard.Label>
|
||||
</SystemQuotaCard>,
|
||||
)
|
||||
|
||||
expect(screen.getByText('Default label').className).toContain('text-text-secondary')
|
||||
})
|
||||
|
||||
it('should apply destructive variant text color when no className provided', () => {
|
||||
render(
|
||||
<SystemQuotaCard variant="destructive">
|
||||
<SystemQuotaCard.Label>Error label</SystemQuotaCard.Label>
|
||||
</SystemQuotaCard>,
|
||||
)
|
||||
|
||||
expect(screen.getByText('Error label').className).toContain('text-text-destructive')
|
||||
})
|
||||
|
||||
it('should override variant color with custom className', () => {
|
||||
render(
|
||||
<SystemQuotaCard variant="destructive">
|
||||
<SystemQuotaCard.Label className="gap-1">Custom label</SystemQuotaCard.Label>
|
||||
</SystemQuotaCard>,
|
||||
)
|
||||
|
||||
const label = screen.getByText('Custom label')
|
||||
expect(label.className).toContain('gap-1')
|
||||
expect(label.className).not.toContain('text-text-destructive')
|
||||
})
|
||||
})
|
||||
|
||||
// Actions sub-component
|
||||
describe('Actions', () => {
|
||||
it('should render action children', () => {
|
||||
render(
|
||||
<SystemQuotaCard>
|
||||
<SystemQuotaCard.Actions>
|
||||
<button>Click me</button>
|
||||
</SystemQuotaCard.Actions>
|
||||
</SystemQuotaCard>,
|
||||
)
|
||||
|
||||
expect(screen.getByRole('button', { name: /click me/i })).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,203 @@
|
||||
import type { ModelProvider } from '../declarations'
|
||||
import { renderHook } from '@testing-library/react'
|
||||
import {
|
||||
ConfigurationMethodEnum,
|
||||
CustomConfigurationStatusEnum,
|
||||
PreferredProviderTypeEnum,
|
||||
} from '../declarations'
|
||||
import { isDestructiveVariant, useCredentialPanelState } from './use-credential-panel-state'
|
||||
|
||||
const mockTrialCredits = { credits: 100, isExhausted: false, isLoading: false, nextCreditResetDate: undefined }
|
||||
|
||||
vi.mock('./use-trial-credits', () => ({
|
||||
useTrialCredits: () => mockTrialCredits,
|
||||
}))
|
||||
|
||||
vi.mock('@/config', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/config')>()
|
||||
return { ...actual, IS_CLOUD_EDITION: true }
|
||||
})
|
||||
|
||||
const createProvider = (overrides: Partial<ModelProvider> = {}): ModelProvider => ({
|
||||
provider: 'test-provider',
|
||||
provider_credential_schema: { credential_form_schemas: [] },
|
||||
custom_configuration: {
|
||||
status: CustomConfigurationStatusEnum.active,
|
||||
current_credential_id: 'cred-1',
|
||||
current_credential_name: 'My Key',
|
||||
available_credentials: [{ credential_id: 'cred-1', credential_name: 'My Key' }],
|
||||
},
|
||||
system_configuration: { enabled: true, current_quota_type: 'trial', quota_configurations: [] },
|
||||
preferred_provider_type: PreferredProviderTypeEnum.system,
|
||||
configurate_methods: [ConfigurationMethodEnum.predefinedModel],
|
||||
supported_model_types: ['llm'],
|
||||
...overrides,
|
||||
} as unknown as ModelProvider)
|
||||
|
||||
describe('useCredentialPanelState', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
Object.assign(mockTrialCredits, { credits: 100, isExhausted: false, isLoading: false })
|
||||
})
|
||||
|
||||
// Credits priority variants
|
||||
describe('Credits priority variants', () => {
|
||||
it('should return credits-active when credits available', () => {
|
||||
const { result } = renderHook(() => useCredentialPanelState(createProvider()))
|
||||
|
||||
expect(result.current.variant).toBe('credits-active')
|
||||
expect(result.current.priority).toBe('credits')
|
||||
expect(result.current.supportsCredits).toBe(true)
|
||||
})
|
||||
|
||||
it('should return api-fallback when credits exhausted but API key authorized', () => {
|
||||
mockTrialCredits.isExhausted = true
|
||||
mockTrialCredits.credits = 0
|
||||
|
||||
const { result } = renderHook(() => useCredentialPanelState(createProvider()))
|
||||
|
||||
expect(result.current.variant).toBe('api-fallback')
|
||||
})
|
||||
|
||||
it('should return no-usage when credits exhausted and API key unauthorized', () => {
|
||||
mockTrialCredits.isExhausted = true
|
||||
const provider = createProvider({
|
||||
custom_configuration: {
|
||||
status: CustomConfigurationStatusEnum.active,
|
||||
current_credential_id: undefined,
|
||||
current_credential_name: undefined,
|
||||
available_credentials: [{ credential_id: 'cred-1', credential_name: 'My Key' }],
|
||||
},
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useCredentialPanelState(provider))
|
||||
|
||||
expect(result.current.variant).toBe('no-usage')
|
||||
})
|
||||
|
||||
it('should return credits-exhausted when credits exhausted and no credentials', () => {
|
||||
mockTrialCredits.isExhausted = true
|
||||
const provider = createProvider({
|
||||
custom_configuration: {
|
||||
status: CustomConfigurationStatusEnum.noConfigure,
|
||||
available_credentials: [],
|
||||
},
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useCredentialPanelState(provider))
|
||||
|
||||
expect(result.current.variant).toBe('credits-exhausted')
|
||||
})
|
||||
})
|
||||
|
||||
// API key priority variants
|
||||
describe('API key priority variants', () => {
|
||||
it('should return api-active when API key authorized', () => {
|
||||
const provider = createProvider({
|
||||
preferred_provider_type: PreferredProviderTypeEnum.custom,
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useCredentialPanelState(provider))
|
||||
|
||||
expect(result.current.variant).toBe('api-active')
|
||||
expect(result.current.priority).toBe('apiKey')
|
||||
})
|
||||
|
||||
it('should return api-unavailable when API key unauthorized', () => {
|
||||
const provider = createProvider({
|
||||
preferred_provider_type: PreferredProviderTypeEnum.custom,
|
||||
custom_configuration: {
|
||||
status: CustomConfigurationStatusEnum.active,
|
||||
current_credential_id: undefined,
|
||||
current_credential_name: undefined,
|
||||
available_credentials: [{ credential_id: 'cred-1', credential_name: 'My Key' }],
|
||||
},
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useCredentialPanelState(provider))
|
||||
|
||||
expect(result.current.variant).toBe('api-unavailable')
|
||||
})
|
||||
|
||||
it('should return api-required-add when no credentials exist', () => {
|
||||
const provider = createProvider({
|
||||
preferred_provider_type: PreferredProviderTypeEnum.custom,
|
||||
custom_configuration: {
|
||||
status: CustomConfigurationStatusEnum.noConfigure,
|
||||
available_credentials: [],
|
||||
},
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useCredentialPanelState(provider))
|
||||
|
||||
expect(result.current.variant).toBe('api-required-add')
|
||||
})
|
||||
})
|
||||
|
||||
// apiKeyOnly priority
|
||||
describe('apiKeyOnly priority (non-cloud / system disabled)', () => {
|
||||
it('should return apiKeyOnly when system config disabled', () => {
|
||||
const provider = createProvider({
|
||||
system_configuration: { enabled: false, current_quota_type: 'trial', quota_configurations: [] },
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useCredentialPanelState(provider))
|
||||
|
||||
expect(result.current.priority).toBe('apiKeyOnly')
|
||||
expect(result.current.supportsCredits).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
// Derived metadata
|
||||
describe('Derived metadata', () => {
|
||||
it('should show priority switcher when credits supported and custom config active', () => {
|
||||
const provider = createProvider()
|
||||
|
||||
const { result } = renderHook(() => useCredentialPanelState(provider))
|
||||
|
||||
expect(result.current.showPrioritySwitcher).toBe(true)
|
||||
})
|
||||
|
||||
it('should hide priority switcher when custom config not active', () => {
|
||||
const provider = createProvider({
|
||||
custom_configuration: {
|
||||
status: CustomConfigurationStatusEnum.noConfigure,
|
||||
available_credentials: [],
|
||||
},
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useCredentialPanelState(provider))
|
||||
|
||||
expect(result.current.showPrioritySwitcher).toBe(false)
|
||||
})
|
||||
|
||||
it('should expose credential name from provider', () => {
|
||||
const { result } = renderHook(() => useCredentialPanelState(createProvider()))
|
||||
|
||||
expect(result.current.credentialName).toBe('My Key')
|
||||
})
|
||||
|
||||
it('should expose credits amount', () => {
|
||||
mockTrialCredits.credits = 500
|
||||
|
||||
const { result } = renderHook(() => useCredentialPanelState(createProvider()))
|
||||
|
||||
expect(result.current.credits).toBe(500)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('isDestructiveVariant', () => {
|
||||
it.each([
|
||||
['credits-exhausted', true],
|
||||
['no-usage', true],
|
||||
['api-unavailable', true],
|
||||
['credits-active', false],
|
||||
['api-fallback', false],
|
||||
['api-active', false],
|
||||
['api-required-add', false],
|
||||
['api-required-configure', false],
|
||||
] as const)('should return %s for variant %s', (variant, expected) => {
|
||||
expect(isDestructiveVariant(variant)).toBe(expected)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user