mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 10:28:10 +08:00
Merge remote-tracking branch 'origin/main' into feat/support-agent-sandbox
# Conflicts: # api/uv.lock # web/app/components/apps/__tests__/app-card.spec.tsx # web/app/components/apps/__tests__/list.spec.tsx # web/app/components/datasets/create/__tests__/index.spec.tsx # web/app/components/datasets/metadata/metadata-dataset/__tests__/dataset-metadata-drawer.spec.tsx # web/app/components/plugins/readme-panel/__tests__/index.spec.tsx # web/app/components/rag-pipeline/__tests__/index.spec.tsx # web/app/components/rag-pipeline/hooks/__tests__/index.spec.ts # web/eslint-suppressions.json
This commit is contained in:
@ -1,19 +1,26 @@
|
||||
import type { SiteInfo } from '@/models/share'
|
||||
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import InfoModal from './info-modal'
|
||||
import { act, cleanup, fireEvent, render, screen } from '@testing-library/react'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import InfoModal from '../info-modal'
|
||||
|
||||
// Only mock react-i18next for translations
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => key,
|
||||
}),
|
||||
}))
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers({ shouldAdvanceTime: true })
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.runOnlyPendingTimers()
|
||||
vi.useRealTimers()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
async function renderModal(ui: React.ReactElement) {
|
||||
const result = render(ui)
|
||||
await act(async () => {
|
||||
vi.runAllTimers()
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
describe('InfoModal', () => {
|
||||
const mockOnClose = vi.fn()
|
||||
|
||||
@ -29,8 +36,8 @@ describe('InfoModal', () => {
|
||||
})
|
||||
|
||||
describe('rendering', () => {
|
||||
it('should not render when isShow is false', () => {
|
||||
render(
|
||||
it('should not render when isShow is false', async () => {
|
||||
await renderModal(
|
||||
<InfoModal
|
||||
isShow={false}
|
||||
onClose={mockOnClose}
|
||||
@ -41,8 +48,8 @@ describe('InfoModal', () => {
|
||||
expect(screen.queryByText('Test App')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render when isShow is true', () => {
|
||||
render(
|
||||
it('should render when isShow is true', async () => {
|
||||
await renderModal(
|
||||
<InfoModal
|
||||
isShow={true}
|
||||
onClose={mockOnClose}
|
||||
@ -53,8 +60,8 @@ describe('InfoModal', () => {
|
||||
expect(screen.getByText('Test App')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render app title', () => {
|
||||
render(
|
||||
it('should render app title', async () => {
|
||||
await renderModal(
|
||||
<InfoModal
|
||||
isShow={true}
|
||||
onClose={mockOnClose}
|
||||
@ -65,13 +72,13 @@ describe('InfoModal', () => {
|
||||
expect(screen.getByText('Test App')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render copyright when provided', () => {
|
||||
it('should render copyright when provided', async () => {
|
||||
const siteInfoWithCopyright: SiteInfo = {
|
||||
...baseSiteInfo,
|
||||
copyright: 'Dify Inc.',
|
||||
}
|
||||
|
||||
render(
|
||||
await renderModal(
|
||||
<InfoModal
|
||||
isShow={true}
|
||||
onClose={mockOnClose}
|
||||
@ -82,13 +89,13 @@ describe('InfoModal', () => {
|
||||
expect(screen.getByText(/Dify Inc./)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render current year in copyright', () => {
|
||||
it('should render current year in copyright', async () => {
|
||||
const siteInfoWithCopyright: SiteInfo = {
|
||||
...baseSiteInfo,
|
||||
copyright: 'Test Company',
|
||||
}
|
||||
|
||||
render(
|
||||
await renderModal(
|
||||
<InfoModal
|
||||
isShow={true}
|
||||
onClose={mockOnClose}
|
||||
@ -100,13 +107,13 @@ describe('InfoModal', () => {
|
||||
expect(screen.getByText(new RegExp(currentYear))).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render custom disclaimer when provided', () => {
|
||||
it('should render custom disclaimer when provided', async () => {
|
||||
const siteInfoWithDisclaimer: SiteInfo = {
|
||||
...baseSiteInfo,
|
||||
custom_disclaimer: 'This is a custom disclaimer',
|
||||
}
|
||||
|
||||
render(
|
||||
await renderModal(
|
||||
<InfoModal
|
||||
isShow={true}
|
||||
onClose={mockOnClose}
|
||||
@ -117,8 +124,8 @@ describe('InfoModal', () => {
|
||||
expect(screen.getByText('This is a custom disclaimer')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not render copyright section when not provided', () => {
|
||||
render(
|
||||
it('should not render copyright section when not provided', async () => {
|
||||
await renderModal(
|
||||
<InfoModal
|
||||
isShow={true}
|
||||
onClose={mockOnClose}
|
||||
@ -130,8 +137,8 @@ describe('InfoModal', () => {
|
||||
expect(screen.queryByText(new RegExp(`©.*${year}`))).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render with undefined data', () => {
|
||||
render(
|
||||
it('should render with undefined data', async () => {
|
||||
await renderModal(
|
||||
<InfoModal
|
||||
isShow={true}
|
||||
onClose={mockOnClose}
|
||||
@ -139,18 +146,17 @@ describe('InfoModal', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
// Modal should still render but without content
|
||||
expect(screen.queryByText('Test App')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render with image icon type', () => {
|
||||
it('should render with image icon type', async () => {
|
||||
const siteInfoWithImage: SiteInfo = {
|
||||
...baseSiteInfo,
|
||||
icon_type: 'image',
|
||||
icon_url: 'https://example.com/icon.png',
|
||||
}
|
||||
|
||||
render(
|
||||
await renderModal(
|
||||
<InfoModal
|
||||
isShow={true}
|
||||
onClose={mockOnClose}
|
||||
@ -163,8 +169,8 @@ describe('InfoModal', () => {
|
||||
})
|
||||
|
||||
describe('close functionality', () => {
|
||||
it('should call onClose when close button is clicked', () => {
|
||||
render(
|
||||
it('should call onClose when close button is clicked', async () => {
|
||||
await renderModal(
|
||||
<InfoModal
|
||||
isShow={true}
|
||||
onClose={mockOnClose}
|
||||
@ -172,7 +178,6 @@ describe('InfoModal', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
// Find the close icon (RiCloseLine) which has text-text-tertiary class
|
||||
const closeIcon = document.querySelector('[class*="text-text-tertiary"]')
|
||||
expect(closeIcon).toBeInTheDocument()
|
||||
if (closeIcon) {
|
||||
@ -183,14 +188,14 @@ describe('InfoModal', () => {
|
||||
})
|
||||
|
||||
describe('both copyright and disclaimer', () => {
|
||||
it('should render both when both are provided', () => {
|
||||
it('should render both when both are provided', async () => {
|
||||
const siteInfoWithBoth: SiteInfo = {
|
||||
...baseSiteInfo,
|
||||
copyright: 'My Company',
|
||||
custom_disclaimer: 'Disclaimer text here',
|
||||
}
|
||||
|
||||
render(
|
||||
await renderModal(
|
||||
<InfoModal
|
||||
isShow={true}
|
||||
onClose={mockOnClose}
|
||||
@ -1,16 +1,8 @@
|
||||
import type { SiteInfo } from '@/models/share'
|
||||
import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import MenuDropdown from './menu-dropdown'
|
||||
import MenuDropdown from '../menu-dropdown'
|
||||
|
||||
// Mock react-i18next
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => key,
|
||||
}),
|
||||
}))
|
||||
|
||||
// Mock next/navigation
|
||||
const mockReplace = vi.fn()
|
||||
const mockPathname = '/test-path'
|
||||
vi.mock('next/navigation', () => ({
|
||||
@ -20,7 +12,6 @@ vi.mock('next/navigation', () => ({
|
||||
usePathname: () => mockPathname,
|
||||
}))
|
||||
|
||||
// Mock web-app-context
|
||||
const mockShareCode = 'test-share-code'
|
||||
vi.mock('@/context/web-app-context', () => ({
|
||||
useWebAppStore: (selector: (state: Record<string, unknown>) => unknown) => {
|
||||
@ -32,7 +23,6 @@ vi.mock('@/context/web-app-context', () => ({
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock webapp-auth service
|
||||
const mockWebAppLogout = vi.fn().mockResolvedValue(undefined)
|
||||
vi.mock('@/service/webapp-auth', () => ({
|
||||
webAppLogout: (...args: unknown[]) => mockWebAppLogout(...args),
|
||||
@ -57,7 +47,6 @@ describe('MenuDropdown', () => {
|
||||
it('should render the trigger button', () => {
|
||||
render(<MenuDropdown data={baseSiteInfo} />)
|
||||
|
||||
// The trigger button contains a settings icon (RiEqualizer2Line)
|
||||
const triggerButton = screen.getByRole('button')
|
||||
expect(triggerButton).toBeInTheDocument()
|
||||
})
|
||||
@ -65,8 +54,7 @@ describe('MenuDropdown', () => {
|
||||
it('should not show dropdown content initially', () => {
|
||||
render(<MenuDropdown data={baseSiteInfo} />)
|
||||
|
||||
// Dropdown content should not be visible initially
|
||||
expect(screen.queryByText('theme.theme')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('common.theme.theme')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show dropdown content when clicked', async () => {
|
||||
@ -76,7 +64,7 @@ describe('MenuDropdown', () => {
|
||||
fireEvent.click(triggerButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('theme.theme')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.theme.theme')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -87,7 +75,7 @@ describe('MenuDropdown', () => {
|
||||
fireEvent.click(triggerButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('userProfile.about')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.userProfile.about')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -105,7 +93,7 @@ describe('MenuDropdown', () => {
|
||||
fireEvent.click(triggerButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('chat.privacyPolicyMiddle')).toBeInTheDocument()
|
||||
expect(screen.getByText('share.chat.privacyPolicyMiddle')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -116,7 +104,7 @@ describe('MenuDropdown', () => {
|
||||
fireEvent.click(triggerButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('chat.privacyPolicyMiddle')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('share.chat.privacyPolicyMiddle')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -133,7 +121,7 @@ describe('MenuDropdown', () => {
|
||||
fireEvent.click(triggerButton)
|
||||
|
||||
await waitFor(() => {
|
||||
const link = screen.getByText('chat.privacyPolicyMiddle').closest('a')
|
||||
const link = screen.getByText('share.chat.privacyPolicyMiddle').closest('a')
|
||||
expect(link).toHaveAttribute('href', privacyUrl)
|
||||
expect(link).toHaveAttribute('target', '_blank')
|
||||
})
|
||||
@ -148,7 +136,7 @@ describe('MenuDropdown', () => {
|
||||
fireEvent.click(triggerButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('userProfile.logout')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.userProfile.logout')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -159,7 +147,7 @@ describe('MenuDropdown', () => {
|
||||
fireEvent.click(triggerButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('userProfile.logout')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('common.userProfile.logout')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -170,10 +158,10 @@ describe('MenuDropdown', () => {
|
||||
fireEvent.click(triggerButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('userProfile.logout')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.userProfile.logout')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
const logoutButton = screen.getByText('userProfile.logout')
|
||||
const logoutButton = screen.getByText('common.userProfile.logout')
|
||||
await act(async () => {
|
||||
fireEvent.click(logoutButton)
|
||||
})
|
||||
@ -193,10 +181,10 @@ describe('MenuDropdown', () => {
|
||||
fireEvent.click(triggerButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('userProfile.about')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.userProfile.about')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
const aboutButton = screen.getByText('userProfile.about')
|
||||
const aboutButton = screen.getByText('common.userProfile.about')
|
||||
fireEvent.click(aboutButton)
|
||||
|
||||
await waitFor(() => {
|
||||
@ -213,13 +201,13 @@ describe('MenuDropdown', () => {
|
||||
fireEvent.click(triggerButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('theme.theme')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.theme.theme')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
rerender(<MenuDropdown data={baseSiteInfo} forceClose={true} />)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('theme.theme')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('common.theme.theme')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -239,16 +227,14 @@ describe('MenuDropdown', () => {
|
||||
|
||||
const triggerButton = screen.getByRole('button')
|
||||
|
||||
// Open
|
||||
fireEvent.click(triggerButton)
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('theme.theme')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.theme.theme')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Close
|
||||
fireEvent.click(triggerButton)
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('theme.theme')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('common.theme.theme')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1,6 +1,6 @@
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import NoData from './index'
|
||||
import NoData from '../index'
|
||||
|
||||
describe('NoData', () => {
|
||||
beforeEach(() => {
|
||||
@ -2,7 +2,7 @@ import type { Mock } from 'vitest'
|
||||
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import RunBatch from './index'
|
||||
import RunBatch from '../index'
|
||||
|
||||
vi.mock('@/hooks/use-breakpoints', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/hooks/use-breakpoints')>()
|
||||
@ -15,14 +15,14 @@ vi.mock('@/hooks/use-breakpoints', async (importOriginal) => {
|
||||
let latestOnParsed: ((data: string[][]) => void) | undefined
|
||||
let receivedCSVDownloadProps: Record<string, unknown> | undefined
|
||||
|
||||
vi.mock('./csv-reader', () => ({
|
||||
vi.mock('../csv-reader', () => ({
|
||||
default: (props: { onParsed: (data: string[][]) => void }) => {
|
||||
latestOnParsed = props.onParsed
|
||||
return <div data-testid="csv-reader" />
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('./csv-download', () => ({
|
||||
vi.mock('../csv-download', () => ({
|
||||
default: (props: { vars: { name: string }[] }) => {
|
||||
receivedCSVDownloadProps = props
|
||||
return <div data-testid="csv-download" />
|
||||
@ -1,6 +1,6 @@
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import CSVDownload from './index'
|
||||
import CSVDownload from '../index'
|
||||
|
||||
const mockType = { Link: 'mock-link' }
|
||||
let capturedProps: Record<string, unknown> | undefined
|
||||
@ -1,13 +1,20 @@
|
||||
import { act, render, screen, waitFor } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import CSVReader from './index'
|
||||
import CSVReader from '../index'
|
||||
|
||||
let mockAcceptedFile: { name: string } | null = null
|
||||
let capturedHandlers: Record<string, (payload: any) => void> = {}
|
||||
|
||||
type CSVReaderHandlers = {
|
||||
onUploadAccepted?: (payload: { data: string[][] }) => void
|
||||
onDragOver?: (event: DragEvent) => void
|
||||
onDragLeave?: (event: DragEvent) => void
|
||||
}
|
||||
|
||||
let capturedHandlers: CSVReaderHandlers = {}
|
||||
|
||||
vi.mock('react-papaparse', () => ({
|
||||
useCSVReader: () => ({
|
||||
CSVReader: ({ children, ...handlers }: any) => {
|
||||
CSVReader: ({ children, ...handlers }: { children: (ctx: { getRootProps: () => Record<string, string>, acceptedFile: { name: string } | null }) => React.ReactNode } & CSVReaderHandlers) => {
|
||||
capturedHandlers = handlers
|
||||
return (
|
||||
<div data-testid="csv-reader-wrapper">
|
||||
@ -1,6 +1,6 @@
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import ResDownload from './index'
|
||||
import ResDownload from '../index'
|
||||
|
||||
const mockType = { Link: 'mock-link' }
|
||||
let capturedProps: Record<string, unknown> | undefined
|
||||
@ -1,4 +1,4 @@
|
||||
import type { InputValueTypes } from '../types'
|
||||
import type { InputValueTypes } from '../../types'
|
||||
import type { PromptConfig, PromptVariable } from '@/models/debug'
|
||||
import type { SiteInfo } from '@/models/share'
|
||||
import type { VisionFile, VisionSettings } from '@/types/app'
|
||||
@ -6,7 +6,7 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { Resolution, TransferMethod } from '@/types/app'
|
||||
import RunOnce from './index'
|
||||
import RunOnce from '../index'
|
||||
|
||||
vi.mock('@/hooks/use-breakpoints', () => {
|
||||
const MediaType = {
|
||||
@ -39,7 +39,6 @@ vi.mock('@/app/components/base/image-uploader/text-generation-image-uploader', (
|
||||
}
|
||||
})
|
||||
|
||||
// Mock FileUploaderInAttachmentWrapper as it requires context providers not available in tests
|
||||
vi.mock('@/app/components/base/file-uploader', () => ({
|
||||
FileUploaderInAttachmentWrapper: ({ value, onChange }: { value: object[], onChange: (files: object[]) => void }) => (
|
||||
<div data-testid="file-uploader-mock">
|
||||
@ -272,7 +271,6 @@ describe('RunOnce', () => {
|
||||
selectInput: 'Option A',
|
||||
})
|
||||
})
|
||||
// The Select component should be rendered
|
||||
expect(screen.getByText('Select Input')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@ -463,7 +461,6 @@ describe('RunOnce', () => {
|
||||
key: 'textInput',
|
||||
name: 'Text Input',
|
||||
type: 'string',
|
||||
// max_length is not set
|
||||
}),
|
||||
],
|
||||
}
|
||||
Reference in New Issue
Block a user