dify/web/app/components/snippets/__tests__/import-snippet-dsl-dialog.spec.tsx
yyh 6736f4005b
fix(web): improve import DSL dialog states (#39432)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-07-23 02:26:59 +00:00

161 lines
5.3 KiB
TypeScript

import { screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { render } from '@/test/console/render'
import ImportSnippetDSLDialog from '../import-snippet-dsl-dialog'
const serviceMocks = vi.hoisted(() => ({
importMutateAsync: vi.fn(),
confirmMutateAsync: vi.fn(),
}))
const routerMocks = vi.hoisted(() => ({
push: vi.fn(),
}))
const toastMocks = vi.hoisted(() => ({
success: vi.fn(),
error: vi.fn(),
}))
const contextMocks = vi.hoisted(() => ({
workspacePermissionKeys: ['snippets.create_and_modify'] as string[],
}))
vi.mock('@/next/navigation', () => ({
useRouter: () => routerMocks,
}))
vi.mock('@/context/permission-state', async () => {
const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture')
return createPermissionStateModuleMock(() => ({
workspacePermissionKeys: contextMocks.workspacePermissionKeys,
}))
})
vi.mock('@langgenius/dify-ui/toast', () => ({
toast: toastMocks,
}))
vi.mock('@/service/use-snippets', () => ({
useImportSnippetDSLMutation: () => ({
isPending: false,
mutateAsync: serviceMocks.importMutateAsync,
}),
useConfirmSnippetImportMutation: () => ({
isPending: false,
mutateAsync: serviceMocks.confirmMutateAsync,
}),
}))
vi.mock('@/app/components/app/create-from-dsl-modal/uploader', () => ({
Uploader: ({ file, updateFile }: { file?: File; updateFile: (file?: File) => void }) => (
<button type="button" onClick={() => updateFile(new File(['name: snippet'], 'snippet.yml'))}>
{file?.name || 'select-dsl-file'}
</button>
),
}))
describe('ImportSnippetDSLDialog', () => {
beforeEach(() => {
vi.clearAllMocks()
contextMocks.workspacePermissionKeys = ['snippets.create_and_modify']
})
it('should import a snippet DSL from URL and navigate to the imported snippet', async () => {
const user = userEvent.setup()
const onClose = vi.fn()
serviceMocks.importMutateAsync.mockResolvedValue({
id: 'import-1',
status: 'completed',
snippet_id: 'snippet-1',
error: '',
})
render(<ImportSnippetDSLDialog isOpen onClose={onClose} />)
await user.click(screen.getByRole('button', { name: 'snippet.importFromDSLUrl' }))
await user.type(
screen.getByPlaceholderText('snippet.importFromDSLUrlPlaceholder'),
'https://example.com/snippet.yml',
)
await user.click(screen.getByRole('button', { name: 'common.operation.create' }))
await waitFor(() => {
expect(serviceMocks.importMutateAsync).toHaveBeenCalledWith({
mode: 'yaml-url',
yamlContent: undefined,
yamlUrl: 'https://example.com/snippet.yml',
})
expect(onClose).toHaveBeenCalledTimes(1)
expect(toastMocks.success).toHaveBeenCalledWith('snippet.importSuccess')
expect(routerMocks.push).toHaveBeenCalledWith('/snippets/snippet-1/orchestrate')
})
})
it('should confirm pending imports before navigating to the snippet', async () => {
const user = userEvent.setup()
serviceMocks.importMutateAsync.mockResolvedValue({
id: 'import-1',
status: 'pending',
current_dsl_version: '1.0.0',
imported_dsl_version: '0.9.0',
error: '',
})
serviceMocks.confirmMutateAsync.mockResolvedValue({
id: 'import-1',
status: 'completed-with-warnings',
snippet_id: 'snippet-2',
error: '',
})
render(<ImportSnippetDSLDialog isOpen onClose={vi.fn()} />)
await user.click(screen.getByRole('button', { name: 'select-dsl-file' }))
await user.click(screen.getByRole('button', { name: 'common.operation.create' }))
expect(await screen.findByText('snippet.dslVersionMismatchTitle')).toBeInTheDocument()
expect(screen.getByText('0.9.0')).toBeInTheDocument()
expect(screen.getByText('1.0.0')).toBeInTheDocument()
await user.click(screen.getByRole('button', { name: 'common.operation.confirm' }))
await waitFor(() => {
expect(serviceMocks.confirmMutateAsync).toHaveBeenCalledWith({ importId: 'import-1' })
expect(routerMocks.push).toHaveBeenCalledWith('/snippets/snippet-2/orchestrate')
})
})
it('should show import errors without closing the dialog', async () => {
const user = userEvent.setup()
const onClose = vi.fn()
serviceMocks.importMutateAsync.mockRejectedValue(new Error('invalid yaml'))
render(<ImportSnippetDSLDialog isOpen onClose={onClose} />)
await user.click(screen.getByRole('button', { name: 'select-dsl-file' }))
await user.click(screen.getByRole('button', { name: 'common.operation.create' }))
await waitFor(() => {
expect(toastMocks.error).toHaveBeenCalledWith('invalid yaml')
expect(onClose).not.toHaveBeenCalled()
})
})
it('should disable import without snippet create permission', async () => {
const user = userEvent.setup()
contextMocks.workspacePermissionKeys = []
render(<ImportSnippetDSLDialog isOpen onClose={vi.fn()} />)
await user.click(screen.getByRole('button', { name: 'snippet.importFromDSLUrl' }))
await user.type(
screen.getByPlaceholderText('snippet.importFromDSLUrlPlaceholder'),
'https://example.com/snippet.yml',
)
await user.click(screen.getByRole('button', { name: 'common.operation.create' }))
expect(screen.getByRole('button', { name: 'common.operation.create' })).toBeDisabled()
expect(serviceMocks.importMutateAsync).not.toHaveBeenCalled()
})
})