test: stabilize remaining async leak specs

Co-authored-by: hyoban <38493346+hyoban@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-03-18 13:50:03 +00:00
parent 039b448b90
commit e823e09afb
4 changed files with 177 additions and 4 deletions

View File

@ -2,6 +2,7 @@ import type { Area } from 'react-easy-crop'
import type { ImageFile } from '@/types/app'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import * as React from 'react'
import { TransferMethod } from '@/types/app'
import AppIconPicker from '../index'
import 'vitest-canvas-mock'
@ -93,6 +94,71 @@ vi.mock('react-easy-crop', () => ({
),
}))
vi.mock('@/app/components/base/emoji-picker/Inner', () => ({
default: function MockEmojiPickerInner({ onSelect, className }: { onSelect: (emoji: string, background: string) => void, className?: string }) {
return (
<div className={className}>
<input placeholder="search" />
<button
type="button"
data-testid="emoji-container-grinning"
onClick={() => onSelect('😀', '#FFEAD5')}
>
grinning
</button>
</div>
)
},
}))
vi.mock('../ImageInput', () => ({
default: function MockImageInput({ onImageInput, className }: {
onImageInput: (isCropped: boolean, fileOrTempUrl: string | File, croppedAreaPixels?: Area, fileName?: string) => void
className?: string
}) {
const [selectedFile, setSelectedFile] = React.useState<File | null>(null)
const [animatedUrl, setAnimatedUrl] = React.useState<string | null>(null)
const [showCropper, setShowCropper] = React.useState(false)
return (
<div className={className}>
<div>drop image here</div>
<input
data-testid="image-input"
type="file"
onChange={(event) => {
const file = event.target.files?.[0]
if (!file)
return
setSelectedFile(file)
if (file.type === 'image/gif') {
const nextUrl = URL.createObjectURL(file)
setAnimatedUrl(nextUrl)
setShowCropper(false)
onImageInput(false, file)
return
}
setAnimatedUrl(null)
setShowCropper(true)
}}
/>
{showCropper && selectedFile && (
<div data-testid="mock-cropper">
<button
type="button"
data-testid="trigger-crop"
onClick={() => onImageInput(true, 'blob:crop-temp-url', { x: 0, y: 0, width: 100, height: 100 }, selectedFile.name)}
>
Trigger Crop
</button>
</div>
)}
{animatedUrl && <img data-testid="animated-image" src={animatedUrl} alt="animated preview" />}
</div>
)
},
}))
vi.mock('../../image-uploader/hooks', () => ({
useLocalFileUploader: (options: LocalFileUploaderOptions) => {
mocks.onUpload = options.onUpload
@ -104,6 +170,14 @@ vi.mock('@/utils/emoji', () => ({
searchEmoji: vi.fn().mockResolvedValue(['grinning', 'sunglasses']),
}))
vi.mock('@/app/components/base/modal', () => ({
default: ({ children, className }: { children: React.ReactNode, className?: string }) => (
<div data-testid="mock-modal" className={className}>
{children}
</div>
),
}))
describe('AppIconPicker', () => {
const originalCreateElement = document.createElement.bind(document)
const originalCreateObjectURL = globalThis.URL.createObjectURL
@ -183,10 +257,10 @@ describe('AppIconPicker', () => {
it('should switch between emoji and image tabs', async () => {
renderPicker()
await userEvent.click(screen.getByText(/image/i))
fireEvent.click(screen.getByText(/image/i))
expect(screen.getByText(/drop.*here/i)).toBeInTheDocument()
await userEvent.click(screen.getByText(/emoji/i))
fireEvent.click(screen.getByText(/emoji/i))
expect(screen.getByPlaceholderText(/search/i)).toBeInTheDocument()
})

View File

@ -56,6 +56,51 @@ vi.mock('@/service/use-triggers', () => ({
useInvalidTriggerDynamicOptions: () => vi.fn(),
}))
vi.mock('../index', () => ({
Authorized: ({
credentials = [],
extraAuthorizationItems = [],
isOpen,
onItemClick,
onOpenChange,
renderTrigger,
}: {
credentials?: Credential[]
extraAuthorizationItems?: Credential[]
isOpen?: boolean
onItemClick?: (id: string) => void
onOpenChange?: (open: boolean) => void
renderTrigger?: (isOpen?: boolean) => ReactNode
}) => (
<div>
<div data-testid="authorized-trigger" onClick={() => onOpenChange?.(!isOpen)}>
{renderTrigger?.(isOpen)}
</div>
{isOpen && (
<div>
{extraAuthorizationItems.map(item => (
<button key={item.id} type="button" onClick={() => onItemClick?.(item.id)}>{item.name}</button>
))}
{credentials.map(item => (
<button key={item.id} type="button" onClick={() => onItemClick?.(item.id)}>{item.name}</button>
))}
</div>
)}
</div>
),
usePluginAuth: () => {
const credentialInfo = mockGetPluginCredentialInfo() ?? { credentials: [] }
return {
canApiKey: true,
canOAuth: false,
credentials: credentialInfo.credentials ?? [],
disabled: false,
invalidPluginCredentialInfo: vi.fn(),
notAllowCustomCredential: false,
}
},
}))
// ==================== Test Utilities ====================
const createTestQueryClient = () =>

View File

@ -1,6 +1,18 @@
import { describe, expect, it } from 'vitest'
import { AuthCategory, CredentialTypeEnum } from '../types'
vi.mock('../authorize/add-api-key-button', () => ({ default: () => null }))
vi.mock('../authorize/add-oauth-button', () => ({ default: () => null }))
vi.mock('../authorize/api-key-modal', () => ({ default: () => null }))
vi.mock('../authorized', () => ({ default: () => null }))
vi.mock('../authorized-in-data-source-node', () => ({ default: () => null }))
vi.mock('../authorized-in-node', () => ({ default: () => null }))
vi.mock('../plugin-auth', () => ({ default: () => null }))
vi.mock('../plugin-auth-in-agent', () => ({ default: () => null }))
vi.mock('../plugin-auth-in-datasource-node', () => ({ default: () => null }))
vi.mock('../hooks/use-plugin-auth', () => ({ usePluginAuth: () => ({}) }))
vi.mock('../hooks/use-plugin-auth-action', () => ({}))
describe('plugin-auth index exports', () => {
it('should export all required components and hooks', async () => {
const exports = await import('../index')

View File

@ -56,6 +56,48 @@ vi.mock('@/service/use-triggers', () => ({
useInvalidTriggerDynamicOptions: () => vi.fn(),
}))
vi.mock('../authorize', () => ({
default: () => (
<button type="button">
plugin.auth.useApiAuth
</button>
),
}))
vi.mock('../authorized', () => ({
default: ({
credentials = [],
extraAuthorizationItems = [],
isOpen,
onItemClick,
onOpenChange,
renderTrigger,
}: {
credentials?: Credential[]
extraAuthorizationItems?: Credential[]
isOpen?: boolean
onItemClick?: (id: string) => void
onOpenChange?: (open: boolean) => void
renderTrigger?: (isOpen?: boolean) => ReactNode
}) => (
<div>
<div data-testid="authorized-trigger" onClick={() => onOpenChange?.(!isOpen)}>
{renderTrigger?.(isOpen)}
</div>
{isOpen && (
<div>
{extraAuthorizationItems.map(item => (
<button key={item.id} type="button" onClick={() => onItemClick?.(item.isWorkspaceDefault ? '' : item.id)}>{item.name}</button>
))}
{credentials.map(item => (
<button key={item.id} type="button" onClick={() => onItemClick?.(item.id)}>{item.name}</button>
))}
</div>
)}
</div>
),
}))
// ==================== Test Utilities ====================
const createTestQueryClient = () =>
@ -120,7 +162,7 @@ describe('PluginAuthInAgent Component', () => {
<PluginAuthInAgent pluginPayload={pluginPayload} />,
{ wrapper: createWrapper() },
)
expect(screen.getByRole('button')).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'plugin.auth.useApiAuth' })).toBeInTheDocument()
})
it('should render Authorized with workspace default when authorized', async () => {
@ -130,7 +172,7 @@ describe('PluginAuthInAgent Component', () => {
<PluginAuthInAgent pluginPayload={pluginPayload} />,
{ wrapper: createWrapper() },
)
expect(screen.getByRole('button')).toBeInTheDocument()
expect(screen.getByRole('button', { name: /plugin\.auth\.workspaceDefault/i })).toBeInTheDocument()
expect(screen.getByText('plugin.auth.workspaceDefault')).toBeInTheDocument()
})