test: improve unit tests for plugin authentication components

- Updated tests for the transformFormSchemasSecretInput function to clarify handling of null and numeric values.
- Added a data-testid to the AddOAuthButton for more reliable selection in tests.
- Refactored tests for AddOAuthButton to use the new data-testid for clicking the settings button.
- Enhanced assertions in ApiKeyModal and OAuthClientSettings tests to ensure proper API call handling during validation and state updates.
This commit is contained in:
CodingOnStar 2025-12-25 17:52:45 +08:00
parent 289d752565
commit 38e5955760
3 changed files with 32 additions and 43 deletions

View File

@ -225,6 +225,7 @@ const AddOAuthButton = ({
>
</div>
<div
data-testid="oauth-settings-button"
className={cn(
'flex h-full w-8 shrink-0 items-center justify-center rounded-r-lg hover:bg-components-button-primary-bg-hover',
buttonRightClassName,

View File

@ -168,6 +168,7 @@ describe('AddApiKeyButton', () => {
render(<AddApiKeyButton pluginPayload={pluginPayload} />, { wrapper: createWrapper() })
// Verify the default button has secondary-accent variant class
expect(screen.getByRole('button').className).toContain('btn-secondary-accent')
})
})
@ -613,18 +614,9 @@ describe('AddOAuthButton', () => {
render(<AddOAuthButton pluginPayload={pluginPayload} />, { wrapper: createWrapper() })
// Find and click the settings icon (right side of split button)
const settingsIcon = screen.getByRole('button').querySelector('[class*="shrink-0"][class*="w-8"]')
if (settingsIcon) {
fireEvent.click(settingsIcon)
}
else {
// Alternative: click by finding the RiEqualizer2Line icon's parent
const icons = screen.getByRole('button').querySelectorAll('svg')
const settingsButton = icons[icons.length - 1]?.parentElement
if (settingsButton)
fireEvent.click(settingsButton)
}
// Click the settings icon using data-testid for reliable selection
const settingsButton = screen.getByTestId('oauth-settings-button')
fireEvent.click(settingsButton)
await waitFor(() => {
expect(screen.getByText('plugin.auth.oauthClientSettings')).toBeInTheDocument()
@ -1138,8 +1130,7 @@ describe('ApiKeyModal', () => {
const confirmButton = screen.getByText('common.operation.save')
fireEvent.click(confirmButton)
// Wait a bit and verify API was not called
await new Promise(resolve => setTimeout(resolve, 100))
// Verify API was not called since validation failed synchronously
expect(mockAddPluginCredential).not.toHaveBeenCalled()
})
@ -1174,7 +1165,7 @@ describe('ApiKeyModal', () => {
])
// Create a promise that we can control
let resolveFirstCall: (value?: unknown) => void
let resolveFirstCall: (value?: unknown) => void = () => {}
let apiCallCount = 0
mockAddPluginCredential.mockImplementation(() => {
@ -1199,20 +1190,19 @@ describe('ApiKeyModal', () => {
// First click starts the request
fireEvent.click(confirmButton)
// Wait a tick to ensure state updates
await new Promise(resolve => setTimeout(resolve, 10))
// Wait for the first API call to be made
await waitFor(() => {
expect(apiCallCount).toBe(1)
})
// Second click while first request is still pending
// Second click while first request is still pending should be ignored
fireEvent.click(confirmButton)
// Wait a bit more
await new Promise(resolve => setTimeout(resolve, 10))
// Only one API call should have been made
// Verify only one API call was made (no additional calls)
expect(apiCallCount).toBe(1)
// Clean up by resolving the promise
resolveFirstCall!()
resolveFirstCall()
})
it('should call onRemove when extra button is clicked in edit mode', async () => {
@ -1714,7 +1704,7 @@ describe('OAuthClientSettings', () => {
it('should return early from handleConfirm if doingActionRef is true', async () => {
const pluginPayload = createPluginPayload()
let resolveFirstCall: (value?: unknown) => void
let resolveFirstCall: (value?: unknown) => void = () => {}
let apiCallCount = 0
mockSetPluginOAuthCustomClient.mockImplementation(() => {
@ -1740,25 +1730,24 @@ describe('OAuthClientSettings', () => {
// First click starts the request
fireEvent.click(saveButton)
// Wait a tick to ensure state updates
await new Promise(resolve => setTimeout(resolve, 10))
// Wait for the first API call to be made
await waitFor(() => {
expect(apiCallCount).toBe(1)
})
// Second click while first request is pending
// Second click while first request is pending should be ignored
fireEvent.click(saveButton)
// Wait a bit more
await new Promise(resolve => setTimeout(resolve, 10))
// Only one API call should have been made
// Verify only one API call was made (no additional calls)
expect(apiCallCount).toBe(1)
// Clean up
resolveFirstCall!()
resolveFirstCall()
})
it('should return early from handleRemove if doingActionRef is true', async () => {
const pluginPayload = createPluginPayload()
let resolveFirstCall: (value?: unknown) => void
let resolveFirstCall: (value?: unknown) => void = () => {}
let deleteCallCount = 0
mockDeletePluginOAuthCustomClient.mockImplementation(() => {
@ -1801,20 +1790,19 @@ describe('OAuthClientSettings', () => {
// First click starts the delete request
fireEvent.click(removeButton)
// Wait a tick to ensure state updates
await new Promise(resolve => setTimeout(resolve, 10))
// Wait for the first delete call to be made
await waitFor(() => {
expect(deleteCallCount).toBe(1)
})
// Second click while first request is pending
// Second click while first request is pending should be ignored
fireEvent.click(removeButton)
// Wait a bit more
await new Promise(resolve => setTimeout(resolve, 10))
// Only one delete call should have been made
// Verify only one delete call was made (no additional calls)
expect(deleteCallCount).toBe(1)
// Clean up
resolveFirstCall!()
resolveFirstCall()
})
})

View File

@ -359,9 +359,9 @@ describe('Utils', () => {
const result = transformFormSchemasSecretInput(secretNames, values as Record<string, unknown>)
// null is falsy, so it won't be transformed
// null is preserved as-is to represent an explicitly unset secret, not masked as [__HIDDEN__]
expect(result.api_key).toBe(null)
// 0 is falsy, so it won't be transformed
// numeric values like 0 are also preserved; only non-empty string secrets are transformed
expect(result.null_key).toBe(0)
})
})