diff --git a/web/app/components/explore/try-app/index.spec.tsx b/web/app/components/explore/try-app/index.spec.tsx index 3ae132b7ed..dc057b4d9f 100644 --- a/web/app/components/explore/try-app/index.spec.tsx +++ b/web/app/components/explore/try-app/index.spec.tsx @@ -16,6 +16,14 @@ vi.mock('react-i18next', () => ({ }), })) +vi.mock('@/config', async (importOriginal) => { + const actual = await importOriginal() as object + return { + ...actual, + IS_CLOUD_EDITION: true, + } +}) + const mockUseGetTryAppInfo = vi.fn() vi.mock('@/service/use-try-app', () => ({ diff --git a/web/app/components/explore/try-app/tab.spec.tsx b/web/app/components/explore/try-app/tab.spec.tsx index 81bb841887..af64a93f43 100644 --- a/web/app/components/explore/try-app/tab.spec.tsx +++ b/web/app/components/explore/try-app/tab.spec.tsx @@ -14,6 +14,14 @@ vi.mock('react-i18next', () => ({ }), })) +vi.mock('@/config', async (importOriginal) => { + const actual = await importOriginal() as object + return { + ...actual, + IS_CLOUD_EDITION: true, + } +}) + describe('Tab', () => { afterEach(() => { cleanup() diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/common-modal.spec.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/common-modal.spec.tsx index 543d3deebc..9155fa15be 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/common-modal.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/common-modal.spec.tsx @@ -1,5 +1,5 @@ import type { TriggerSubscriptionBuilder } from '@/app/components/workflow/block-selector/types' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { act, fireEvent, render, screen, waitFor } from '@testing-library/react' import * as React from 'react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' // Import after mocks @@ -821,6 +821,9 @@ describe('CommonCreateModal', () => { expect(mockCreateBuilder).toHaveBeenCalled() }) + // Flush pending state updates from createBuilder promise resolution + await act(async () => {}) + const input = screen.getByTestId('form-field-webhook_url') fireEvent.change(input, { target: { value: 'https://example.com/webhook' } }) diff --git a/web/app/components/rag-pipeline/components/update-dsl-modal.spec.tsx b/web/app/components/rag-pipeline/components/update-dsl-modal.spec.tsx index b96d3dfb1f..23b92f3e15 100644 --- a/web/app/components/rag-pipeline/components/update-dsl-modal.spec.tsx +++ b/web/app/components/rag-pipeline/components/update-dsl-modal.spec.tsx @@ -140,13 +140,13 @@ class MockFileReader { onload: ((e: { target: { result: string | null } }) => void) | null = null readAsText(_file: File) { - // Simulate async file reading - setTimeout(() => { + // Simulate async file reading using queueMicrotask for more reliable async behavior + queueMicrotask(() => { this.result = 'test file content' if (this.onload) { this.onload({ target: { result: this.result } }) } - }, 0) + }) } } @@ -174,6 +174,7 @@ describe('UpdateDSLModal', () => { status: DSLImportStatus.COMPLETED, pipeline_id: 'test-pipeline-id', }) + mockHandleCheckPluginDependencies.mockResolvedValue(undefined) // Mock FileReader originalFileReader = globalThis.FileReader @@ -472,14 +473,14 @@ describe('UpdateDSLModal', () => { await waitFor(() => { const importButton = screen.getByText('common.overwriteAndImport') expect(importButton).not.toBeDisabled() - }) + }, { timeout: 1000 }) const importButton = screen.getByText('common.overwriteAndImport') fireEvent.click(importButton) await waitFor(() => { expect(mockOnImport).toHaveBeenCalled() - }) + }, { timeout: 1000 }) }) it('should show warning notification on import with warnings', async () => { @@ -664,7 +665,7 @@ describe('UpdateDSLModal', () => { await waitFor(() => { const importButton = screen.getByText('common.overwriteAndImport') expect(importButton).not.toBeDisabled() - }) + }, { timeout: 1000 }) const importButton = screen.getByText('common.overwriteAndImport') fireEvent.click(importButton) @@ -672,7 +673,7 @@ describe('UpdateDSLModal', () => { // Wait for the error modal to be shown after setTimeout await waitFor(() => { expect(screen.getByText('newApp.appCreateDSLErrorTitle')).toBeInTheDocument() - }, { timeout: 500 }) + }, { timeout: 1000 }) }) it('should show version info in error modal', async () => { diff --git a/web/app/components/rag-pipeline/hooks/use-DSL.spec.ts b/web/app/components/rag-pipeline/hooks/use-DSL.spec.ts index 0f235516e0..0d217f3605 100644 --- a/web/app/components/rag-pipeline/hooks/use-DSL.spec.ts +++ b/web/app/components/rag-pipeline/hooks/use-DSL.spec.ts @@ -61,6 +61,12 @@ vi.mock('@/service/use-pipeline', () => ({ }), })) +// Mock download utility +const mockDownloadBlob = vi.fn() +vi.mock('@/utils/download', () => ({ + downloadBlob: (...args: unknown[]) => mockDownloadBlob(...args), +})) + // Mock workflow service const mockFetchWorkflowDraft = vi.fn() vi.mock('@/service/workflow', () => ({ @@ -77,33 +83,9 @@ vi.mock('@/app/components/workflow/constants', () => ({ // ============================================================================ describe('useDSL', () => { - let mockLink: { href: string, download: string, click: ReturnType } - let originalCreateElement: typeof document.createElement - let mockCreateObjectURL: ReturnType - let mockRevokeObjectURL: ReturnType - beforeEach(() => { vi.clearAllMocks() - // Create a proper mock link element - mockLink = { - href: '', - download: '', - click: vi.fn(), - } - - // Save original and mock selectively - only intercept 'a' elements - originalCreateElement = document.createElement.bind(document) - document.createElement = vi.fn((tagName: string) => { - if (tagName === 'a') { - return mockLink as unknown as HTMLElement - } - return originalCreateElement(tagName) - }) as typeof document.createElement - - mockCreateObjectURL = vi.spyOn(URL, 'createObjectURL').mockReturnValue('blob:test-url') - mockRevokeObjectURL = vi.spyOn(URL, 'revokeObjectURL').mockImplementation(() => {}) - // Default store state mockWorkflowStoreGetState.mockReturnValue({ pipelineId: 'test-pipeline-id', @@ -118,9 +100,6 @@ describe('useDSL', () => { }) afterEach(() => { - document.createElement = originalCreateElement - mockCreateObjectURL.mockRestore() - mockRevokeObjectURL.mockRestore() vi.clearAllMocks() }) @@ -187,9 +166,7 @@ describe('useDSL', () => { await result.current.handleExportDSL() }) - expect(document.createElement).toHaveBeenCalledWith('a') - expect(mockCreateObjectURL).toHaveBeenCalled() - expect(mockRevokeObjectURL).toHaveBeenCalledWith('blob:test-url') + expect(mockDownloadBlob).toHaveBeenCalled() }) it('should use correct file extension for download', async () => { @@ -199,17 +176,25 @@ describe('useDSL', () => { await result.current.handleExportDSL() }) - expect(mockLink.download).toBe('Test Knowledge Base.pipeline') + expect(mockDownloadBlob).toHaveBeenCalledWith( + expect.objectContaining({ + fileName: 'Test Knowledge Base.pipeline', + }), + ) }) - it('should trigger download click', async () => { + it('should pass blob data to downloadBlob', async () => { const { result } = renderHook(() => useDSL()) await act(async () => { await result.current.handleExportDSL() }) - expect(mockLink.click).toHaveBeenCalled() + expect(mockDownloadBlob).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.any(Blob), + }), + ) }) it('should show error notification on export failure', async () => { diff --git a/web/app/components/tools/edit-custom-collection-modal/index.spec.tsx b/web/app/components/tools/edit-custom-collection-modal/index.spec.tsx index 848412f0ac..204772a3e2 100644 --- a/web/app/components/tools/edit-custom-collection-modal/index.spec.tsx +++ b/web/app/components/tools/edit-custom-collection-modal/index.spec.tsx @@ -172,6 +172,9 @@ describe('EditCustomCollectionModal', () => { expect(parseParamsSchemaMock).toHaveBeenCalledWith('{}') }) + // Flush pending state updates from parseParamsSchema promise resolution + await act(async () => {}) + await act(async () => { fireEvent.click(screen.getByText('common.operation.save')) }) @@ -184,6 +187,10 @@ describe('EditCustomCollectionModal', () => { credentials: { auth_type: 'none', }, + icon: { + content: '🕵️', + background: '#FEF7C3', + }, labels: [], })) expect(toastNotifySpy).not.toHaveBeenCalled()