mirror of
https://github.com/langgenius/dify.git
synced 2026-04-30 07:28:05 +08:00
test(web): expand credential panel and dropdown test coverage for all 8 card variants
Add comprehensive behavioral tests covering all discriminated union variants, destructive/default styling, warning icons, CreditsFallbackAlert conditions, credential CRUD interactions, AlertDialog delete confirmation, and Popover behavior.
This commit is contained in:
@ -3,6 +3,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import {
|
||||
ConfigurationMethodEnum,
|
||||
CurrentSystemQuotaTypeEnum,
|
||||
CustomConfigurationStatusEnum,
|
||||
PreferredProviderTypeEnum,
|
||||
} from '../declarations'
|
||||
@ -121,7 +122,7 @@ describe('CredentialPanel', () => {
|
||||
expect(screen.getByText(/aiCreditsInUse/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show "Credits exhausted" for credits-exhausted variant', () => {
|
||||
it('should show "Credits exhausted" for credits-exhausted variant (no credentials)', () => {
|
||||
mockTrialCredits.isExhausted = true
|
||||
mockTrialCredits.credits = 0
|
||||
renderWithQueryClient(createProvider({
|
||||
@ -133,7 +134,7 @@ describe('CredentialPanel', () => {
|
||||
expect(screen.getByText(/quotaExhausted/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show "No available usage" for no-usage variant', () => {
|
||||
it('should show "No available usage" for no-usage variant (exhausted + credential unauthorized)', () => {
|
||||
mockTrialCredits.isExhausted = true
|
||||
renderWithQueryClient(createProvider({
|
||||
custom_configuration: {
|
||||
@ -146,7 +147,7 @@ describe('CredentialPanel', () => {
|
||||
expect(screen.getByText(/noAvailableUsage/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show "API key required" for api-required-add variant', () => {
|
||||
it('should show "API key required" for api-required-add variant (custom priority, no credentials)', () => {
|
||||
renderWithQueryClient(createProvider({
|
||||
preferred_provider_type: PreferredProviderTypeEnum.custom,
|
||||
custom_configuration: {
|
||||
@ -156,21 +157,48 @@ describe('CredentialPanel', () => {
|
||||
}))
|
||||
expect(screen.getByText(/apiKeyRequired/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show "API key required" for api-required-configure variant (custom priority, credential exists but name missing)', () => {
|
||||
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' }],
|
||||
},
|
||||
}))
|
||||
expect(screen.getByText(/apiKeyRequired/)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Status label variants', () => {
|
||||
it('should show green indicator and credential name for api-fallback', () => {
|
||||
it('should show green indicator and credential name for api-fallback (exhausted + authorized key)', () => {
|
||||
mockTrialCredits.isExhausted = true
|
||||
renderWithQueryClient(createProvider())
|
||||
expect(screen.getByTestId('indicator')).toHaveAttribute('data-color', 'green')
|
||||
expect(screen.getByText('test-credential')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show green indicator for api-active', () => {
|
||||
it('should show warning icon for api-fallback variant', () => {
|
||||
mockTrialCredits.isExhausted = true
|
||||
const { container } = renderWithQueryClient(createProvider())
|
||||
expect(container.querySelector('.i-ri-error-warning-fill')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should show green indicator for api-active (custom priority + authorized)', () => {
|
||||
renderWithQueryClient(createProvider({
|
||||
preferred_provider_type: PreferredProviderTypeEnum.custom,
|
||||
}))
|
||||
expect(screen.getByTestId('indicator')).toHaveAttribute('data-color', 'green')
|
||||
expect(screen.getByText('test-credential')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should NOT show warning icon for api-active variant', () => {
|
||||
const { container } = renderWithQueryClient(createProvider({
|
||||
preferred_provider_type: PreferredProviderTypeEnum.custom,
|
||||
}))
|
||||
expect(container.querySelector('.i-ri-error-warning-fill')).toBeNull()
|
||||
})
|
||||
|
||||
it('should show red indicator and "Unavailable" for api-unavailable', () => {
|
||||
@ -185,6 +213,7 @@ describe('CredentialPanel', () => {
|
||||
}))
|
||||
expect(screen.getByTestId('indicator')).toHaveAttribute('data-color', 'red')
|
||||
expect(screen.getByText(/unavailable/i)).toBeInTheDocument()
|
||||
expect(screen.getByText('Bad Key')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -200,14 +229,71 @@ describe('CredentialPanel', () => {
|
||||
expect(container.querySelector('[class*="border-state-destructive"]')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should apply destructive container for no-usage variant', () => {
|
||||
mockTrialCredits.isExhausted = true
|
||||
const { container } = renderWithQueryClient(createProvider({
|
||||
custom_configuration: {
|
||||
status: CustomConfigurationStatusEnum.active,
|
||||
current_credential_id: undefined,
|
||||
current_credential_name: undefined,
|
||||
available_credentials: [{ credential_id: 'cred-1' }],
|
||||
},
|
||||
}))
|
||||
expect(container.querySelector('[class*="border-state-destructive"]')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should apply destructive container for api-unavailable variant', () => {
|
||||
const { container } = renderWithQueryClient(createProvider({
|
||||
preferred_provider_type: PreferredProviderTypeEnum.custom,
|
||||
custom_configuration: {
|
||||
status: CustomConfigurationStatusEnum.active,
|
||||
current_credential_id: undefined,
|
||||
current_credential_name: 'Bad Key',
|
||||
available_credentials: [{ credential_id: 'cred-1', credential_name: 'Bad Key' }],
|
||||
},
|
||||
}))
|
||||
expect(container.querySelector('[class*="border-state-destructive"]')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should apply default container for credits-active', () => {
|
||||
const { container } = renderWithQueryClient(createProvider())
|
||||
expect(container.querySelector('[class*="bg-white"]')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should apply default container for api-active', () => {
|
||||
const { container } = renderWithQueryClient(createProvider({
|
||||
preferred_provider_type: PreferredProviderTypeEnum.custom,
|
||||
}))
|
||||
expect(container.querySelector('[class*="bg-white"]')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should apply default container for api-fallback', () => {
|
||||
mockTrialCredits.isExhausted = true
|
||||
const { container } = renderWithQueryClient(createProvider())
|
||||
expect(container.querySelector('[class*="bg-white"]')).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Text color', () => {
|
||||
it('should use destructive text color for credits-exhausted label', () => {
|
||||
mockTrialCredits.isExhausted = true
|
||||
const { container } = renderWithQueryClient(createProvider({
|
||||
custom_configuration: {
|
||||
status: CustomConfigurationStatusEnum.noConfigure,
|
||||
available_credentials: [],
|
||||
},
|
||||
}))
|
||||
expect(container.querySelector('.text-text-destructive')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should use secondary text color for credits-active label', () => {
|
||||
const { container } = renderWithQueryClient(createProvider())
|
||||
expect(container.querySelector('.text-text-secondary')).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Priority change', () => {
|
||||
it('should call mutation and trigger side effects on success', async () => {
|
||||
it('should call mutation with correct params on priority change', async () => {
|
||||
renderWithQueryClient(createProvider())
|
||||
|
||||
await act(async () => {
|
||||
@ -220,6 +306,14 @@ describe('CredentialPanel', () => {
|
||||
body: { preferred_provider_type: 'custom' },
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should show success toast and refresh data after successful mutation', async () => {
|
||||
renderWithQueryClient(createProvider())
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByTestId('change-priority-btn'))
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockToastNotify).toHaveBeenCalledWith(
|
||||
@ -233,9 +327,92 @@ describe('CredentialPanel', () => {
|
||||
})
|
||||
|
||||
describe('ModelAuthDropdown integration', () => {
|
||||
it('should pass state variant to ModelAuthDropdown', () => {
|
||||
it('should pass credits-active variant to dropdown when credits available', () => {
|
||||
renderWithQueryClient(createProvider())
|
||||
expect(screen.getByTestId('model-auth-dropdown')).toHaveAttribute('data-variant', 'credits-active')
|
||||
})
|
||||
|
||||
it('should pass api-fallback variant to dropdown when exhausted with valid key', () => {
|
||||
mockTrialCredits.isExhausted = true
|
||||
renderWithQueryClient(createProvider())
|
||||
expect(screen.getByTestId('model-auth-dropdown')).toHaveAttribute('data-variant', 'api-fallback')
|
||||
})
|
||||
|
||||
it('should pass credits-exhausted variant when exhausted with no credentials', () => {
|
||||
mockTrialCredits.isExhausted = true
|
||||
renderWithQueryClient(createProvider({
|
||||
custom_configuration: {
|
||||
status: CustomConfigurationStatusEnum.noConfigure,
|
||||
available_credentials: [],
|
||||
},
|
||||
}))
|
||||
expect(screen.getByTestId('model-auth-dropdown')).toHaveAttribute('data-variant', 'credits-exhausted')
|
||||
})
|
||||
|
||||
it('should pass api-active variant for custom priority with authorized key', () => {
|
||||
renderWithQueryClient(createProvider({
|
||||
preferred_provider_type: PreferredProviderTypeEnum.custom,
|
||||
}))
|
||||
expect(screen.getByTestId('model-auth-dropdown')).toHaveAttribute('data-variant', 'api-active')
|
||||
})
|
||||
|
||||
it('should pass api-required-add variant for custom priority with no credentials', () => {
|
||||
renderWithQueryClient(createProvider({
|
||||
preferred_provider_type: PreferredProviderTypeEnum.custom,
|
||||
custom_configuration: {
|
||||
status: CustomConfigurationStatusEnum.noConfigure,
|
||||
available_credentials: [],
|
||||
},
|
||||
}))
|
||||
expect(screen.getByTestId('model-auth-dropdown')).toHaveAttribute('data-variant', 'api-required-add')
|
||||
})
|
||||
|
||||
it('should pass api-unavailable variant for custom priority with named but unauthorized key', () => {
|
||||
renderWithQueryClient(createProvider({
|
||||
preferred_provider_type: PreferredProviderTypeEnum.custom,
|
||||
custom_configuration: {
|
||||
status: CustomConfigurationStatusEnum.active,
|
||||
current_credential_id: undefined,
|
||||
current_credential_name: 'Bad Key',
|
||||
available_credentials: [{ credential_id: 'cred-1', credential_name: 'Bad Key' }],
|
||||
},
|
||||
}))
|
||||
expect(screen.getByTestId('model-auth-dropdown')).toHaveAttribute('data-variant', 'api-unavailable')
|
||||
})
|
||||
|
||||
it('should pass no-usage variant when exhausted + credential but unauthorized', () => {
|
||||
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.getByTestId('model-auth-dropdown')).toHaveAttribute('data-variant', 'no-usage')
|
||||
})
|
||||
})
|
||||
|
||||
describe('apiKeyOnly priority (system disabled)', () => {
|
||||
it('should derive api-required-add when system config disabled and no credentials', () => {
|
||||
renderWithQueryClient(createProvider({
|
||||
system_configuration: { enabled: false, current_quota_type: CurrentSystemQuotaTypeEnum.trial, quota_configurations: [] },
|
||||
preferred_provider_type: PreferredProviderTypeEnum.system,
|
||||
custom_configuration: {
|
||||
status: CustomConfigurationStatusEnum.noConfigure,
|
||||
available_credentials: [],
|
||||
},
|
||||
}))
|
||||
expect(screen.getByTestId('model-auth-dropdown')).toHaveAttribute('data-variant', 'api-required-add')
|
||||
expect(screen.getByText(/apiKeyRequired/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should derive api-active when system config disabled but has authorized key', () => {
|
||||
renderWithQueryClient(createProvider({
|
||||
system_configuration: { enabled: false, current_quota_type: CurrentSystemQuotaTypeEnum.trial, quota_configurations: [] },
|
||||
}))
|
||||
expect(screen.getByTestId('model-auth-dropdown')).toHaveAttribute('data-variant', 'api-active')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -27,6 +27,22 @@ vi.mock('../../model-auth/hooks', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('../../model-auth/authorized/credential-item', () => ({
|
||||
default: ({ credential, onItemClick, onEdit, onDelete }: {
|
||||
credential: { credential_id: string, credential_name: string }
|
||||
onItemClick?: (c: unknown) => void
|
||||
onEdit?: (c: unknown) => void
|
||||
onDelete?: (c: unknown) => void
|
||||
}) => (
|
||||
<div data-testid={`credential-${credential.credential_id}`}>
|
||||
<span>{credential.credential_name}</span>
|
||||
<button data-testid={`click-${credential.credential_id}`} onClick={() => onItemClick?.(credential)}>select</button>
|
||||
<button data-testid={`edit-${credential.credential_id}`} onClick={() => onEdit?.(credential)}>edit</button>
|
||||
<button data-testid={`delete-${credential.credential_id}`} onClick={() => onDelete?.(credential)}>delete</button>
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
const createProvider = (overrides: Partial<ModelProvider> = {}): ModelProvider => ({
|
||||
provider: 'test',
|
||||
custom_configuration: {
|
||||
@ -66,9 +82,8 @@ describe('DropdownContent', () => {
|
||||
mockDeleteCredentialId = null
|
||||
})
|
||||
|
||||
// Conditional sections rendering
|
||||
describe('Conditional sections', () => {
|
||||
it('should show UsagePrioritySection when showPrioritySwitcher is true', () => {
|
||||
describe('UsagePrioritySection visibility', () => {
|
||||
it('should show when showPrioritySwitcher is true', () => {
|
||||
render(
|
||||
<DropdownContent
|
||||
provider={createProvider()}
|
||||
@ -78,11 +93,10 @@ describe('DropdownContent', () => {
|
||||
onClose={onClose}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText(/usagePriority/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should hide UsagePrioritySection when showPrioritySwitcher is false', () => {
|
||||
it('should hide when showPrioritySwitcher is false', () => {
|
||||
render(
|
||||
<DropdownContent
|
||||
provider={createProvider()}
|
||||
@ -92,11 +106,12 @@ describe('DropdownContent', () => {
|
||||
onClose={onClose}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.queryByText(/usagePriority/)).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should show CreditsExhaustedAlert when credits exhausted and supports credits', () => {
|
||||
describe('CreditsExhaustedAlert', () => {
|
||||
it('should show when credits exhausted and supports credits', () => {
|
||||
render(
|
||||
<DropdownContent
|
||||
provider={createProvider()}
|
||||
@ -106,11 +121,10 @@ describe('DropdownContent', () => {
|
||||
onClose={onClose}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getAllByText(/creditsExhausted/).length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('should hide CreditsExhaustedAlert when credits not exhausted', () => {
|
||||
it('should hide when credits not exhausted', () => {
|
||||
render(
|
||||
<DropdownContent
|
||||
provider={createProvider()}
|
||||
@ -120,14 +134,140 @@ describe('DropdownContent', () => {
|
||||
onClose={onClose}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.queryByText(/creditsExhausted/)).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should hide when credits exhausted but supportsCredits is false', () => {
|
||||
render(
|
||||
<DropdownContent
|
||||
provider={createProvider()}
|
||||
state={createState({ isCreditsExhausted: true, supportsCredits: false })}
|
||||
isChangingPriority={false}
|
||||
onChangePriority={onChangePriority}
|
||||
onClose={onClose}
|
||||
/>,
|
||||
)
|
||||
expect(screen.queryByText(/creditsExhausted/)).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show fallback message when api-fallback variant with exhausted credits', () => {
|
||||
render(
|
||||
<DropdownContent
|
||||
provider={createProvider()}
|
||||
state={createState({
|
||||
variant: 'api-fallback',
|
||||
isCreditsExhausted: true,
|
||||
supportsCredits: true,
|
||||
priority: 'credits',
|
||||
})}
|
||||
isChangingPriority={false}
|
||||
onChangePriority={onChangePriority}
|
||||
onClose={onClose}
|
||||
/>,
|
||||
)
|
||||
expect(screen.getAllByText(/creditsExhaustedFallback/).length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('should show non-fallback message when credits-exhausted variant', () => {
|
||||
render(
|
||||
<DropdownContent
|
||||
provider={createProvider()}
|
||||
state={createState({
|
||||
variant: 'credits-exhausted',
|
||||
isCreditsExhausted: true,
|
||||
supportsCredits: true,
|
||||
hasCredentials: false,
|
||||
priority: 'credits',
|
||||
})}
|
||||
isChangingPriority={false}
|
||||
onChangePriority={onChangePriority}
|
||||
onClose={onClose}
|
||||
/>,
|
||||
)
|
||||
expect(screen.getByText(/creditsExhaustedMessage/)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('CreditsFallbackAlert', () => {
|
||||
it('should show when priority is apiKey, supports credits, not exhausted, and variant is not api-active', () => {
|
||||
render(
|
||||
<DropdownContent
|
||||
provider={createProvider()}
|
||||
state={createState({
|
||||
variant: 'api-required-add',
|
||||
priority: 'apiKey',
|
||||
supportsCredits: true,
|
||||
isCreditsExhausted: false,
|
||||
hasCredentials: false,
|
||||
})}
|
||||
isChangingPriority={false}
|
||||
onChangePriority={onChangePriority}
|
||||
onClose={onClose}
|
||||
/>,
|
||||
)
|
||||
expect(screen.getByText(/noApiKeysFallback/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show unavailable message when priority is apiKey with credentials but not api-active', () => {
|
||||
render(
|
||||
<DropdownContent
|
||||
provider={createProvider()}
|
||||
state={createState({
|
||||
variant: 'api-unavailable',
|
||||
priority: 'apiKey',
|
||||
supportsCredits: true,
|
||||
isCreditsExhausted: false,
|
||||
hasCredentials: true,
|
||||
})}
|
||||
isChangingPriority={false}
|
||||
onChangePriority={onChangePriority}
|
||||
onClose={onClose}
|
||||
/>,
|
||||
)
|
||||
expect(screen.getAllByText(/apiKeyUnavailableFallback/).length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('should NOT show when variant is api-active', () => {
|
||||
render(
|
||||
<DropdownContent
|
||||
provider={createProvider()}
|
||||
state={createState({
|
||||
variant: 'api-active',
|
||||
priority: 'apiKey',
|
||||
supportsCredits: true,
|
||||
isCreditsExhausted: false,
|
||||
})}
|
||||
isChangingPriority={false}
|
||||
onChangePriority={onChangePriority}
|
||||
onClose={onClose}
|
||||
/>,
|
||||
)
|
||||
expect(screen.queryByText(/noApiKeysFallback/)).not.toBeInTheDocument()
|
||||
expect(screen.queryByText(/apiKeyUnavailableFallback/)).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should NOT show when priority is credits', () => {
|
||||
render(
|
||||
<DropdownContent
|
||||
provider={createProvider()}
|
||||
state={createState({
|
||||
variant: 'credits-active',
|
||||
priority: 'credits',
|
||||
supportsCredits: true,
|
||||
isCreditsExhausted: false,
|
||||
})}
|
||||
isChangingPriority={false}
|
||||
onChangePriority={onChangePriority}
|
||||
onClose={onClose}
|
||||
/>,
|
||||
)
|
||||
expect(screen.queryByText(/noApiKeysFallback/)).not.toBeInTheDocument()
|
||||
expect(screen.queryByText(/apiKeyUnavailableFallback/)).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// API key section
|
||||
describe('API key section', () => {
|
||||
it('should render credential items', () => {
|
||||
it('should render all credential items', () => {
|
||||
render(
|
||||
<DropdownContent
|
||||
provider={createProvider()}
|
||||
@ -137,7 +277,6 @@ describe('DropdownContent', () => {
|
||||
onClose={onClose}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText('My Key')).toBeInTheDocument()
|
||||
expect(screen.getByText('Other Key')).toBeInTheDocument()
|
||||
})
|
||||
@ -157,14 +296,69 @@ describe('DropdownContent', () => {
|
||||
onClose={onClose}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText(/noApiKeysTitle/)).toBeInTheDocument()
|
||||
expect(screen.getByText(/noApiKeysDescription/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should call handleActiveCredential and close on credential item click', () => {
|
||||
render(
|
||||
<DropdownContent
|
||||
provider={createProvider()}
|
||||
state={createState()}
|
||||
isChangingPriority={false}
|
||||
onChangePriority={onChangePriority}
|
||||
onClose={onClose}
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByTestId('click-cred-1'))
|
||||
|
||||
expect(mockHandleActiveCredential).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ credential_id: 'cred-1' }),
|
||||
)
|
||||
expect(onClose).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should call handleOpenModal and close on edit credential', () => {
|
||||
render(
|
||||
<DropdownContent
|
||||
provider={createProvider()}
|
||||
state={createState()}
|
||||
isChangingPriority={false}
|
||||
onChangePriority={onChangePriority}
|
||||
onClose={onClose}
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByTestId('edit-cred-2'))
|
||||
|
||||
expect(mockHandleOpenModal).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ credential_id: 'cred-2' }),
|
||||
)
|
||||
expect(onClose).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should call openConfirmDelete on delete credential', () => {
|
||||
render(
|
||||
<DropdownContent
|
||||
provider={createProvider()}
|
||||
state={createState()}
|
||||
isChangingPriority={false}
|
||||
onChangePriority={onChangePriority}
|
||||
onClose={onClose}
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByTestId('delete-cred-2'))
|
||||
|
||||
expect(mockOpenConfirmDelete).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ credential_id: 'cred-2' }),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
// Add API Key action
|
||||
describe('Add API Key', () => {
|
||||
it('should call handleOpenModal and onClose when adding API key', () => {
|
||||
it('should call handleOpenModal with no args and close on add', () => {
|
||||
render(
|
||||
<DropdownContent
|
||||
provider={createProvider({
|
||||
@ -187,7 +381,36 @@ describe('DropdownContent', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// Width constraint
|
||||
describe('AlertDialog for delete confirmation', () => {
|
||||
it('should show confirm dialog when deleteCredentialId is set', () => {
|
||||
mockDeleteCredentialId = 'cred-1'
|
||||
render(
|
||||
<DropdownContent
|
||||
provider={createProvider()}
|
||||
state={createState()}
|
||||
isChangingPriority={false}
|
||||
onChangePriority={onChangePriority}
|
||||
onClose={onClose}
|
||||
/>,
|
||||
)
|
||||
expect(screen.getByText(/confirmDelete/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not show confirm dialog when deleteCredentialId is null', () => {
|
||||
mockDeleteCredentialId = null
|
||||
render(
|
||||
<DropdownContent
|
||||
provider={createProvider()}
|
||||
state={createState()}
|
||||
isChangingPriority={false}
|
||||
onChangePriority={onChangePriority}
|
||||
onClose={onClose}
|
||||
/>,
|
||||
)
|
||||
expect(screen.queryByText(/confirmDelete/)).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Layout', () => {
|
||||
it('should have 320px width container', () => {
|
||||
const { container } = render(
|
||||
@ -199,9 +422,7 @@ describe('DropdownContent', () => {
|
||||
onClose={onClose}
|
||||
/>,
|
||||
)
|
||||
|
||||
const widthContainer = container.querySelector('.w-\\[320px\\]')
|
||||
expect(widthContainer).toBeTruthy()
|
||||
expect(container.querySelector('.w-\\[320px\\]')).toBeTruthy()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { ModelProvider } from '../../declarations'
|
||||
import type { CredentialPanelState } from '../use-credential-panel-state'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { CustomConfigurationStatusEnum, PreferredProviderTypeEnum } from '../../declarations'
|
||||
import ModelAuthDropdown from './index'
|
||||
|
||||
@ -16,7 +16,11 @@ vi.mock('../../model-auth/hooks', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
const createProvider = (): ModelProvider => ({
|
||||
vi.mock('../use-trial-credits', () => ({
|
||||
useTrialCredits: () => ({ credits: 0, isExhausted: true, isLoading: false }),
|
||||
}))
|
||||
|
||||
const createProvider = (overrides: Partial<ModelProvider> = {}): ModelProvider => ({
|
||||
provider: 'test',
|
||||
custom_configuration: {
|
||||
status: CustomConfigurationStatusEnum.active,
|
||||
@ -24,6 +28,7 @@ const createProvider = (): ModelProvider => ({
|
||||
},
|
||||
system_configuration: { enabled: true, current_quota_type: 'trial', quota_configurations: [] },
|
||||
preferred_provider_type: PreferredProviderTypeEnum.system,
|
||||
...overrides,
|
||||
} as unknown as ModelProvider)
|
||||
|
||||
const createState = (overrides: Partial<CredentialPanelState> = {}): CredentialPanelState => ({
|
||||
@ -45,9 +50,8 @@ describe('ModelAuthDropdown', () => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
// Button text based on variant
|
||||
describe('Button configuration', () => {
|
||||
it('should show "Add API Key" when no credentials and non-accent variant', () => {
|
||||
describe('Button text', () => {
|
||||
it('should show "Add API Key" when no credentials for credits-active', () => {
|
||||
render(
|
||||
<ModelAuthDropdown
|
||||
provider={createProvider()}
|
||||
@ -56,11 +60,10 @@ describe('ModelAuthDropdown', () => {
|
||||
onChangePriority={onChangePriority}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByRole('button', { name: /addApiKey/ })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show "Configure" when has credentials and non-accent variant', () => {
|
||||
it('should show "Configure" when has credentials for api-active', () => {
|
||||
render(
|
||||
<ModelAuthDropdown
|
||||
provider={createProvider()}
|
||||
@ -69,11 +72,10 @@ describe('ModelAuthDropdown', () => {
|
||||
onChangePriority={onChangePriority}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByRole('button', { name: /config/ })).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: /config/i })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show "Add API Key" for api-required-add variant with accent style', () => {
|
||||
it('should show "Add API Key" for api-required-add variant', () => {
|
||||
render(
|
||||
<ModelAuthDropdown
|
||||
provider={createProvider()}
|
||||
@ -82,12 +84,10 @@ describe('ModelAuthDropdown', () => {
|
||||
onChangePriority={onChangePriority}
|
||||
/>,
|
||||
)
|
||||
|
||||
const button = screen.getByRole('button', { name: /addApiKey/ })
|
||||
expect(button).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: /addApiKey/ })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show "Configure" for api-required-configure variant with accent style', () => {
|
||||
it('should show "Configure" for api-required-configure variant', () => {
|
||||
render(
|
||||
<ModelAuthDropdown
|
||||
provider={createProvider()}
|
||||
@ -96,8 +96,109 @@ describe('ModelAuthDropdown', () => {
|
||||
onChangePriority={onChangePriority}
|
||||
/>,
|
||||
)
|
||||
expect(screen.getByRole('button', { name: /config/i })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
expect(screen.getByRole('button', { name: /config/ })).toBeInTheDocument()
|
||||
it('should show "Configure" for credits-active when has credentials', () => {
|
||||
render(
|
||||
<ModelAuthDropdown
|
||||
provider={createProvider()}
|
||||
state={createState({ hasCredentials: true, variant: 'credits-active' })}
|
||||
isChangingPriority={false}
|
||||
onChangePriority={onChangePriority}
|
||||
/>,
|
||||
)
|
||||
expect(screen.getByRole('button', { name: /config/i })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show "Add API Key" for credits-exhausted (no credentials)', () => {
|
||||
render(
|
||||
<ModelAuthDropdown
|
||||
provider={createProvider()}
|
||||
state={createState({ variant: 'credits-exhausted', hasCredentials: false })}
|
||||
isChangingPriority={false}
|
||||
onChangePriority={onChangePriority}
|
||||
/>,
|
||||
)
|
||||
expect(screen.getByRole('button', { name: /addApiKey/ })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show "Configure" for api-unavailable (has credentials)', () => {
|
||||
render(
|
||||
<ModelAuthDropdown
|
||||
provider={createProvider()}
|
||||
state={createState({ variant: 'api-unavailable', hasCredentials: true })}
|
||||
isChangingPriority={false}
|
||||
onChangePriority={onChangePriority}
|
||||
/>,
|
||||
)
|
||||
expect(screen.getByRole('button', { name: /config/i })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show "Configure" for api-fallback (has credentials)', () => {
|
||||
render(
|
||||
<ModelAuthDropdown
|
||||
provider={createProvider()}
|
||||
state={createState({ variant: 'api-fallback', hasCredentials: true })}
|
||||
isChangingPriority={false}
|
||||
onChangePriority={onChangePriority}
|
||||
/>,
|
||||
)
|
||||
expect(screen.getByRole('button', { name: /config/i })).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Button variant styling', () => {
|
||||
it('should use secondary-accent for api-required-add', () => {
|
||||
const { container } = render(
|
||||
<ModelAuthDropdown
|
||||
provider={createProvider()}
|
||||
state={createState({ variant: 'api-required-add', hasCredentials: false })}
|
||||
isChangingPriority={false}
|
||||
onChangePriority={onChangePriority}
|
||||
/>,
|
||||
)
|
||||
const button = container.querySelector('button')
|
||||
expect(button?.getAttribute('data-variant') ?? button?.className).toMatch(/accent/)
|
||||
})
|
||||
|
||||
it('should use secondary-accent for api-required-configure', () => {
|
||||
const { container } = render(
|
||||
<ModelAuthDropdown
|
||||
provider={createProvider()}
|
||||
state={createState({ variant: 'api-required-configure', hasCredentials: true })}
|
||||
isChangingPriority={false}
|
||||
onChangePriority={onChangePriority}
|
||||
/>,
|
||||
)
|
||||
const button = container.querySelector('button')
|
||||
expect(button?.getAttribute('data-variant') ?? button?.className).toMatch(/accent/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Popover behavior', () => {
|
||||
it('should open popover on button click and show dropdown content', async () => {
|
||||
render(
|
||||
<ModelAuthDropdown
|
||||
provider={createProvider({
|
||||
custom_configuration: {
|
||||
status: CustomConfigurationStatusEnum.active,
|
||||
available_credentials: [{ credential_id: 'c1', credential_name: 'Key 1' }],
|
||||
current_credential_id: 'c1',
|
||||
current_credential_name: 'Key 1',
|
||||
},
|
||||
})}
|
||||
state={createState({ hasCredentials: true, variant: 'api-active' })}
|
||||
isChangingPriority={false}
|
||||
onChangePriority={onChangePriority}
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /config/i }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Key 1')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user