Merge remote-tracking branch 'origin/main' into feat/model-plugins-implementing

This commit is contained in:
yyh
2026-03-09 23:42:04 +08:00
14 changed files with 5865 additions and 2616 deletions

View File

@ -172,12 +172,8 @@ describe('dataset-config/card-item', () => {
const [editButton] = within(card).getAllByRole('button', { hidden: true })
await user.click(editButton)
expect(screen.getByText('Mock settings modal')).toBeInTheDocument()
await waitFor(() => {
expect(screen.getByRole('dialog')).toBeVisible()
})
fireEvent.click(screen.getByText('Save changes'))
expect(await screen.findByText('Mock settings modal')).toBeInTheDocument()
fireEvent.click(await screen.findByText('Save changes'))
await waitFor(() => {
expect(onSave).toHaveBeenCalledWith(expect.objectContaining({ name: 'Updated dataset' }))
@ -194,7 +190,7 @@ describe('dataset-config/card-item', () => {
const card = screen.getByText(dataset.name).closest('.group') as HTMLElement
const buttons = within(card).getAllByRole('button', { hidden: true })
const deleteButton = buttons[buttons.length - 1]
const deleteButton = buttons.at(-1)!
expect(deleteButton.className).not.toContain('action-btn-destructive')
@ -233,7 +229,7 @@ describe('dataset-config/card-item', () => {
await user.click(editButton)
expect(screen.getByText('Mock settings modal')).toBeInTheDocument()
const overlay = Array.from(document.querySelectorAll('[class]'))
const overlay = [...document.querySelectorAll('[class]')]
.find(element => element.className.toString().includes('bg-black/30'))
expect(overlay).toBeInTheDocument()

View File

@ -164,7 +164,7 @@ const VoiceParamConfig = ({
</div>
<div className="flex items-center gap-1">
<Listbox
value={voiceItem ?? {}}
value={voiceItem}
disabled={!languageItem}
onChange={(value: Item) => {
handleChange({

View File

@ -1,55 +1,72 @@
import type { FormType } from '../../..'
import type { CustomActionsProps } from '../actions'
import { fireEvent, render, screen } from '@testing-library/react'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { formContext } from '../../..'
import Actions from '../actions'
const renderWithForm = ({
canSubmit,
isSubmitting,
CustomActions,
}: {
canSubmit: boolean
isSubmitting: boolean
const mockFormState = vi.hoisted(() => ({
canSubmit: true,
isSubmitting: false,
}))
vi.mock('@tanstack/react-form', async () => {
const actual = await vi.importActual<typeof import('@tanstack/react-form')>('@tanstack/react-form')
return {
...actual,
useStore: (_store: unknown, selector: (state: typeof mockFormState) => unknown) => selector(mockFormState),
}
})
type RenderWithFormOptions = {
canSubmit?: boolean
isSubmitting?: boolean
CustomActions?: (props: CustomActionsProps) => React.ReactNode
}) => {
const submitSpy = vi.fn()
const state = {
canSubmit,
isSubmitting,
}
onSubmit?: () => void
}
const renderWithForm = ({
canSubmit = true,
isSubmitting = false,
CustomActions,
onSubmit = vi.fn(),
}: RenderWithFormOptions = {}) => {
mockFormState.canSubmit = canSubmit
mockFormState.isSubmitting = isSubmitting
const form = {
store: {
state,
subscribe: () => () => {},
},
handleSubmit: submitSpy,
store: {},
handleSubmit: onSubmit,
}
const TestComponent = () => {
return (
<formContext.Provider value={form as unknown as FormType}>
<Actions
CustomActions={CustomActions}
/>
</formContext.Provider>
)
}
render(
<formContext.Provider value={form as unknown as FormType}>
<Actions CustomActions={CustomActions} />
</formContext.Provider>,
)
render(<TestComponent />)
return { submitSpy }
return { onSubmit }
}
describe('Actions', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('should disable submit button when form cannot submit', () => {
renderWithForm({ canSubmit: false, isSubmitting: false })
renderWithForm({ canSubmit: false })
expect(screen.getByRole('button', { name: 'common.operation.submit' })).toBeDisabled()
})
it('should call form submit when users click submit button', () => {
const { submitSpy } = renderWithForm({ canSubmit: true, isSubmitting: false })
it('should call form submit when users click submit button', async () => {
const submitSpy = vi.fn()
renderWithForm({ onSubmit: submitSpy })
fireEvent.click(screen.getByRole('button', { name: 'common.operation.submit' }))
expect(submitSpy).toHaveBeenCalledTimes(1)
await waitFor(() => {
expect(submitSpy).toHaveBeenCalledTimes(1)
})
})
it('should render custom actions when provided', () => {
@ -60,15 +77,14 @@ describe('Actions', () => {
))
renderWithForm({
canSubmit: true,
isSubmitting: true,
CustomActions: customActionsSpy,
})
expect(screen.queryByRole('button', { name: 'common.operation.submit' })).not.toBeInTheDocument()
expect(screen.getByText('custom-true-true')).toBeInTheDocument()
expect(screen.getByText('custom-false-true')).toBeInTheDocument()
expect(customActionsSpy).toHaveBeenCalledWith(expect.objectContaining({
isSubmitting: true,
form: expect.any(Object),
isSubmitting: false,
canSubmit: true,
}))
})

View File

@ -100,11 +100,11 @@ const Select: FC<ISelectProps> = ({
disabled={disabled}
value={selectedItem}
className={className}
onChange={(value: Item) => {
onChange={(value) => {
if (!disabled) {
setSelectedItem(value)
setOpen(false)
onSelect(value)
onSelect(value as Item)
}
}}
>
@ -224,10 +224,10 @@ const SimpleSelect: FC<ISelectProps> = ({
<Listbox
ref={listboxRef}
value={selectedItem}
onChange={(value: Item) => {
onChange={(value) => {
if (!disabled) {
setSelectedItem(value)
onSelect(value)
onSelect(value as Item)
}
}}
>

View File

@ -1,4 +1,5 @@
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
import { act, render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import SVGRenderer from '..'
const mockClick = vi.fn()
@ -117,6 +118,7 @@ describe('SVGRenderer', () => {
})
it('closes image preview on cancel', async () => {
const user = userEvent.setup()
render(<SVGRenderer content={validSvg} />)
await waitFor(() => {
@ -129,9 +131,11 @@ describe('SVGRenderer', () => {
expect(screen.getByAltText('Preview')).toBeInTheDocument()
fireEvent.keyDown(document, { key: 'Escape' })
await user.click(screen.getByTestId('image-preview-close-button'))
expect(screen.queryByAltText('Preview')).not.toBeInTheDocument()
await waitFor(() => {
expect(screen.queryByAltText('Preview')).not.toBeInTheDocument()
})
})
})
})

View File

@ -1,4 +1,5 @@
import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import Operations from '../operations'
@ -355,16 +356,14 @@ describe('Operations', () => {
})
it('should show rename modal when rename is clicked', async () => {
const user = userEvent.setup()
render(<Operations {...defaultProps} />)
await openPopover()
const renameButton = screen.getByText('datasetDocuments.list.table.rename')
await act(async () => {
fireEvent.click(renameButton)
})
// Rename modal should be shown
await waitFor(() => {
expect(screen.getByDisplayValue('Test Document')).toBeInTheDocument()
})
const renameAction = screen.getByText('datasetDocuments.list.table.rename').parentElement as HTMLElement
await user.click(renameAction)
const renameInput = await screen.findByRole('textbox')
expect(renameInput).toHaveValue('Test Document')
})
it('should call sync for notion data source', async () => {

View File

@ -5,6 +5,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { ToastContext } from '@/app/components/base/toast/context'
import { ProcessMode } from '@/models/datasets'
import * as datasetsService from '@/service/datasets'
import * as useDataset from '@/service/knowledge/use-dataset'
@ -13,8 +14,22 @@ import { IndexingType } from '../../../../create/step-two'
import { DocumentContext } from '../../context'
import EmbeddingDetail from '../index'
const { mockNotify, mockClose } = vi.hoisted(() => ({
mockNotify: vi.fn(),
mockClose: vi.fn(),
}))
vi.mock('@/service/datasets')
vi.mock('@/service/knowledge/use-dataset')
vi.mock('@/app/components/base/toast/context', async () => {
const { createContext } = await vi.importActual<typeof import('use-context-selector')>('use-context-selector')
return {
ToastContext: createContext({
notify: mockNotify,
close: mockClose,
}),
}
})
const mockFetchIndexingStatus = vi.mocked(datasetsService.fetchIndexingStatus)
const mockPauseDocIndexing = vi.mocked(datasetsService.pauseDocIndexing)
@ -32,9 +47,11 @@ const createWrapper = (contextValue: DocumentContextValue = { datasetId: 'ds1',
const queryClient = createTestQueryClient()
return ({ children }: { children: ReactNode }) => (
<QueryClientProvider client={queryClient}>
<DocumentContext.Provider value={contextValue}>
{children}
</DocumentContext.Provider>
<ToastContext.Provider value={{ notify: mockNotify, close: vi.fn() }}>
<DocumentContext.Provider value={contextValue}>
{children}
</DocumentContext.Provider>
</ToastContext.Provider>
</QueryClientProvider>
)
}