From e823e09afb3fdf7409e9e9dc0caf5b3ca9f430cc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 13:50:03 +0000 Subject: [PATCH] test: stabilize remaining async leak specs Co-authored-by: hyoban <38493346+hyoban@users.noreply.github.com> --- .../app-icon-picker/__tests__/index.spec.tsx | 78 ++++++++++++++++++- .../__tests__/authorized-in-node.spec.tsx | 45 +++++++++++ .../plugin-auth/__tests__/index.spec.tsx | 12 +++ .../__tests__/plugin-auth-in-agent.spec.tsx | 46 ++++++++++- 4 files changed, 177 insertions(+), 4 deletions(-) diff --git a/web/app/components/base/app-icon-picker/__tests__/index.spec.tsx b/web/app/components/base/app-icon-picker/__tests__/index.spec.tsx index 8334512047..c78934811a 100644 --- a/web/app/components/base/app-icon-picker/__tests__/index.spec.tsx +++ b/web/app/components/base/app-icon-picker/__tests__/index.spec.tsx @@ -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 ( +
+ + +
+ ) + }, +})) + +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(null) + const [animatedUrl, setAnimatedUrl] = React.useState(null) + const [showCropper, setShowCropper] = React.useState(false) + + return ( +
+
drop image here
+ { + 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 && ( +
+ +
+ )} + {animatedUrl && animated preview} +
+ ) + }, +})) + 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 }) => ( +
+ {children} +
+ ), +})) + 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() }) diff --git a/web/app/components/plugins/plugin-auth/__tests__/authorized-in-node.spec.tsx b/web/app/components/plugins/plugin-auth/__tests__/authorized-in-node.spec.tsx index 91ffbaa24a..e3cd8224cf 100644 --- a/web/app/components/plugins/plugin-auth/__tests__/authorized-in-node.spec.tsx +++ b/web/app/components/plugins/plugin-auth/__tests__/authorized-in-node.spec.tsx @@ -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 + }) => ( +
+
onOpenChange?.(!isOpen)}> + {renderTrigger?.(isOpen)} +
+ {isOpen && ( +
+ {extraAuthorizationItems.map(item => ( + + ))} + {credentials.map(item => ( + + ))} +
+ )} +
+ ), + usePluginAuth: () => { + const credentialInfo = mockGetPluginCredentialInfo() ?? { credentials: [] } + return { + canApiKey: true, + canOAuth: false, + credentials: credentialInfo.credentials ?? [], + disabled: false, + invalidPluginCredentialInfo: vi.fn(), + notAllowCustomCredential: false, + } + }, +})) + // ==================== Test Utilities ==================== const createTestQueryClient = () => diff --git a/web/app/components/plugins/plugin-auth/__tests__/index.spec.tsx b/web/app/components/plugins/plugin-auth/__tests__/index.spec.tsx index d259b27c30..e71fabc09d 100644 --- a/web/app/components/plugins/plugin-auth/__tests__/index.spec.tsx +++ b/web/app/components/plugins/plugin-auth/__tests__/index.spec.tsx @@ -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') diff --git a/web/app/components/plugins/plugin-auth/__tests__/plugin-auth-in-agent.spec.tsx b/web/app/components/plugins/plugin-auth/__tests__/plugin-auth-in-agent.spec.tsx index 901c3ab49a..316972b006 100644 --- a/web/app/components/plugins/plugin-auth/__tests__/plugin-auth-in-agent.spec.tsx +++ b/web/app/components/plugins/plugin-auth/__tests__/plugin-auth-in-agent.spec.tsx @@ -56,6 +56,48 @@ vi.mock('@/service/use-triggers', () => ({ useInvalidTriggerDynamicOptions: () => vi.fn(), })) +vi.mock('../authorize', () => ({ + default: () => ( + + ), +})) + +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 + }) => ( +
+
onOpenChange?.(!isOpen)}> + {renderTrigger?.(isOpen)} +
+ {isOpen && ( +
+ {extraAuthorizationItems.map(item => ( + + ))} + {credentials.map(item => ( + + ))} +
+ )} +
+ ), +})) + // ==================== Test Utilities ==================== const createTestQueryClient = () => @@ -120,7 +162,7 @@ describe('PluginAuthInAgent Component', () => { , { 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', () => { , { wrapper: createWrapper() }, ) - expect(screen.getByRole('button')).toBeInTheDocument() + expect(screen.getByRole('button', { name: /plugin\.auth\.workspaceDefault/i })).toBeInTheDocument() expect(screen.getByText('plugin.auth.workspaceDefault')).toBeInTheDocument() })