test(workflow): verify editor identity and request isolation

Keep all test and fixture migrations in this commit. Cover scoped session reset versus Query refresh, App/Pipeline/Snippet variable contracts and cache isolation, deletion failures and reset 204, Pipeline environment persistence, and distinct LLM model modes. Replace obsolete hook mocks with real editor scopes and retain browser interaction coverage.
This commit is contained in:
yyh
2026-10-15 22:48:36 +08:00
parent f9b1c2d395
commit 7ac3d2624f
256 changed files with 3709 additions and 2754 deletions
@@ -9,7 +9,6 @@ import {
seedAppDetail,
} from '@/test/console/query-data'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import { AppACLPermission } from '@/utils/permission'
import AppDetailLayout from '../layout-main'
@@ -63,7 +62,7 @@ const createAppDetail = (overrides: Partial<AppDetailWithSite> = {}) =>
createAppDetailFixture({
id: 'app-1',
name: 'Demo App',
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
permission_keys: [AppACLPermission.ViewLayout, AppACLPermission.Monitor],
...overrides,
})
@@ -148,15 +147,15 @@ describe('AppDetailLayout', () => {
describe('Document title', () => {
it.each([
['/app/app-1/workflow', 'common.appMenus.promptEng', AppModeEnum.WORKFLOW],
['/app/app-1/configuration', 'common.appMenus.promptEng', AppModeEnum.CHAT],
['/app/app-1/access-point', 'common.appMenus.accessPoint', AppModeEnum.WORKFLOW],
['/app/app-1/deploy', 'common.appMenus.deploy', AppModeEnum.WORKFLOW],
['/app/app-1/logs', 'common.appMenus.logs', AppModeEnum.WORKFLOW],
['/app/app-1/annotations', 'common.appMenus.annotations', AppModeEnum.CHAT],
['/app/app-1/overview', 'common.appMenus.overview', AppModeEnum.WORKFLOW],
['/app/app-1/access-config', 'navigation.settings.resourceAccess', AppModeEnum.WORKFLOW],
])('identifies the current detail page for %s', async (pathname, pageTitle, mode) => {
['/app/app-1/workflow', 'common.appMenus.promptEng', 'workflow'],
['/app/app-1/configuration', 'common.appMenus.promptEng', 'chat'],
['/app/app-1/access-point', 'common.appMenus.accessPoint', 'workflow'],
['/app/app-1/deploy', 'common.appMenus.deploy', 'workflow'],
['/app/app-1/logs', 'common.appMenus.logs', 'workflow'],
['/app/app-1/annotations', 'common.appMenus.annotations', 'chat'],
['/app/app-1/overview', 'common.appMenus.overview', 'workflow'],
['/app/app-1/access-config', 'navigation.settings.resourceAccess', 'workflow'],
] as const)('identifies the current detail page for %s', async (pathname, pageTitle, mode) => {
mockPathname = pathname
mockAppResponse.mockResolvedValue(
createAppDetail({
@@ -543,7 +542,7 @@ describe('AppDetailLayout', () => {
mockPathname = '/app/app-1/access-config'
mockAppResponse.mockResolvedValue(
createAppDetail({
mode: AppModeEnum.AGENT,
mode: 'agent' as const,
bound_agent_id: 'agent-1',
permission_keys: [AppACLPermission.AccessConfig],
}),
@@ -570,7 +569,7 @@ describe('AppDetailLayout', () => {
</AppDetailLayout>,
{
cachedAppDetail: createAppDetail({
mode: AppModeEnum.AGENT,
mode: 'agent' as const,
bound_agent_id: 'agent-1',
permission_keys: [AppACLPermission.AccessConfig],
}),
@@ -608,7 +607,7 @@ describe('AppDetailLayout', () => {
mockPathname = '/app/app-1/annotations'
mockAppResponse.mockResolvedValue(
createAppDetail({
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
permission_keys: [AppACLPermission.Monitor],
}),
)
@@ -629,7 +628,7 @@ describe('AppDetailLayout', () => {
mockPathname = '/app/app-1/annotations'
mockAppResponse.mockResolvedValue(
createAppDetail({
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
permission_keys: [AppACLPermission.LogAndAnnotation],
}),
)
@@ -3,7 +3,6 @@ import { screen } from '@testing-library/react'
import { consoleQuery } from '@/service/console'
import { createConsoleQueryClient, renderWithConsoleQuery } from '@/test/console/query-data'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import { AppACLPermission } from '@/utils/permission'
import AppDetailSection from '../app-detail-section'
@@ -221,18 +220,18 @@ describe('AppDetailSection', () => {
it.each([
{
label: 'the app is not a workflow app',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
permissionKeys: [AppACLPermission.Deploy],
},
{
label: 'app deploy ACL permission is missing',
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
permissionKeys: [AppACLPermission.Monitor],
},
])('should hide deploy navigation when $label', ({ mode, permissionKeys }) => {
] as const)('should hide deploy navigation when $label', ({ mode, permissionKeys }) => {
// Arrange
mockAppMode = mode
mockAppPermissionKeys = permissionKeys
mockAppPermissionKeys = [...permissionKeys]
// Act
render(<AppDetailSection />)
@@ -241,7 +240,7 @@ describe('AppDetailSection', () => {
expect(screen.queryByRole('link', { name: 'common.appMenus.deploy' })).not.toBeInTheDocument()
})
it.each([AppModeEnum.CHAT, AppModeEnum.AGENT_CHAT])(
it.each(['chat', 'agent-chat'] as const)(
'should render resource access navigation for %s apps when app access config permission is granted',
(mode) => {
// Arrange
@@ -263,7 +262,7 @@ describe('AppDetailSection', () => {
it('should hide resource access navigation for Agent apps', () => {
// Arrange
mockAppMode = AppModeEnum.AGENT
mockAppMode = 'agent'
mockAppPermissionKeys = [AppACLPermission.AccessConfig]
// Act
@@ -3,7 +3,6 @@ import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import * as React from 'react'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import AppInfoModals from '../app-info-modals'
vi.mock('next/dynamic', () => ({
@@ -112,7 +111,7 @@ const createAppDetail = (overrides: Partial<AppDetailWithSite> = {}) =>
createAppDetailFixture({
id: 'app-1',
name: 'Test App',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
icon: '🤖',
icon_type: 'emoji',
icon_background: '#FFEAD5',
@@ -5,7 +5,6 @@ import * as React from 'react'
import { createAccountProfileQueryWrapper } from '@/test/console/account-profile'
import { render as renderWithConsoleState } from '@/test/console/render'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import { AppACLPermission } from '@/utils/permission'
import AppInfoTrigger from '../app-info-trigger'
@@ -41,7 +40,7 @@ const createAppDetail = (overrides: Partial<AppDetailWithSite> = {}) =>
createAppDetailFixture({
id: 'app-1',
name: 'Test App',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
icon: '🤖',
icon_type: 'emoji',
icon_background: '#FFEAD5',
@@ -74,7 +73,7 @@ describe('AppInfoTrigger', () => {
it('renders expanded app metadata without making the app info clickable', async () => {
const user = userEvent.setup()
const props = createProps({
appDetail: createAppDetail({ name: 'My Chatbot', mode: AppModeEnum.ADVANCED_CHAT }),
appDetail: createAppDetail({ name: 'My Chatbot', mode: 'advanced-chat' as const }),
})
render(<AppInfoTrigger {...props} />)
@@ -119,7 +118,7 @@ describe('AppInfoTrigger', () => {
it('shows import DSL for workflow apps without a workflow conversion operation', async () => {
const user = userEvent.setup()
const props = createProps({
appDetail: createAppDetail({ mode: AppModeEnum.WORKFLOW }),
appDetail: createAppDetail({ mode: 'workflow' as const }),
})
render(<AppInfoTrigger {...props} />)
@@ -1,6 +1,5 @@
import type { TFunction } from 'i18next'
import { withSelectorKey } from '@/test/i18n-mock'
import { AppModeEnum } from '@/types/app'
import { getAppModeLabel } from '../app-mode-labels'
describe('getAppModeLabel', () => {
@@ -10,19 +9,19 @@ describe('getAppModeLabel', () => {
}, 'app') as unknown as TFunction<['app']>
it('should return advanced chat label', () => {
expect(getAppModeLabel(AppModeEnum.ADVANCED_CHAT, t)).toBe('app.types.advanced')
expect(getAppModeLabel('advanced-chat', t)).toBe('app.types.advanced')
})
it('should return agent chat label', () => {
expect(getAppModeLabel(AppModeEnum.AGENT_CHAT, t)).toBe('app.types.agent')
expect(getAppModeLabel('agent-chat', t)).toBe('app.types.agent')
})
it('should return chatbot label', () => {
expect(getAppModeLabel(AppModeEnum.CHAT, t)).toBe('app.types.chatbot')
expect(getAppModeLabel('chat', t)).toBe('app.types.chatbot')
})
it('should return completion label', () => {
expect(getAppModeLabel(AppModeEnum.COMPLETION, t)).toBe('app.types.completion')
expect(getAppModeLabel('completion', t)).toBe('app.types.completion')
})
it('should return workflow label for unknown mode', () => {
@@ -30,6 +29,6 @@ describe('getAppModeLabel', () => {
})
it('should return workflow label for workflow mode', () => {
expect(getAppModeLabel(AppModeEnum.WORKFLOW, t)).toBe('app.types.workflow')
expect(getAppModeLabel('workflow', t)).toBe('app.types.workflow')
})
})
@@ -3,7 +3,6 @@ import { act, waitFor } from '@testing-library/react'
import { consoleQuery } from '@/service/console'
import { createConsoleQueryClient, renderHookWithConsoleQuery } from '@/test/console/query-data'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import { getRedirection } from '@/utils/app-redirection'
import { useAppInfoActions } from '../use-app-info-actions'
@@ -140,7 +139,7 @@ describe('useAppInfoActions', () => {
mockAppDetail = createAppDetailFixture({
id: 'app-1',
name: 'Test App',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
icon: '🤖',
icon_type: 'emoji',
icon_background: '#FFEAD5',
@@ -370,7 +369,7 @@ describe('useAppInfoActions', () => {
})
it('should open export warning modal for workflow mode', async () => {
mockAppDetail = createAppDetailFixture({ ...mockAppDetail, mode: AppModeEnum.WORKFLOW })
mockAppDetail = createAppDetailFixture({ ...mockAppDetail, mode: 'workflow' as const })
const { result } = renderActions()
@@ -382,7 +381,7 @@ describe('useAppInfoActions', () => {
})
it('should open export warning modal for advanced_chat mode', async () => {
mockAppDetail = createAppDetailFixture({ ...mockAppDetail, mode: AppModeEnum.ADVANCED_CHAT })
mockAppDetail = createAppDetailFixture({ ...mockAppDetail, mode: 'advanced-chat' as const })
const { result } = renderActions()
@@ -396,7 +395,7 @@ describe('useAppInfoActions', () => {
describe('handleConfirmExport', () => {
it('keeps the warning open after failure so the user can retry', async () => {
mockAppDetail = createAppDetailFixture({ ...mockAppDetail, mode: AppModeEnum.WORKFLOW })
mockAppDetail = createAppDetailFixture({ ...mockAppDetail, mode: 'workflow' as const })
mockExportWorkflowAppDsl
.mockResolvedValueOnce({ status: 'failed' })
.mockResolvedValueOnce({ status: 'downloaded' })
@@ -415,7 +414,7 @@ describe('useAppInfoActions', () => {
})
it('should export directly when no secret env variables', async () => {
mockAppDetail = createAppDetailFixture({ ...mockAppDetail, mode: AppModeEnum.WORKFLOW })
mockAppDetail = createAppDetailFixture({ ...mockAppDetail, mode: 'workflow' as const })
const { result } = renderActions()
await act(async () => {
@@ -429,7 +428,7 @@ describe('useAppInfoActions', () => {
})
it('should set secret env list when secret variables exist', async () => {
mockAppDetail = createAppDetailFixture({ ...mockAppDetail, mode: AppModeEnum.WORKFLOW })
mockAppDetail = createAppDetailFixture({ ...mockAppDetail, mode: 'workflow' as const })
const secretVars = [{ value_type: 'secret', name: 'API_KEY', value: 'secret' }]
mockExportWorkflowAppDsl.mockResolvedValue({
status: 'confirmation-required',
@@ -10,7 +10,6 @@ import {
import { consoleQuery } from '@/service/console'
import { createConsoleQueryClient, renderWithConsoleQuery } from '@/test/console/query-data'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import { AppACLPermission } from '@/utils/permission'
import AppAccessConfigPage from '../index'
@@ -375,7 +374,7 @@ describe('AppAccessConfigPage', () => {
it('should not mount access config data hooks for Agent apps', () => {
appDetail = createAppDetailFixture({
id: 'app-1',
mode: AppModeEnum.AGENT,
mode: 'agent' as const,
maintainer: 'account-1',
permission_keys: [AppACLPermission.AccessConfig],
})
@@ -1,5 +1,5 @@
import type { AppMode } from '@dify/contracts/api/console/apps/types.gen'
import type { ReactElement } from 'react'
import type { AppModeEnum } from '@/types/app'
import { screen } from '@testing-library/react'
import { consoleQuery } from '@/service/console'
import { createHttpError } from '@/service/http-error'
@@ -43,7 +43,7 @@ function render(ui: ReactElement) {
queryClient.setQueryData(['system-features'], { webapp_auth: { enabled: true } })
queryClient.setQueryData(
consoleQuery.apps.byAppId.get.queryKey({ input: { params: { app_id: 'app-1' } } }),
createAppDetailFixture({ ...mocks.appInfo, mode: mocks.appInfo.mode as AppModeEnum }),
createAppDetailFixture({ ...mocks.appInfo, mode: mocks.appInfo.mode as AppMode }),
)
return renderWithConsole(ui, { wrapper: createQueryClientWrapper(queryClient) })
}
@@ -1,3 +1,4 @@
import type { AppMode } from '@dify/contracts/api/console/apps/types.gen'
import type { ReactElement } from 'react'
import { screen, waitFor, within } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
@@ -9,7 +10,6 @@ import { QueryClientTestProvider } from '@/test/console/query-provider'
import { render } from '@/test/console/render'
import { createAppDetailFixture, createAppSiteFixture } from '@/test/fixtures/app'
import { createTestQueryClient } from '@/test/query-client'
import { AppModeEnum } from '@/types/app'
import { EnvironmentServiceApiCard } from '../deployed-environment-access-points/environment-service-api-card'
import { EnvironmentWebAppCard } from '../deployed-environment-access-points/environment-web-app-card'
@@ -32,7 +32,7 @@ vi.mock('@/features/system-features/client', () => ({
}),
}))
let mockAppMode: AppModeEnum = AppModeEnum.WORKFLOW
let mockAppMode: AppMode = 'workflow'
vi.mock('@/context/i18n', () => ({
useDocLink: () => (path: string) => `https://docs.example.test/en${path}`,
@@ -149,7 +149,7 @@ describe('environment access point cards', () => {
beforeEach(() => {
vi.clearAllMocks()
mocks.saveSiteConfig.mockResolvedValue(true)
mockAppMode = AppModeEnum.WORKFLOW
mockAppMode = 'workflow'
vi.stubGlobal('fetch', async (input: RequestInfo | URL, init?: RequestInit) => {
const request = new Request(input, init)
const path = new URL(request.url).pathname
@@ -247,7 +247,7 @@ describe('environment access point cards', () => {
)
it('sends a chatflow app to the chat web app shell', async () => {
mockAppMode = AppModeEnum.ADVANCED_CHAT
mockAppMode = 'advanced-chat'
renderCard(<EnvironmentWebAppCard appId="app-1" environmentId="staging" canManageAccessPoint />)
@@ -1,3 +1,4 @@
import type { AppMode } from '@dify/contracts/api/console/apps/types.gen'
import type { AppEnvironment } from '@dify/contracts/enterprise-app-deploy/types.gen'
import type { ReactNode } from 'react'
import type { AccessPoint as AccessPointType } from '@/app/components/app/deploy/utils/access-point'
@@ -11,11 +12,10 @@ import { QueryClientTestProvider } from '@/test/console/query-provider'
import { render } from '@/test/console/render'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { createTestQueryClient } from '@/test/query-client'
import { AppModeEnum } from '@/types/app'
import { AppACLPermission } from '@/utils/permission'
import AccessPoint from '..'
let appMode: AppModeEnum = AppModeEnum.WORKFLOW
let appMode: AppMode = 'workflow'
let appPermissionKeys: string[] = [AppACLPermission.AccessPointView]
const accessPointMocks = vi.hoisted(() => ({
builtIn: vi.fn(),
@@ -133,7 +133,7 @@ const renderAccessPoint = ({
describe('AccessPoint', () => {
beforeEach(() => {
vi.clearAllMocks()
appMode = AppModeEnum.WORKFLOW
appMode = 'workflow'
appPermissionKeys = [AppACLPermission.AccessPointView]
})
@@ -243,7 +243,7 @@ describe('AccessPoint', () => {
})
it('hides environment tabs for app types without multi-environment support', () => {
appMode = AppModeEnum.CHAT
appMode = 'chat'
renderAccessPoint()
@@ -8,7 +8,6 @@ import { toast } from '@/app/notifications'
import { render } from '@/test/console/render'
import { createAppDetailFixture, createAppModelConfigFixture } from '@/test/fixtures/app'
import { createTestQueryClient } from '@/test/query-client'
import { AppModeEnum } from '@/types/app'
import { MCPAccessPointCard } from '../built-in-access-points/mcp-card'
const mocks = vi.hoisted(() => ({
@@ -65,7 +64,7 @@ vi.mock('@/app/components/tools/mcp/mcp-server-modal', () => ({
const appInfo = createAppDetailFixture({
api_base_url: 'https://api.example.test/v1',
id: 'app-1',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
model_config: createAppModelConfigFixture({
updated_at: 1_710_000_000,
user_input_form: [
@@ -82,7 +81,7 @@ const appInfo = createAppDetailFixture({
const workflowAppInfo = createAppDetailFixture({
...appInfo,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
model_config: null,
})
@@ -1,4 +1,4 @@
import type { AppDetailWithSite } from '@dify/contracts/api/console/apps/types.gen'
import type { AppDetailWithSite, AppMode } from '@dify/contracts/api/console/apps/types.gen'
import { QueryClientProvider, useSuspenseQuery } from '@tanstack/react-query'
import { screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
@@ -7,7 +7,6 @@ import { consoleQuery } from '@/service/console'
import { render } from '@/test/console/render'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { createTestQueryClient } from '@/test/query-client'
import { AppModeEnum } from '@/types/app'
import { ServiceApiAccessPointCard } from '../built-in-access-points/service-api-card'
const mocks = vi.hoisted(() => ({
@@ -53,7 +52,7 @@ vi.mock('../built-in-access-points/api-secret-key-button', () => ({
}))
function createAppInfo(
mode: AppModeEnum,
mode: AppMode,
overrides: Partial<AppDetailWithSite> = {},
): AppDetailWithSite {
return createAppDetailFixture({
@@ -66,7 +65,7 @@ function createAppInfo(
}
function renderCard(
mode: AppModeEnum,
mode: AppMode,
availability: 'available' | 'loading' | 'unavailable' = 'available',
canManage = true,
overrides: Partial<AppDetailWithSite> = {},
@@ -124,12 +123,12 @@ describe('ServiceApiAccessPointCard', () => {
})
it.each([
[AppModeEnum.ADVANCED_CHAT, '/api-reference/guides/chatflow'],
[AppModeEnum.WORKFLOW, '/api-reference/guides/workflow'],
[AppModeEnum.CHAT, '/api-reference/guides/chat'],
[AppModeEnum.AGENT_CHAT, '/api-reference/guides/chat'],
[AppModeEnum.COMPLETION, '/api-reference/guides/completion'],
])('links %s apps to the matching external API reference', (mode, path) => {
['advanced-chat', '/api-reference/guides/chatflow'],
['workflow', '/api-reference/guides/workflow'],
['chat', '/api-reference/guides/chat'],
['agent-chat', '/api-reference/guides/chat'],
['completion', '/api-reference/guides/completion'],
] as const)('links %s apps to the matching external API reference', (mode, path) => {
renderCard(mode)
const apiReferenceLink = screen.getByRole('link', { name: /apiInfo\.doc/ })
@@ -141,7 +140,7 @@ describe('ServiceApiAccessPointCard', () => {
it('updates API status through the generated contract', async () => {
const user = userEvent.setup()
renderCard(AppModeEnum.WORKFLOW)
renderCard('workflow')
await user.click(screen.getByRole('switch'))
@@ -154,7 +153,7 @@ describe('ServiceApiAccessPointCard', () => {
})
it('shows loading without reporting an environment failure', () => {
renderCard(AppModeEnum.WORKFLOW, 'loading')
renderCard('workflow', 'loading')
const card = screen.getByRole('region', { name: /serviceApi\.title/ })
expect(card).toHaveAttribute('aria-busy', 'true')
@@ -165,7 +164,7 @@ describe('ServiceApiAccessPointCard', () => {
})
it('keeps API keys and external documentation available when the API is stopped', () => {
renderCard(AppModeEnum.WORKFLOW, 'available', true, { enable_api: false })
renderCard('workflow', 'available', true, { enable_api: false })
expect(screen.getByRole('button', { name: 'api-secret-keys' })).toBeEnabled()
const apiReferenceLink = screen.getByRole('link', { name: /apiInfo\.doc/ })
@@ -176,14 +175,14 @@ describe('ServiceApiAccessPointCard', () => {
})
it('disables API management without Access Point management permission', () => {
renderCard(AppModeEnum.WORKFLOW, 'available', false)
renderCard('workflow', 'available', false)
expect(screen.getByRole('button', { name: 'api-secret-keys' })).toBeDisabled()
expect(screen.getByRole('switch')).toHaveAttribute('aria-disabled', 'true')
})
it('disables API keys and external documentation when the access point is unavailable', () => {
renderCard(AppModeEnum.WORKFLOW, 'unavailable')
renderCard('workflow', 'unavailable')
expect(screen.getByRole('button', { name: 'api-secret-keys' })).toBeDisabled()
expect(screen.getByRole('button', { name: /apiInfo\.doc/ })).toBeDisabled()
@@ -193,7 +192,7 @@ describe('ServiceApiAccessPointCard', () => {
const user = userEvent.setup()
const toggle = createDeferredPromise<{ enable_api: boolean }>()
mocks.apiEnable.mockReturnValueOnce(toggle.promise)
renderCard(AppModeEnum.WORKFLOW)
renderCard('workflow')
const accessSwitch = screen.getByRole('switch')
await user.click(accessSwitch)
@@ -212,7 +211,7 @@ describe('ServiceApiAccessPointCard', () => {
it('keeps successful status changes silent', async () => {
const user = userEvent.setup()
mocks.apiEnable.mockResolvedValueOnce(createAppDetailFixture({ enable_api: false }))
renderCard(AppModeEnum.WORKFLOW)
renderCard('workflow')
const accessSwitch = screen.getByRole('switch')
await user.click(accessSwitch)
@@ -6,7 +6,6 @@ import { toast } from '@/app/notifications'
import { render } from '@/test/console/render'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { createTestQueryClient } from '@/test/query-client'
import { AppModeEnum } from '@/types/app'
import { TriggerAccessPointCard } from '../built-in-access-points/trigger-card'
const mocks = vi.hoisted(() => ({
@@ -71,7 +70,7 @@ vi.mock('@/app/components/workflow/block-icon', () => ({
const appInfo = createAppDetailFixture({
id: 'app-1',
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
})
function createTrigger(id: string, status: AppTrigger['status']): AppTrigger {
@@ -1,4 +1,8 @@
import type { AppDetailWithSite, AppSiteResponse } from '@dify/contracts/api/console/apps/types.gen'
import type {
AppDetailWithSite,
AppMode,
AppSiteResponse,
} from '@dify/contracts/api/console/apps/types.gen'
import type { PublishedWorkflow } from '../shared/utils'
import type { InputVar, Node } from '@/app/components/workflow/types'
import { QueryClientProvider, useSuspenseQuery } from '@tanstack/react-query'
@@ -12,7 +16,6 @@ import { seedAccountProfileQuery } from '@/test/console/account-profile'
import { render } from '@/test/console/render'
import { createAppDetailFixture, createAppSiteFixture } from '@/test/fixtures/app'
import { createTestQueryClient } from '@/test/query-client'
import { AppModeEnum } from '@/types/app'
import { basePath } from '@/utils/var'
import { WebAppAccessPointCard } from '../built-in-access-points/web-app-card'
@@ -92,7 +95,7 @@ vi.mock('@/context/i18n', () => ({
useDocLink: () => (path: string) => `https://docs.example.test/en${path}`,
}))
function createAppInfo(mode: AppModeEnum): AppDetailWithSite {
function createAppInfo(mode: AppMode): AppDetailWithSite {
return createAppDetailFixture({
access_mode: AccessMode.PUBLIC,
api_base_url: 'https://api.example.test/v1',
@@ -111,7 +114,7 @@ function createAppInfo(mode: AppModeEnum): AppDetailWithSite {
}
function renderCard(
mode: AppModeEnum,
mode: AppMode,
availability: 'available' | 'loading' | 'unavailable' = 'available',
workflow?: PublishedWorkflow,
{
@@ -259,7 +262,7 @@ describe('WebAppAccessPointCard', () => {
})
it('disables site actions when the app has no site', () => {
renderCard(AppModeEnum.CHAT, 'available', undefined, { appOverrides: { site: null } })
renderCard('chat', 'available', undefined, { appOverrides: { site: null } })
expect(screen.getByRole('button', { name: /settings\.settings/ })).toBeDisabled()
expect(
@@ -272,7 +275,7 @@ describe('WebAppAccessPointCard', () => {
})
it('does not present an unknown access mode as public or allow launching', () => {
renderCard(AppModeEnum.CHAT, 'available', undefined, { accessMode: null })
renderCard('chat', 'available', undefined, { accessMode: null })
expect(
screen.queryByRole('button', { name: /accessControlDialog\.accessItems\.anyone/ }),
@@ -287,7 +290,7 @@ describe('WebAppAccessPointCard', () => {
})
it('shows the current access mode without a redundant section label', () => {
renderCard(AppModeEnum.CHAT)
renderCard('chat')
expect(screen.queryByText(/publishApp\.title/)).not.toBeInTheDocument()
expect(
@@ -298,7 +301,7 @@ describe('WebAppAccessPointCard', () => {
it('disables Open and explains when the user cannot access the Web App', async () => {
const user = userEvent.setup()
mocks.getUserCanAccess.mockResolvedValue({ result: false })
renderCard(AppModeEnum.WORKFLOW, 'available', undefined, {
renderCard('workflow', 'available', undefined, {
accessMode: AccessMode.SPECIFIC_GROUPS_MEMBERS,
})
@@ -319,7 +322,7 @@ describe('WebAppAccessPointCard', () => {
it('keeps Open disabled until access is granted', async () => {
const permission = createDeferredPromise<{ result: boolean }>()
mocks.getUserCanAccess.mockReturnValueOnce(permission.promise)
renderCard(AppModeEnum.WORKFLOW)
renderCard('workflow')
expect(screen.getByRole('button', { name: /studio\.accessPoint\.open/ })).toHaveAttribute(
'aria-disabled',
@@ -336,7 +339,7 @@ describe('WebAppAccessPointCard', () => {
it('allows opening external-member Web Apps independently of platform access', () => {
mocks.getUserCanAccess.mockResolvedValue({ result: false })
renderCard(AppModeEnum.WORKFLOW, 'available', undefined, {
renderCard('workflow', 'available', undefined, {
accessMode: AccessMode.EXTERNAL_MEMBERS,
})
@@ -347,7 +350,7 @@ describe('WebAppAccessPointCard', () => {
})
it('allows opening Web Apps without querying permissions when access control is disabled', () => {
renderCard(AppModeEnum.CHAT, 'available', undefined, { showAccessControl: false })
renderCard('chat', 'available', undefined, { showAccessControl: false })
expect(screen.getByRole('link', { name: /studio\.accessPoint\.open/ })).toHaveAttribute(
'href',
@@ -359,7 +362,7 @@ describe('WebAppAccessPointCard', () => {
it('updates Open after saving Web App access permissions', async () => {
const user = userEvent.setup()
mocks.getUserCanAccess.mockResolvedValue({ result: false })
renderCard(AppModeEnum.CHAT, 'available', undefined, {
renderCard('chat', 'available', undefined, {
accessMode: AccessMode.SPECIFIC_GROUPS_MEMBERS,
})
@@ -380,7 +383,7 @@ describe('WebAppAccessPointCard', () => {
)
})
it.each([AppModeEnum.WORKFLOW, AppModeEnum.COMPLETION])(
it.each(['workflow', 'completion'] as const)(
'does not offer Embed into site for %s apps',
(mode) => {
renderCard(mode)
@@ -393,7 +396,7 @@ describe('WebAppAccessPointCard', () => {
it('keeps Embed into site for non-workflow Web apps', async () => {
const user = userEvent.setup()
renderCard(AppModeEnum.CHAT)
renderCard('chat')
await user.click(screen.getByRole('button', { name: /embedIntoSite/ }))
@@ -404,7 +407,7 @@ describe('WebAppAccessPointCard', () => {
it('updates site status through the generated contract', async () => {
const user = userEvent.setup()
renderCard(AppModeEnum.CHAT)
renderCard('chat')
await user.click(screen.getByRole('switch'))
@@ -418,7 +421,7 @@ describe('WebAppAccessPointCard', () => {
it('keeps site status changes behind Access Point management permission', async () => {
const user = userEvent.setup()
renderCard(AppModeEnum.CHAT, 'available', undefined, { canManageAccessPoint: false })
renderCard('chat', 'available', undefined, { canManageAccessPoint: false })
await user.click(screen.getByRole('switch'))
@@ -429,7 +432,7 @@ describe('WebAppAccessPointCard', () => {
const user = userEvent.setup()
const refresh = createDeferredPromise<AppDetailWithSite | null>()
mocks.getAppDetail.mockReturnValueOnce(refresh.promise)
renderCard(AppModeEnum.CHAT)
renderCard('chat')
await user.click(screen.getByRole('button', { name: /overview\.appInfo\.regenerate/ }))
await user.click(screen.getByRole('button', { name: /operation\.confirm/ }))
@@ -456,7 +459,7 @@ describe('WebAppAccessPointCard', () => {
const user = userEvent.setup()
const error = new Error('request failed')
mocks.siteEnable.mockRejectedValueOnce(error)
renderCard(AppModeEnum.CHAT)
renderCard('chat')
await user.click(screen.getByRole('switch'))
@@ -467,7 +470,7 @@ describe('WebAppAccessPointCard', () => {
it('passes hidden Chatflow inputs to the embed dialog', async () => {
const user = userEvent.setup()
renderCard(AppModeEnum.ADVANCED_CHAT, 'available', workflowWithHiddenInput)
renderCard('advanced-chat', 'available', workflowWithHiddenInput)
await user.click(screen.getByRole('button', { name: /embedIntoSite/ }))
@@ -478,7 +481,7 @@ describe('WebAppAccessPointCard', () => {
it('configures hidden workflow inputs before opening the Web App', async () => {
const user = userEvent.setup()
const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null)
renderCard(AppModeEnum.WORKFLOW, 'available', workflowWithHiddenInput)
renderCard('workflow', 'available', workflowWithHiddenInput)
await user.click(screen.getByRole('button', { name: /operation\.config/ }))
await user.type(screen.getByLabelText('Secret'), 'top-secret')
@@ -496,7 +499,7 @@ describe('WebAppAccessPointCard', () => {
const user = userEvent.setup()
const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null)
mocks.getUserCanAccess.mockResolvedValue({ result: false })
renderCard(AppModeEnum.WORKFLOW, 'available', workflowWithHiddenInput, {
renderCard('workflow', 'available', workflowWithHiddenInput, {
accessMode: AccessMode.SPECIFIC_GROUPS_MEMBERS,
})
@@ -514,7 +517,7 @@ describe('WebAppAccessPointCard', () => {
})
it('shows loading without reporting an environment failure', () => {
renderCard(AppModeEnum.WORKFLOW, 'loading')
renderCard('workflow', 'loading')
const card = screen.getByRole('region', { name: /webApp\.title/ })
expect(card).toHaveAttribute('aria-busy', 'true')
@@ -525,7 +528,7 @@ describe('WebAppAccessPointCard', () => {
})
it('does not announce an unavailable access control entry as loading', () => {
renderCard(AppModeEnum.WORKFLOW, 'unavailable')
renderCard('workflow', 'unavailable')
const card = screen.getByRole('region', { name: /webApp\.title/ })
expect(within(card).queryByRole('status', { name: 'common.loading' })).not.toBeInTheDocument()
@@ -538,7 +541,7 @@ describe('WebAppAccessPointCard', () => {
mocks.siteEnable
.mockReturnValueOnce(firstToggle.promise)
.mockReturnValueOnce(secondToggle.promise)
renderCard(AppModeEnum.CHAT)
renderCard('chat')
const accessSwitch = screen.getByRole('switch')
await user.click(accessSwitch)
@@ -573,7 +576,7 @@ describe('WebAppAccessPointCard', () => {
it('disables Web App management actions without Access Point management', async () => {
const user = userEvent.setup()
renderCard(AppModeEnum.CHAT, 'available', undefined, { canManageAccessPoint: false })
renderCard('chat', 'available', undefined, { canManageAccessPoint: false })
expect(screen.getByRole('switch')).toHaveAttribute('aria-disabled', 'true')
expect(screen.getByRole('button', { name: /embedIntoSite/ })).toBeDisabled()
@@ -17,7 +17,6 @@ import {
} from '@/service/annotation'
import { renderWithConsoleQuery } from '@/test/console/query-data'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import Annotation from '../index'
import { AnnotationEnableStatus, JobStatus } from '../type'
@@ -181,7 +180,7 @@ const updateAnnotationStatusMock = updateAnnotationStatus as Mock
const appDetail = createAppDetailFixture({
id: 'app-id',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
})
const createAnnotation = (overrides: Partial<AnnotationItem> = {}): AnnotationItem => ({
@@ -11,7 +11,6 @@ import {
renderWithConsoleQuery as renderWithQuery,
} from '@/test/console/query-data'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import { AppACLPermission } from '@/utils/permission'
import { basePath } from '@/utils/var'
import { AppPublisher } from '../index'
@@ -269,7 +268,7 @@ describe('AppPublisher', () => {
mockAppDetail = {
id: 'app-1',
name: 'Demo App',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
maintainer: 'user-2',
permission_keys: [],
access_mode: AccessMode.SPECIFIC_GROUPS_MEMBERS,
@@ -346,7 +345,7 @@ describe('AppPublisher', () => {
const invalidateQueries = vi.spyOn(queryClient, 'invalidateQueries')
mockAppDetail = {
...mockAppDetail,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
permission_keys: [AppACLPermission.Deploy],
}
mockOnPublish.mockResolvedValue(undefined)
@@ -398,7 +397,7 @@ describe('AppPublisher', () => {
const invalidateQueries = vi.spyOn(queryClient, 'invalidateQueries')
mockAppDetail = {
...mockAppDetail,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
permission_keys: [],
}
mockOnPublish.mockResolvedValue(undefined)
@@ -423,7 +422,7 @@ describe('AppPublisher', () => {
const user = userEvent.setup()
mockAppDetail = {
...mockAppDetail,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
}
mockPublishedWorkflow = {
created_at: Math.floor(Date.now() / 1000),
@@ -458,7 +457,7 @@ describe('AppPublisher', () => {
expect(mockUpdateWorkflow).toHaveBeenCalledWith(
{
appId: 'app-1',
appMode: AppModeEnum.WORKFLOW,
appMode: 'workflow' as const,
url: '/apps/app-1/workflows/workflow-version-5',
title: 'Release 6',
releaseNotes: 'Updated notes',
@@ -480,7 +479,7 @@ describe('AppPublisher', () => {
const queryClient = createConsoleQueryClient()
mockAppDetail = {
...mockAppDetail,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
permission_keys: [AppACLPermission.Deploy],
}
const environmentsQuery =
@@ -513,7 +512,7 @@ describe('AppPublisher', () => {
it('should keep the workflow publisher single-environment without app deploy ACL', () => {
mockAppDetail = {
...mockAppDetail,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
permission_keys: [],
}
@@ -581,7 +580,7 @@ describe('AppPublisher', () => {
}
mockAppDetail = {
...mockAppDetail,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
permission_keys: [AppACLPermission.Deploy],
}
const environmentsQuery =
@@ -684,7 +683,7 @@ describe('AppPublisher', () => {
const detailRequests: Request[] = []
mockAppDetail = {
...mockAppDetail,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
permission_keys: [AppACLPermission.Deploy],
}
const environmentsQuery =
@@ -832,7 +831,7 @@ describe('AppPublisher', () => {
it('should keep workflow tool drawer mounted after closing the publish popover', () => {
mockAppDetail = {
...mockAppDetail,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
}
render(<AppPublisher appId="app-1" publishedAt={Date.now()} toolPublished />)
@@ -853,7 +852,7 @@ describe('AppPublisher', () => {
it('should show one success toast when automatically publishing a workflow tool', async () => {
mockAppDetail = {
...mockAppDetail,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
}
mockOnPublish.mockImplementation(async (_params, options?: { showSuccessToast?: boolean }) => {
if (options?.showSuccessToast !== false) mockToastSuccess('common.api.actionSuccess')
@@ -876,7 +875,7 @@ describe('AppPublisher', () => {
it('should not show a success toast when workflow tool creation fails after publishing', async () => {
mockAppDetail = {
...mockAppDetail,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
}
mockOnPublish.mockImplementation(async (_params, options?: { showSuccessToast?: boolean }) => {
if (options?.showSuccessToast !== false) mockToastSuccess('common.api.actionSuccess')
@@ -900,7 +899,7 @@ describe('AppPublisher', () => {
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
mockAppDetail = {
...mockAppDetail,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
}
mockOnPublish.mockRejectedValueOnce(new Error('publish failed'))
@@ -922,7 +921,7 @@ describe('AppPublisher', () => {
mockWorkspacePermissionKeys = []
mockAppDetail = {
...mockAppDetail,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
}
render(<AppPublisher appId="app-1" publishedAt={Date.now()} />)
@@ -1066,7 +1065,7 @@ describe('AppPublisher', () => {
const user = userEvent.setup()
mockAppDetail = {
...mockAppDetail,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
}
mockPublishedWorkflow = {
created_at: 1_710_000_100,
@@ -1102,7 +1101,7 @@ describe('AppPublisher', () => {
}
mockAppDetail = {
...mockAppDetail,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
}
mockFetchPublishedWorkflow.mockResolvedValue({
created_at: 1_710_000_300,
@@ -2,7 +2,6 @@ import { detectPlatform } from '@tanstack/react-hotkeys'
import { act, render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { useState } from 'react'
import { AppModeEnum } from '@/types/app'
import { PublisherPanel } from '../publisher-content/publisher-panel'
vi.mock('../environment-deployment-flow', () => ({
@@ -38,7 +37,7 @@ function PublisherPanelHarness({
builtInPublisher={{
actions: {
appId: 'app-1',
appMode: AppModeEnum.CHAT,
appMode: 'chat' as const,
appURL: 'https://example.com/app',
canViewAccessPoint: false,
disabledFunctionButton: false,
@@ -8,7 +8,6 @@ import { useRef } from 'react'
import { expectLoadingButton } from '@/test/button'
import { createConsoleQueryWrapper } from '@/test/console/query-data'
import { render as renderWithConsoleState } from '@/test/console/render'
import { AppModeEnum } from '@/types/app'
import { PublisherActionsSection } from '../built-in-publisher/actions-section'
import { PublisherSummarySection } from '../built-in-publisher/summary-section'
import { usePublishController } from '../publisher-content/use-publish-controller'
@@ -82,7 +81,7 @@ function PublisherPopup({
function PublishingPopup({ onPublish }: { onPublish: () => Promise<void> }) {
const publish = usePublishController({
appMode: AppModeEnum.CHAT,
appMode: 'chat' as const,
supportsMultiEnvironment: false,
onClose: () => {},
onPublish,
@@ -535,7 +534,7 @@ describe('app-publisher sections', () => {
render(
<PublisherActionsSection
appId={'workflow-app'}
appMode={AppModeEnum.WORKFLOW}
appMode={'workflow'}
appURL="https://example.com/app"
canViewAccessPoint
disabledFunctionButton={false}
@@ -595,7 +594,7 @@ describe('app-publisher sections', () => {
render(
<PublisherActionsSection
appId={'workflow-app'}
appMode={AppModeEnum.WORKFLOW}
appMode={'workflow'}
appURL="https://example.com/app"
canViewAccessPoint
disabledFunctionButton={false}
@@ -626,7 +625,7 @@ describe('app-publisher sections', () => {
it('should show the disabled reason below setup and configured workflow tool actions', () => {
const commonProps = {
appId: 'workflow-app',
appMode: AppModeEnum.WORKFLOW,
appMode: 'workflow' as const,
appURL: 'https://example.com/app',
canViewAccessPoint: true,
disabledFunctionButton: false,
@@ -666,7 +665,7 @@ describe('app-publisher sections', () => {
const onConfigureWorkflowTool = vi.fn()
const commonProps = {
appId: 'workflow-app',
appMode: AppModeEnum.WORKFLOW,
appMode: 'workflow' as const,
appURL: 'https://example.com/app',
canViewAccessPoint: true,
disabledFunctionButton: false,
@@ -710,7 +709,7 @@ describe('app-publisher sections', () => {
render(
<PublisherActionsSection
appId={'trigger-app'}
appMode={AppModeEnum.WORKFLOW}
appMode={'workflow'}
appURL="https://example.com/app"
canViewAccessPoint
disabledFunctionButton={false}
@@ -740,7 +739,7 @@ describe('app-publisher sections', () => {
render(
<PublisherActionsSection
appId={'workflow-app'}
appMode={AppModeEnum.WORKFLOW}
appMode={'workflow'}
appURL="https://example.com/app"
canViewAccessPoint={false}
disabledFunctionButton={false}
@@ -765,7 +764,7 @@ describe('app-publisher sections', () => {
render(
<PublisherActionsSection
appId={'workflow-app'}
appMode={AppModeEnum.WORKFLOW}
appMode={'workflow'}
appURL="https://example.com/app"
canViewAccessPoint
disabledFunctionButton
@@ -795,7 +794,7 @@ describe('app-publisher sections', () => {
render(
<PublisherActionsSection
appId={'workflow-app'}
appMode={AppModeEnum.WORKFLOW}
appMode={'workflow'}
appURL="https://example.com/app"
canViewAccessPoint
disabledFunctionButton
@@ -822,7 +821,7 @@ describe('app-publisher sections', () => {
render(
<PublisherActionsSection
appId={'workflow-app'}
appMode={AppModeEnum.WORKFLOW}
appMode={'workflow'}
appURL="https://example.com/app"
canViewAccessPoint
disabledFunctionButton
@@ -1,17 +1,16 @@
import type { TFunction } from 'i18next'
import { withSelectorKey } from '@/test/i18n-mock'
import { AppModeEnum } from '@/types/app'
import { basePath } from '@/utils/var'
import { getDisabledFunctionTooltip, getPublisherAppMode, getPublisherAppUrl } from '../utils'
describe('app-publisher utils', () => {
describe('getPublisherAppMode', () => {
it('should normalize chat-like apps to chat mode', () => {
expect(getPublisherAppMode(AppModeEnum.AGENT_CHAT)).toBe(AppModeEnum.CHAT)
expect(getPublisherAppMode('agent-chat')).toBe('chat')
})
it('should keep completion mode unchanged', () => {
expect(getPublisherAppMode(AppModeEnum.COMPLETION)).toBe(AppModeEnum.COMPLETION)
expect(getPublisherAppMode('completion')).toBe('completion')
})
})
@@ -21,7 +20,7 @@ describe('app-publisher utils', () => {
getPublisherAppUrl({
appBaseUrl: 'https://example.com',
accessToken: 'token-1',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
}),
).toBe(`https://example.com${basePath}/chat/token-1`)
})
@@ -9,7 +9,7 @@ import { fireEvent, render, screen } from '@testing-library/react'
import * as React from 'react'
import { PluginCategoryEnum } from '@/app/components/plugins/types'
import { CollectionType } from '@/app/components/tools/types'
import { AppModeEnum, ModelModeType } from '@/types/app'
import { ModelModeType } from '@/types/app'
import ConfigurationView from '../configuration-view'
const mockIsAgentV2Enabled = vi.hoisted(() => vi.fn(() => false))
@@ -96,7 +96,7 @@ const createContextValue = (): ComponentProps<typeof ConfigContext.Provider>['va
appId: 'app-1',
onOpenFeatures: vi.fn(),
isTrailFinished: false,
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
modelModeType: ModelModeType.chat,
promptMode: 'simple' as never,
setPromptMode: vi.fn(),
@@ -351,7 +351,7 @@ describe('ConfigurationView', () => {
it('should show the legacy Agent badge for legacy Agent apps when Agent v2 is enabled', async () => {
mockIsAgentV2Enabled.mockReturnValue(true)
const contextValue = createContextValue()
contextValue.mode = AppModeEnum.AGENT_CHAT
contextValue.mode = 'agent-chat'
render(<ConfigurationView {...createViewModel({ contextValue })} />)
@@ -375,7 +375,7 @@ describe('ConfigurationView', () => {
it('should not show the legacy Agent badge when Agent v2 is disabled', () => {
const contextValue = createContextValue()
contextValue.mode = AppModeEnum.AGENT_CHAT
contextValue.mode = 'agent-chat'
render(<ConfigurationView {...createViewModel({ contextValue })} />)
@@ -1,5 +1,5 @@
import type { ModelConfig } from '@/models/debug'
import { AppModeEnum, ModelModeType, Resolution, TransferMethod } from '@/types/app'
import { ModelModeType, Resolution, TransferMethod } from '@/types/app'
import {
buildConfigurationFeaturesData,
getConfigurationPublishingState,
@@ -129,7 +129,7 @@ describe('configuration utils', () => {
hasSetContextVar: false,
hasSelectedDataSets: false,
isAdvancedMode: true,
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
modelModeType: ModelModeType.completion,
promptTemplate: 'ignored',
})
@@ -158,7 +158,7 @@ describe('configuration utils', () => {
hasSetContextVar: false,
hasSelectedDataSets: true,
isAdvancedMode: false,
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
modelModeType: ModelModeType.completion,
promptTemplate: 'Prompt',
})
@@ -188,7 +188,7 @@ describe('configuration utils', () => {
hasSetContextVar: true,
hasSelectedDataSets: false,
isAdvancedMode: true,
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
modelModeType: ModelModeType.chat,
promptTemplate: 'ignored',
})
@@ -217,7 +217,7 @@ describe('configuration utils', () => {
hasSetContextVar: true,
hasSelectedDataSets: false,
isAdvancedMode: true,
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
modelModeType: ModelModeType.completion,
promptTemplate: 'ignored',
})
@@ -4,7 +4,6 @@ import { fireEvent, render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { INSERT_VARIABLE_VALUE_BLOCK_COMMAND } from '@/app/components/base/prompt-editor/plugins/variable-block'
import ConfigContext from '@/context/debug-configuration'
import { AppModeEnum } from '@/types/app'
import AdvancedPromptInput from '../advanced-prompt-input'
const mockEmit = vi.fn()
@@ -82,7 +81,7 @@ vi.mock('../prompt-editor-height-resize-wrap', () => ({
const createContextValue = () =>
({
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
hasSetBlockStatus: {
context: false,
history: false,
@@ -5,7 +5,7 @@ import * as React from 'react'
import { MAX_PROMPT_MESSAGE_LENGTH } from '@/config'
import ConfigContext from '@/context/debug-configuration'
import { PromptRole } from '@/models/debug'
import { AppModeEnum, ModelModeType } from '@/types/app'
import { ModelModeType } from '@/types/app'
import Prompt from '../index'
type DebugConfiguration = {
@@ -113,7 +113,7 @@ const renderComponent = (
contextOverrides: Partial<DebugConfiguration> = {},
) => {
const mergedProps: IPromptProps = {
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
promptTemplate: 'initial template',
promptVariables: defaultPromptVariables,
onChange: vi.fn(),
@@ -144,7 +144,7 @@ describe('Prompt config component', () => {
const simplePrompt = screen.getByTestId('simple-prompt-input')
expect(simplePrompt)!.toBeInTheDocument()
expect(simplePrompt)!.toHaveAttribute('data-mode', AppModeEnum.CHAT)
expect(simplePrompt)!.toHaveAttribute('data-mode', 'chat')
expect(mockSimplePromptInputProps?.promptTemplate).toBe('initial template')
fireEvent.click(simplePrompt)
expect(onChange).toHaveBeenCalledWith('mocked prompt', defaultPromptVariables)
@@ -1,5 +1,4 @@
import { render, screen } from '@testing-library/react'
import { AppModeEnum } from '@/types/app'
import Prompt from '../simple-prompt-input'
vi.mock('@/app/components/base/features/hooks', () => ({
@@ -9,7 +8,7 @@ vi.mock('@/app/components/base/features/hooks', () => ({
}))
describe('SimplePromptInput accessibility', () => {
it.each([AppModeEnum.CHAT, AppModeEnum.COMPLETION])(
it.each(['chat', 'completion'] as const)(
'names the real editor from the visible section heading in %s mode',
(mode) => {
render(<Prompt mode={mode} promptTemplate="" promptVariables={[]} noResize />)
@@ -22,9 +21,7 @@ describe('SimplePromptInput accessibility', () => {
)
it('keeps an accessible editor name when embedded without a section heading', () => {
render(
<Prompt mode={AppModeEnum.CHAT} promptTemplate="" promptVariables={[]} noTitle noResize />,
)
render(<Prompt mode={'chat'} promptTemplate="" promptVariables={[]} noTitle noResize />)
expect(screen.getByRole('textbox', { name: 'appDebug.chatSubTitle' })).toBeInTheDocument()
expect(screen.queryByRole('heading')).not.toBeInTheDocument()
})
@@ -2,7 +2,6 @@ import type { ReactNode } from 'react'
import { fireEvent, render, screen } from '@testing-library/react'
import { INSERT_VARIABLE_VALUE_BLOCK_COMMAND } from '@/app/components/base/prompt-editor/plugins/variable-block'
import ConfigContext from '@/context/debug-configuration'
import { AppModeEnum } from '@/types/app'
import Prompt from '../simple-prompt-input'
const mockEmit = vi.fn()
@@ -144,7 +143,7 @@ describe('SimplePromptInput', () => {
render(
<ConfigContext.Provider value={createContextValue()}>
<Prompt
mode={AppModeEnum.CHAT}
mode={'chat'}
promptTemplate="Hello {{new_var}}"
promptVariables={[]}
onChange={mockOnChange}
@@ -170,7 +169,7 @@ describe('SimplePromptInput', () => {
render(
<ConfigContext.Provider value={createContextValue()}>
<Prompt
mode={AppModeEnum.CHAT}
mode={'chat'}
promptTemplate="Hello"
promptVariables={[
{ key: 'existing_var', name: 'Existing', type: 'string', required: true },
@@ -215,12 +214,7 @@ describe('SimplePromptInput', () => {
it('should apply automatic generation results to prompt and opening statement', () => {
render(
<ConfigContext.Provider value={createContextValue()}>
<Prompt
mode={AppModeEnum.CHAT}
promptTemplate="Hello"
promptVariables={[]}
onChange={mockOnChange}
/>
<Prompt mode={'chat'} promptTemplate="Hello" promptVariables={[]} onChange={mockOnChange} />
</ConfigContext.Provider>,
)
@@ -275,7 +269,7 @@ describe('SimplePromptInput', () => {
})}
>
<Prompt
mode={AppModeEnum.CHAT}
mode={'chat'}
promptTemplate="Hello {{existing_var}}"
promptVariables={[
{ key: 'existing_var', name: 'Existing', type: 'string', required: true },
@@ -300,7 +294,7 @@ describe('SimplePromptInput', () => {
})}
>
<Prompt
mode={AppModeEnum.CHAT}
mode={'chat'}
promptTemplate="Hello {{search_api}} {{existing_var}}"
promptVariables={[
{ key: 'existing_var', name: 'Existing', type: 'string', required: true },
@@ -321,7 +315,7 @@ describe('SimplePromptInput', () => {
render(
<ConfigContext.Provider value={createContextValue()}>
<Prompt
mode={AppModeEnum.CHAT}
mode={'chat'}
promptTemplate="Hello {{existing_var}}"
promptVariables={[{ key: 'existing_var', name: '', type: 'string', required: true }]}
onChange={mockOnChange}
@@ -9,7 +9,6 @@ import { vi } from 'vite-plus/test'
import { toast } from '@/app/components/app/configuration/toast'
import DebugConfigurationContext from '@/context/debug-configuration'
import { renderWithConsoleQuery as render } from '@/test/console/query-data'
import { AppModeEnum } from '@/types/app'
import ConfigVar, { ADD_EXTERNAL_DATA_TOOL } from '../index'
const toastErrorSpy = vi.spyOn(toast, 'error').mockReturnValue('toast-error')
@@ -69,7 +68,7 @@ type DebugConfigurationState = React.ComponentProps<
>['value']
const defaultDebugConfigValue = {
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
dataSets: [],
modelConfig: {
model_id: 'test-model',
@@ -399,7 +398,7 @@ describe('ConfigVar', () => {
onPromptVariablesChange,
},
{
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
dataSets: [{ id: 'dataset-1' } as DebugConfigurationState['dataSets'][number]],
},
)
@@ -10,7 +10,6 @@ import {
seedAppDetail,
} from '@/test/console/query-data'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import ConfigModal from '../index'
vi.mock('@/next/navigation', async (importOriginal) => ({
@@ -116,12 +115,12 @@ const createPayload = (overrides: Partial<InputVar> = {}): InputVar => ({
const renderConfigModal = (payload: InputVar = createPayload()) => {
const queryClient = createConsoleQueryClient()
seedAppDetail(queryClient, createAppDetailFixture({ id: 'app-1', mode: AppModeEnum.CHAT }))
seedAppDetail(queryClient, createAppDetailFixture({ id: 'app-1', mode: 'chat' as const }))
return renderWithConsoleQuery(
<DebugConfigurationContext.Provider
value={
{
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
dataSets: [],
modelConfig: { model_id: 'model-1' },
} as any
@@ -11,7 +11,7 @@ import {
seedAppDetail,
} from '@/test/console/query-data'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { AppModeEnum, TransferMethod } from '@/types/app'
import { TransferMethod } from '@/types/app'
import ConfigModal from '../index'
vi.mock('@/next/navigation', async (importOriginal) => ({
@@ -37,7 +37,7 @@ vi.mock('@monaco-editor/react', async () => ({
const render = (ui: React.ReactElement) => {
const queryClient = createConsoleQueryClient()
seedAppDetail(queryClient, createAppDetailFixture({ id: 'app-1', mode: AppModeEnum.CHAT }))
seedAppDetail(queryClient, createAppDetailFixture({ id: 'app-1', mode: 'chat' as const }))
return renderWithConsoleQuery(ui, { queryClient })
}
@@ -1,9 +1,10 @@
import type { AppMode } from '@dify/contracts/api/console/apps/types.gen'
import type { ModelConfig, PromptVariable } from '@/models/debug'
import type { ToolItem } from '@/types/app'
import { render, screen } from '@testing-library/react'
import * as React from 'react'
import * as useContextSelector from 'use-context-selector'
import { AgentStrategy, AppModeEnum, ModelModeType } from '@/types/app'
import { AgentStrategy, ModelModeType } from '@/types/app'
import Config from '../index'
vi.mock('use-context-selector', async (importOriginal) => {
@@ -64,7 +65,7 @@ vi.mock('../../config-prompt/conversation-history/history-panel', () => ({
}))
type MockContext = {
mode: AppModeEnum
mode: AppMode
isAdvancedMode: boolean
modelModeType: ModelModeType
isAgent: boolean
@@ -118,7 +119,7 @@ const createModelConfig = (overrides: Partial<ModelConfig> = {}): ModelConfig =>
})
const createContextValue = (overrides: Partial<MockContext> = {}): MockContext => ({
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
isAdvancedMode: false,
modelModeType: ModelModeType.chat,
isAgent: false,
@@ -177,7 +178,7 @@ describe('Config - Rendering', () => {
const showHistoryModal = vi.fn()
renderConfig({
isAdvancedMode: true,
mode: AppModeEnum.ADVANCED_CHAT,
mode: 'advanced-chat' as const,
modelModeType: ModelModeType.completion,
hasSetBlockStatus: {
context: false,
@@ -1,7 +1,6 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { page } from 'vite-plus/test/browser'
import { render } from 'vitest-browser-react'
import { AppModeEnum } from '@/types/app'
import GetAutomaticRes from '../get-automatic-res'
vi.mock('react-i18next', async () => {
@@ -71,7 +70,7 @@ describe('Prompt generator reflow', () => {
const screen = await render(
<QueryClientProvider client={queryClient}>
<GetAutomaticRes
mode={AppModeEnum.CHAT}
modelMode="chat"
isShow
onClose={vi.fn()}
onFinished={vi.fn()}
@@ -1,5 +1,4 @@
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'
import { AppModeEnum } from '@/types/app'
import GetAutomaticRes from '../get-automatic-res'
const mockGenerateBasicAppFirstTimeRule = vi.fn()
@@ -53,7 +52,9 @@ vi.mock(
}) => (
<div>
<button
onClick={() => setModel({ modelId: 'gpt-4o-mini', provider: 'openai', mode: 'chat' })}
onClick={() =>
setModel({ modelId: 'gpt-4o-mini', provider: 'openai', mode: 'chat' as const })
}
>
change-model
</button>
@@ -121,7 +122,7 @@ describe('GetAutomaticRes', () => {
it('should apply a basic suggestion and persist model updates', async () => {
render(
<GetAutomaticRes
mode={AppModeEnum.CHAT}
modelMode="chat"
isShow
onClose={mockOnClose}
onFinished={mockOnFinished}
@@ -152,7 +153,7 @@ describe('GetAutomaticRes', () => {
render(
<GetAutomaticRes
mode={AppModeEnum.CHAT}
modelMode="chat"
isShow
onClose={mockOnClose}
onFinished={mockOnFinished}
@@ -176,7 +177,7 @@ describe('GetAutomaticRes', () => {
it('should block generation when instruction is empty', () => {
render(
<GetAutomaticRes
mode={AppModeEnum.CHAT}
modelMode="chat"
isShow
onClose={mockOnClose}
onFinished={mockOnFinished}
@@ -203,7 +204,7 @@ describe('GetAutomaticRes', () => {
render(
<GetAutomaticRes
mode={AppModeEnum.CHAT}
modelMode="chat"
isShow
onClose={mockOnClose}
onFinished={mockOnFinished}
@@ -254,7 +255,7 @@ describe('GetAutomaticRes', () => {
render(
<GetAutomaticRes
mode={AppModeEnum.CHAT}
modelMode="chat"
isShow
onClose={mockOnClose}
onFinished={mockOnFinished}
@@ -291,7 +292,7 @@ describe('GetAutomaticRes', () => {
render(
<GetAutomaticRes
mode={AppModeEnum.ADVANCED_CHAT}
modelMode="chat"
isShow
onClose={mockOnClose}
onFinished={mockOnFinished}
@@ -315,6 +316,7 @@ describe('GetAutomaticRes', () => {
current: 'current prompt',
instruction: 'workflow instruction',
ideal_output: 'ideal output',
model_config: expect.objectContaining({ mode: 'chat' }),
}),
)
})
@@ -1,6 +1,5 @@
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
import { AppModeEnum } from '@/types/app'
import GetCodeGeneratorResModal from '../get-code-generator-res'
const mockGenerateRule = vi.fn()
@@ -52,7 +51,9 @@ vi.mock(
}) => (
<div>
<button
onClick={() => setModel({ modelId: 'gpt-4o-mini', provider: 'openai', mode: 'chat' })}
onClick={() =>
setModel({ modelId: 'gpt-4o-mini', provider: 'openai', mode: 'chat' as const })
}
>
change-model
</button>
@@ -119,7 +120,7 @@ describe('GetCodeGeneratorResModal', () => {
flowId="flow-1"
nodeId="node-1"
currentCode="print(1)"
mode={AppModeEnum.CHAT}
modelMode="chat"
isShow
codeLanguages={CodeLanguage.python3}
onClose={mockOnClose}
@@ -146,7 +147,7 @@ describe('GetCodeGeneratorResModal', () => {
flowId="flow-1"
nodeId="node-1"
currentCode="print(1)"
mode={AppModeEnum.CHAT}
modelMode="chat"
isShow
codeLanguages={CodeLanguage.python3}
onClose={mockOnClose}
@@ -173,7 +174,7 @@ describe('GetCodeGeneratorResModal', () => {
flowId="flow-1"
nodeId="node-1"
currentCode="print(1)"
mode={AppModeEnum.CHAT}
modelMode="chat"
isShow
codeLanguages={CodeLanguage.python3}
onClose={mockOnClose}
@@ -200,7 +201,7 @@ describe('GetCodeGeneratorResModal', () => {
flowId="flow-1"
nodeId="node-1"
currentCode="print(1)"
mode={AppModeEnum.CHAT}
modelMode="completion"
isShow
codeLanguages={CodeLanguage.python3}
onClose={mockOnClose}
@@ -221,6 +222,7 @@ describe('GetCodeGeneratorResModal', () => {
instruction: 'code instruction',
ideal_output: 'code output',
language: 'python',
model_config: expect.objectContaining({ mode: 'completion' }),
}),
)
})
@@ -255,7 +257,7 @@ describe('GetCodeGeneratorResModal', () => {
flowId="flow-1"
nodeId="node-1"
currentCode="print(1)"
mode={AppModeEnum.CHAT}
modelMode="chat"
isShow
codeLanguages={CodeLanguage.python3}
onClose={mockOnClose}
@@ -295,7 +297,7 @@ describe('GetCodeGeneratorResModal', () => {
flowId="flow-1"
nodeId="node-1"
currentCode="print(1)"
mode={AppModeEnum.CHAT}
modelMode="chat"
isShow
codeLanguages={CodeLanguage.javascript}
onClose={mockOnClose}
@@ -11,7 +11,7 @@ import { getSelectedDatasetsMode } from '@/app/components/workflow/nodes/knowled
import { DatasetPermission, DataSourceType } from '@/models/datasets'
import { createConsoleQueryWrapper } from '@/test/console/query-data'
import { render as renderWithConsoleState } from '@/test/console/render'
import { AppModeEnum, ModelModeType, RETRIEVE_TYPE } from '@/types/app'
import { ModelModeType, RETRIEVE_TYPE } from '@/types/app'
import { DatasetACLPermission, getDatasetACLCapabilities } from '@/utils/permission'
import DatasetConfig from '../index'
@@ -185,7 +185,7 @@ vi.mock(
// Mock context
const mockConfigContext: any = {
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
modelModeType: ModelModeType.chat,
isAgent: false,
dataSets: [],
@@ -516,7 +516,7 @@ describe('DatasetConfig', () => {
it('should show context variable selector in completion mode with datasets', () => {
const dataset = createMockDataset()
renderDatasetConfig({
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
dataSets: [dataset],
modelConfig: {
configs: {
@@ -537,7 +537,7 @@ describe('DatasetConfig', () => {
it('should not show context variable selector in chat mode', () => {
const dataset = createMockDataset()
renderDatasetConfig({
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
dataSets: [dataset],
modelConfig: {
configs: {
@@ -555,7 +555,7 @@ describe('DatasetConfig', () => {
const user = userEvent.setup()
const dataset = createMockDataset()
renderDatasetConfig({
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
dataSets: [dataset],
modelConfig: {
configs: {
@@ -877,7 +877,7 @@ describe('DatasetConfig', () => {
// Context var is only shown when datasets exist AND there are prompt_variables
// Test with no datasets to ensure context var is not shown
renderDatasetConfig({
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
dataSets: [],
modelConfig: {
configs: {
@@ -960,7 +960,7 @@ describe('DatasetConfig', () => {
metadata_model_config: {
provider: 'openai',
name: 'gpt-3.5-turbo',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
completion_params: { temperature: 0.7 },
},
},
@@ -976,7 +976,7 @@ describe('DatasetConfig', () => {
metadata_model_config: {
provider: 'openai',
name: 'gpt-4o-mini',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
completion_params: { temperature: 0.7 },
},
}),
@@ -993,7 +993,7 @@ describe('DatasetConfig', () => {
metadata_model_config: {
provider: 'openai',
name: 'gpt-3.5-turbo',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
completion_params: { temperature: 0.5, max_tokens: 100 },
},
},
@@ -6,7 +6,7 @@ import * as React from 'react'
import Log from '@/app/components/base/chat/chat/log'
import { ModelFeatureEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import ConfigContext from '@/context/debug-configuration'
import { AppModeEnum, ModelModeType, TransferMethod } from '@/types/app'
import { ModelModeType, TransferMethod } from '@/types/app'
import Debug from '../index'
import { APP_CHAT_WITH_MULTIPLE_MODEL, APP_CHAT_WITH_MULTIPLE_MODEL_RESTART } from '../types'
@@ -345,7 +345,7 @@ const createContextValue = (overrides: Partial<DebugContextValue> = {}): DebugCo
appId: 'app-id',
onOpenFeatures: vi.fn(),
isTrailFinished: false,
mode: AppModeEnum.CHAT,
mode: 'chat',
modelModeType: ModelModeType.chat,
promptMode: 'simple' as DebugContextValue['promptMode'],
setPromptMode: vi.fn(),
@@ -676,7 +676,7 @@ describe('Debug', () => {
const { notify } = renderDebug({
contextValue: {
isAdvancedMode: true,
mode: AppModeEnum.CHAT,
mode: 'chat',
modelModeType: ModelModeType.completion,
hasSetBlockStatus: { context: false, history: false, query: true },
},
@@ -693,7 +693,7 @@ describe('Debug', () => {
const { notify } = renderDebug({
contextValue: {
isAdvancedMode: true,
mode: AppModeEnum.CHAT,
mode: 'chat',
modelModeType: ModelModeType.completion,
hasSetBlockStatus: { context: false, history: true, query: false },
},
@@ -711,7 +711,7 @@ describe('Debug', () => {
it('should render prompt value panel and no-result placeholder', () => {
renderDebug({
contextValue: {
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
},
})
@@ -722,7 +722,7 @@ describe('Debug', () => {
it('should notify when required input is missing', () => {
const { notify } = renderDebug({
contextValue: {
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
inputs: {},
modelConfig: {
...createContextValue().modelConfig,
@@ -752,7 +752,7 @@ describe('Debug', () => {
it('should notify when local file upload is still pending', () => {
const { notify } = renderDebug({
contextValue: {
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
modelConfig: {
...createContextValue().modelConfig,
configs: {
@@ -776,7 +776,7 @@ describe('Debug', () => {
it('should show cannot-query-dataset warning when dataset context variable is missing', () => {
renderDebug({
contextValue: {
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
dataSets: [{ id: 'dataset-1' }] as DebugContextValue['dataSets'],
hasSetContextVar: false,
modelConfig: {
@@ -800,7 +800,7 @@ describe('Debug', () => {
const user = userEvent.setup()
const { notify } = renderDebug({
contextValue: {
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
isAdvancedMode: true,
chatPromptConfig: { prompt: [{ text: 'Missing role' }] },
},
@@ -817,7 +817,7 @@ describe('Debug', () => {
const user = userEvent.setup()
const file = { ...mockState.mockFeaturesState.file, enabled: true, number_limits: 1.5 }
mockState.mockFeaturesState.file = file
const { notify } = renderDebug({ contextValue: { mode: AppModeEnum.COMPLETION } })
const { notify } = renderDebug({ contextValue: { mode: 'completion' as const } })
await user.click(screen.getByRole('button', { name: 'Send' }))
@@ -870,7 +870,7 @@ describe('Debug', () => {
renderDebug({
contextValue: {
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
promptMode: 'simple' as DebugContextValue['promptMode'],
textToSpeechConfig: { enabled: true, voice: 'alloy', language: 'en' },
modelConfig: {
@@ -924,7 +924,7 @@ describe('Debug', () => {
mockState.mockSendCompletionMessage.mockImplementation(() => undefined)
const { notify } = renderDebug({
contextValue: {
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
modelConfig: {
...createContextValue().modelConfig,
configs: {
@@ -975,7 +975,7 @@ describe('Debug', () => {
renderDebug({
contextValue: {
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
modelConfig: {
...createContextValue().modelConfig,
configs: {
@@ -999,7 +999,7 @@ describe('Debug', () => {
handlers.onData('Completion answer', true, { messageId: 'completion-1' })
handlers.onCompleted()
})
renderDebug({ contextValue: { mode: AppModeEnum.COMPLETION } })
renderDebug({ contextValue: { mode: 'completion' as const } })
await user.click(screen.getByRole('button', { name: 'Send' }))
await user.click(
within(screen.getByRole('region', { name: 'First answer' })).getByRole('button', {
@@ -1080,7 +1080,7 @@ describe('Debug', () => {
it('should emit completion event in multiple-model completion mode', () => {
renderDebug({
contextValue: {
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
modelConfig: {
...createContextValue().modelConfig,
configs: {
@@ -1166,7 +1166,7 @@ describe('Debug', () => {
it('should update feature store according to multiple-model vision support', () => {
renderDebug({
contextValue: {
mode: AppModeEnum.CHAT,
mode: 'chat',
},
props: {
debugWithMultipleModel: true,
@@ -3,7 +3,6 @@ import type { ModelAndParameter } from '../../types'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { ModelStatusEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { AppModeEnum } from '@/types/app'
import DebugItem from '../debug-item'
const mockUseDebugConfigurationContext = vi.fn()
@@ -98,7 +97,7 @@ describe('DebugItem', () => {
capturedModelParameterTriggerProps = null
mockUseDebugConfigurationContext.mockReturnValue({
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
})
mockUseDebugWithMultipleModelContext.mockReturnValue({
@@ -156,7 +155,7 @@ describe('DebugItem', () => {
describe('ChatItem rendering', () => {
it('should render ChatItem in CHAT mode with active model', () => {
mockUseDebugConfigurationContext.mockReturnValue({ mode: AppModeEnum.CHAT })
mockUseDebugConfigurationContext.mockReturnValue({ mode: 'chat' as const })
mockModelListQuery.mockReturnValue({
data: createTextGenerationModelList([
{ provider: 'openai', model: 'gpt-3.5-turbo', status: ModelStatusEnum.active },
@@ -170,7 +169,7 @@ describe('DebugItem', () => {
})
it('should render ChatItem in AGENT_CHAT mode with active model', () => {
mockUseDebugConfigurationContext.mockReturnValue({ mode: AppModeEnum.AGENT_CHAT })
mockUseDebugConfigurationContext.mockReturnValue({ mode: 'agent-chat' as const })
mockModelListQuery.mockReturnValue({
data: createTextGenerationModelList([
{ provider: 'openai', model: 'gpt-3.5-turbo', status: ModelStatusEnum.active },
@@ -183,7 +182,7 @@ describe('DebugItem', () => {
})
it('should not render ChatItem when model is not active', () => {
mockUseDebugConfigurationContext.mockReturnValue({ mode: AppModeEnum.CHAT })
mockUseDebugConfigurationContext.mockReturnValue({ mode: 'chat' as const })
mockModelListQuery.mockReturnValue({
data: createTextGenerationModelList([
{ provider: 'openai', model: 'gpt-3.5-turbo', status: ModelStatusEnum.disabled },
@@ -196,7 +195,7 @@ describe('DebugItem', () => {
})
it('should not render ChatItem when provider not found', () => {
mockUseDebugConfigurationContext.mockReturnValue({ mode: AppModeEnum.CHAT })
mockUseDebugConfigurationContext.mockReturnValue({ mode: 'chat' as const })
mockModelListQuery.mockReturnValue({
data: createTextGenerationModelList([
{ provider: 'anthropic', model: 'claude-3', status: ModelStatusEnum.active },
@@ -209,7 +208,7 @@ describe('DebugItem', () => {
})
it('should not render ChatItem when model not found', () => {
mockUseDebugConfigurationContext.mockReturnValue({ mode: AppModeEnum.CHAT })
mockUseDebugConfigurationContext.mockReturnValue({ mode: 'chat' as const })
mockModelListQuery.mockReturnValue({
data: createTextGenerationModelList([
{ provider: 'openai', model: 'gpt-4', status: ModelStatusEnum.active },
@@ -224,7 +223,7 @@ describe('DebugItem', () => {
describe('TextGenerationItem rendering', () => {
it('should render TextGenerationItem in COMPLETION mode with active model', () => {
mockUseDebugConfigurationContext.mockReturnValue({ mode: AppModeEnum.COMPLETION })
mockUseDebugConfigurationContext.mockReturnValue({ mode: 'completion' as const })
mockModelListQuery.mockReturnValue({
data: createTextGenerationModelList([
{ provider: 'openai', model: 'gpt-3.5-turbo', status: ModelStatusEnum.active },
@@ -238,7 +237,7 @@ describe('DebugItem', () => {
})
it('should not render TextGenerationItem when provider is not found', () => {
mockUseDebugConfigurationContext.mockReturnValue({ mode: AppModeEnum.COMPLETION })
mockUseDebugConfigurationContext.mockReturnValue({ mode: 'completion' as const })
mockModelListQuery.mockReturnValue({
data: createTextGenerationModelList([
{ provider: 'anthropic', model: 'claude-3', status: ModelStatusEnum.active },
@@ -503,7 +502,7 @@ describe('DebugItem', () => {
})
it('should handle model with quotaExceeded status', () => {
mockUseDebugConfigurationContext.mockReturnValue({ mode: AppModeEnum.CHAT })
mockUseDebugConfigurationContext.mockReturnValue({ mode: 'chat' as const })
mockModelListQuery.mockReturnValue({
data: createTextGenerationModelList([
{ provider: 'anthropic', model: 'not-matching', status: ModelStatusEnum.quotaExceeded },
@@ -1,3 +1,4 @@
import type { AppMode } from '@dify/contracts/api/console/apps/types.gen'
import type { CSSProperties } from 'react'
import type { ModelAndParameter } from '../../types'
import type { DebugWithMultipleModelContextType } from '../context'
@@ -12,7 +13,7 @@ import {
DEFAULT_CHAT_PROMPT_CONFIG,
DEFAULT_COMPLETION_PROMPT_CONFIG,
} from '@/config'
import { AppModeEnum, ModelModeType, Resolution, TransferMethod } from '@/types/app'
import { ModelModeType, Resolution, TransferMethod } from '@/types/app'
import { APP_CHAT_WITH_MULTIPLE_MODEL } from '../../types'
import DebugWithMultipleModel from '../index'
@@ -161,7 +162,7 @@ const createModelConfig = (promptVariables: PromptVariableWithMeta[] = []): Mode
type DebugConfiguration = {
onOpenFeatures: () => void
mode: AppModeEnum
mode: AppMode
inputs: Inputs
modelConfig: ModelConfig
readonly: boolean
@@ -172,7 +173,7 @@ const createDebugConfiguration = (
overrides: Partial<DebugConfiguration> = {},
): DebugConfiguration => ({
onOpenFeatures: vi.fn(),
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
inputs: {},
modelConfig: createModelConfig(),
readonly: false,
@@ -536,7 +537,7 @@ describe('DebugWithMultipleModel', () => {
// Arrange
mockUseDebugConfigurationContext.mockReturnValue(
createDebugConfiguration({
mode: AppModeEnum.AGENT_CHAT,
mode: 'agent-chat' as const,
}),
)
@@ -552,7 +553,7 @@ describe('DebugWithMultipleModel', () => {
// Arrange
mockUseDebugConfigurationContext.mockReturnValue(
createDebugConfiguration({
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
}),
)
const multipleModelConfigs = [createModelAndParameter()]
@@ -842,7 +843,7 @@ describe('DebugWithMultipleModel', () => {
it('should set full height when chat input is hidden', () => {
mockUseDebugConfigurationContext.mockReturnValue(
createDebugConfiguration({
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
}),
)
@@ -10,7 +10,7 @@ import { CollectionType } from '@/app/components/tools/types'
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
import { PromptMode } from '@/models/debug'
import { renderWithAccountProfile as render } from '@/test/console/account-profile'
import { AgentStrategy, AppModeEnum, ModelModeType, Resolution, TransferMethod } from '@/types/app'
import { AgentStrategy, ModelModeType, Resolution, TransferMethod } from '@/types/app'
import DebugWithSingleModel from '../index'
// ============================================================================
@@ -135,7 +135,7 @@ const mockDebugConfigContext = {
appId: 'test-app-id',
onOpenFeatures: vi.fn(),
isTrailFinished: false,
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
modelModeType: ModelModeType.chat,
promptMode: PromptMode.simple,
setPromptMode: vi.fn(),
@@ -7,7 +7,7 @@ import {
} from '@/app/components/base/prompt-editor/constants'
import { PromptMode, PromptRole } from '@/models/debug'
import { fetchPromptTemplate } from '@/service/debug'
import { AppModeEnum, ModelModeType } from '@/types/app'
import { ModelModeType } from '@/types/app'
import useAdvancedPromptConfig from '../use-advanced-prompt-config'
vi.mock('@/service/debug', () => ({
@@ -25,7 +25,7 @@ describe('useAdvancedPromptConfig', () => {
const handleUserChangedPrompt = vi.fn()
const { result } = renderHook(() =>
useAdvancedPromptConfig({
appMode: AppModeEnum.CHAT,
appMode: 'chat' as const,
modelModeType: ModelModeType.chat,
modelName: 'gpt-4o',
promptMode: PromptMode.advanced,
@@ -55,7 +55,7 @@ describe('useAdvancedPromptConfig', () => {
it('should derive simple prompt block status from the pre-prompt', () => {
const { result } = renderHook(() =>
useAdvancedPromptConfig({
appMode: AppModeEnum.COMPLETION,
appMode: 'completion' as const,
modelModeType: ModelModeType.completion,
modelName: 'gpt-4o',
promptMode: PromptMode.simple,
@@ -79,7 +79,7 @@ describe('useAdvancedPromptConfig', () => {
const handleUserChangedPrompt = vi.fn()
const { result } = renderHook(() =>
useAdvancedPromptConfig({
appMode: AppModeEnum.CHAT,
appMode: 'chat' as const,
modelModeType: ModelModeType.chat,
modelName: 'gpt-4o',
promptMode: PromptMode.simple,
@@ -118,7 +118,7 @@ describe('useAdvancedPromptConfig', () => {
const { result } = renderHook(() =>
useAdvancedPromptConfig({
appMode: AppModeEnum.COMPLETION,
appMode: 'completion' as const,
modelModeType: ModelModeType.completion,
modelName: 'gpt-4o',
promptMode: PromptMode.simple,
@@ -137,7 +137,7 @@ describe('useAdvancedPromptConfig', () => {
await waitFor(() => {
expect(mockFetchPromptTemplate).toHaveBeenCalledWith({
appMode: AppModeEnum.COMPLETION,
appMode: 'completion' as const,
mode: ModelModeType.completion,
modelName: 'gpt-4o',
hasSetDataSet: true,
@@ -168,7 +168,7 @@ describe('useAdvancedPromptConfig', () => {
const { result } = renderHook(() =>
useAdvancedPromptConfig({
appMode: AppModeEnum.CHAT,
appMode: 'chat' as const,
modelModeType: ModelModeType.chat,
modelName: 'gpt-4o',
promptMode: PromptMode.simple,
@@ -211,7 +211,7 @@ describe('useAdvancedPromptConfig', () => {
const { result } = renderHook(() =>
useAdvancedPromptConfig({
appMode: AppModeEnum.CHAT,
appMode: 'chat' as const,
modelModeType: ModelModeType.completion,
modelName: 'gpt-4o',
promptMode: PromptMode.advanced,
@@ -268,7 +268,7 @@ describe('useAdvancedPromptConfig', () => {
const { result } = renderHook(() =>
useAdvancedPromptConfig({
appMode: AppModeEnum.COMPLETION,
appMode: 'completion' as const,
modelModeType: ModelModeType.completion,
modelName: 'gpt-4o',
promptMode: PromptMode.advanced,
@@ -6,7 +6,6 @@ import { createAppDetailFixture, createAppModelConfigFixture } from '@/test/fixt
import { withSelectorKey } from '@/test/i18n-mock'
import {
AgentStrategy,
AppModeEnum,
ModelModeType,
Resolution,
RETRIEVE_TYPE,
@@ -133,7 +132,7 @@ describe('useConfiguration utils', () => {
mockFetchCollectionList.mockResolvedValue([])
mockFetchAppDetailDirect.mockResolvedValue(
createAppDetailFixture({
mode: AppModeEnum.AGENT_CHAT,
mode: 'agent-chat' as const,
model_config: createAppModelConfigFixture({
model: {
provider: 'langgenius/openai/openai',
@@ -277,7 +276,7 @@ describe('useConfiguration utils', () => {
top_k: 4,
} as any,
deletedTools: [{ type: 'tool', provider_id: 'tool-1', tool_name: 'search' }],
mode: AppModeEnum.AGENT_CHAT,
mode: 'agent-chat' as const,
nextDataSets: [{ id: 'dataset-1' }] as any,
})
@@ -336,7 +335,7 @@ describe('useConfiguration utils', () => {
datasets: { datasets: [] },
retrieval_model: RETRIEVE_TYPE.multiWay,
} as any,
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
nextDataSets: [],
})
@@ -369,7 +368,7 @@ describe('useConfiguration utils', () => {
datasets: { datasets: [] },
retrieval_model: RETRIEVE_TYPE.multiWay,
} as any,
mode: AppModeEnum.CHAT,
mode: 'chat',
nextDataSets: [],
})
@@ -496,7 +495,7 @@ describe('useConfiguration utils', () => {
])
mockFetchAppDetailDirect.mockResolvedValue({
deleted_tools: [],
mode: AppModeEnum.CHAT,
mode: 'chat',
model_config: createAppModelConfigFixture({
prompt_type: 'advanced',
chat_prompt_config: {
@@ -581,7 +580,7 @@ describe('useConfiguration utils', () => {
})
mockFetchAppDetailDirect.mockResolvedValue({
deleted_tools: [],
mode: AppModeEnum.AGENT_CHAT,
mode: 'agent-chat' as const,
model_config: createAppModelConfigFixture({
prompt_type: 'simple',
chat_prompt_config: { prompt: [] },
@@ -642,7 +641,7 @@ describe('useConfiguration utils', () => {
mockFetchCollectionList.mockResolvedValue([])
mockFetchAppDetailDirect.mockResolvedValue({
deleted_tools: [],
mode: AppModeEnum.CHAT,
mode: 'chat',
model_config: createAppModelConfigFixture({
prompt_type: 'simple',
chat_prompt_config: { prompt: [] },
@@ -839,7 +838,7 @@ describe('useConfiguration utils', () => {
backendModelConfig,
collectionList: [],
datasetConfigs: buildConfigurationDatasetConfigs({ backendModelConfig, nextDataSets: [] }),
mode: AppModeEnum.CHAT,
mode: 'chat',
nextDataSets: [],
})
const setPublishedConfig = vi.fn()
@@ -862,7 +861,7 @@ describe('useConfiguration utils', () => {
hasSetBlockStatus: { history: true, query: true },
isAdvancedMode: true,
isFunctionCall: false,
mode: AppModeEnum.CHAT,
mode: 'chat',
modelConfig: config.modelConfig,
promptEmpty: false,
promptMode: 'advanced',
@@ -911,7 +910,7 @@ describe('useConfiguration utils', () => {
},
isAdvancedMode: true,
isFunctionCall: true,
mode: AppModeEnum.CHAT,
mode: 'chat',
modelConfig: {
agentConfig: {
enabled: true,
@@ -1019,7 +1018,7 @@ describe('useConfiguration utils', () => {
},
isAdvancedMode: true,
isFunctionCall: false,
mode: AppModeEnum.CHAT,
mode: 'chat',
modelConfig: {
configs: {
prompt_template: 'hello',
@@ -1080,7 +1079,7 @@ describe('useConfiguration utils', () => {
handleSetVisionConfig,
isAdvancedMode: true,
migrateToDefaultPrompt,
mode: AppModeEnum.CHAT,
mode: 'chat',
modelConfig: {
model_id: 'gpt-4o-mini',
provider: 'langgenius/openai/openai',
@@ -1137,7 +1136,7 @@ describe('useConfiguration utils', () => {
handleSetVisionConfig,
isAdvancedMode: true,
migrateToDefaultPrompt,
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
modelConfig: {
model_id: 'gpt-4o-mini',
provider: 'langgenius/openai/openai',
@@ -12,7 +12,7 @@ import { seedAppDetail } from '@/test/console/query-data'
import { renderHook as renderHookWithConsoleState } from '@/test/console/render'
import { createAppDetailFixture, createAppModelConfigFixture } from '@/test/fixtures/app'
import { createTestQueryClient } from '@/test/query-client'
import { AppModeEnum, ModelModeType } from '@/types/app'
import { ModelModeType } from '@/types/app'
import { AppACLPermission } from '@/utils/permission'
import { useConfiguration } from '../use-configuration'
@@ -24,7 +24,7 @@ const createRouteAppDetail = (overrides: Partial<AppDetailWithSite> = {}) =>
model_config: createAppModelConfigFixture({
updated_at: 1710000000,
}),
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
permission_keys: mockAppPermissionKeys,
...overrides,
})
@@ -252,7 +252,7 @@ describe('useConfiguration', () => {
vi.mocked(consoleClient.apps.byAppId.modelConfig.post).mockResolvedValue(undefined as never)
mockFetchAppDetailDirect.mockResolvedValue({
deleted_tools: [],
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
model_config: createAppModelConfigFixture({
prompt_type: 'advanced',
chat_prompt_config: {
@@ -675,7 +675,7 @@ describe('useConfiguration', () => {
mockCurrentModelFeatures = ['vision', 'document', 'audio', 'video']
mockFetchAppDetailDirect.mockResolvedValueOnce({
deleted_tools: [],
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
model_config: createAppModelConfigFixture({
prompt_type: 'simple',
chat_prompt_config: { prompt: [] },
@@ -788,7 +788,7 @@ describe('useConfiguration', () => {
})
mockFetchAppDetailDirect.mockResolvedValueOnce({
deleted_tools: [],
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
model_config: createAppModelConfigFixture({
prompt_type: 'simple',
chat_prompt_config: { prompt: [] },
@@ -4,7 +4,7 @@ import userEvent from '@testing-library/user-event'
import * as React from 'react'
import { beforeEach, describe, expect, it, vi } from 'vite-plus/test'
import ConfigContext from '@/context/debug-configuration'
import { AppModeEnum, ModelModeType, Resolution } from '@/types/app'
import { ModelModeType, Resolution } from '@/types/app'
import PromptValuePanel from '../index'
const mockOnOpenFeatures = vi.fn()
@@ -78,7 +78,7 @@ const baseContextValue: any = {
},
setInputs: mockSetInputs,
canTestAndRun: true,
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
isAdvancedMode: false,
completionPromptConfig: {
prompt: { text: 'completion' },
@@ -88,7 +88,7 @@ const baseContextValue: any = {
} as any
const defaultProps: IPromptValuePanelProps = {
appType: AppModeEnum.COMPLETION,
appType: 'completion',
onSend: mockOnSend,
inputs: { textVar: 'initial', boolVar: false },
visionConfig: { enabled: false, number_limits: 0, detail: Resolution.low, transfer_methods: [] },
@@ -142,10 +142,10 @@ describe('PromptValuePanel', () => {
it('disables run when mode is not completion', () => {
renderPanel({
context: {
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
},
props: {
appType: AppModeEnum.CHAT,
appType: 'chat',
},
})
@@ -4,7 +4,6 @@ import { screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { trackEvent } from '@/app/components/base/amplitude'
import { renderWithConsoleQuery } from '@/test/console/query-data'
import { AppModeEnum } from '@/types/app'
import AppCard from '../index'
vi.mock('@/app/components/base/amplitude', () => ({ trackEvent: vi.fn() }))
@@ -16,7 +15,7 @@ const app = {
can_trial: true,
app: {
id: 'app-1',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
icon_type: 'emoji',
icon: '🤖',
icon_background: '#FFEAD5',
@@ -8,7 +8,6 @@ import { useRouter } from '@/next/navigation'
import { renderWithConsoleQuery } from '@/test/console/query-data'
import { mockEmojiData } from '@/test/emoji-picker'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import { getRedirection } from '@/utils/app-redirection'
import { trackCreateApp } from '@/utils/create-app-tracking'
import CreateAppModal from '../index'
@@ -109,7 +108,7 @@ const renderModal = () => {
show
onClose={onClose}
onCreateFromTemplate={onCreateFromTemplate}
defaultAppMode={AppModeEnum.ADVANCED_CHAT}
defaultAppMode={'advanced-chat'}
/>,
)
return { onClose, onCreateFromTemplate }
@@ -138,7 +137,7 @@ describe('CreateAppModal', () => {
it('creates an app and fires callbacks', async () => {
const mockApp = createAppDetailFixture({
mode: AppModeEnum.ADVANCED_CHAT,
mode: 'advanced-chat' as const,
maintainer: 'user-1',
})
mockCreateApp.mockResolvedValue(mockApp)
@@ -155,13 +154,13 @@ describe('CreateAppModal', () => {
icon_type: 'emoji',
icon: '🤖',
icon_background: '#FFEAD5',
mode: AppModeEnum.ADVANCED_CHAT,
mode: 'advanced-chat' as const,
}),
)
expect(mockTrackCreateApp).toHaveBeenCalledWith({
source: 'studio_blank',
appMode: AppModeEnum.ADVANCED_CHAT,
appMode: 'advanced-chat' as const,
})
expect(onClose).toHaveBeenCalledTimes(1)
await waitFor(() =>
@@ -176,7 +175,7 @@ describe('CreateAppModal', () => {
it('waits for create_app tracking before redirecting after blank app creation', async () => {
const mockApp = createAppDetailFixture({
mode: AppModeEnum.ADVANCED_CHAT,
mode: 'advanced-chat' as const,
maintainer: 'user-1',
})
let resolveTracking: (() => void) | undefined
@@ -196,7 +195,7 @@ describe('CreateAppModal', () => {
await waitFor(() => {
expect(mockTrackCreateApp).toHaveBeenCalledWith({
source: 'studio_blank',
appMode: AppModeEnum.ADVANCED_CHAT,
appMode: 'advanced-chat' as const,
})
})
const createButton = screen.getByRole('button', { name: /app\.newApp\.Create/ })
@@ -251,7 +250,7 @@ describe('CreateAppModal', () => {
async ({ platform, modifier }) => {
vi.spyOn(navigator, 'platform', 'get').mockReturnValue(platform)
mockCreateApp.mockResolvedValue(
createAppDetailFixture({ id: 'chat-app', mode: AppModeEnum.CHAT }),
createAppDetailFixture({ id: 'chat-app', mode: 'chat' as const }),
)
renderModal()
@@ -288,7 +287,7 @@ describe('CreateAppModal', () => {
icon_type: 'emoji',
icon: '🤖',
icon_background: '#F3FEE7',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
})
})
},
@@ -302,7 +301,7 @@ describe('CreateAppModal', () => {
async ({ platform, modifier }) => {
vi.spyOn(navigator, 'platform', 'get').mockReturnValue(platform)
mockCreateApp.mockResolvedValue(
createAppDetailFixture({ id: 'reopened-app', mode: AppModeEnum.ADVANCED_CHAT }),
createAppDetailFixture({ id: 'reopened-app', mode: 'advanced-chat' as const }),
)
const onClose = vi.fn()
const modal = (show: boolean) => (
@@ -310,7 +309,7 @@ describe('CreateAppModal', () => {
show={show}
onClose={onClose}
onCreateFromTemplate={vi.fn()}
defaultAppMode={AppModeEnum.ADVANCED_CHAT}
defaultAppMode={'advanced-chat'}
/>
)
const { rerender } = render(modal(true))
@@ -366,7 +365,7 @@ describe('CreateAppModal', () => {
it('should switch between app types before creating a completion app', async () => {
mockCreateApp.mockResolvedValue(
createAppDetailFixture({ id: 'completion-app', mode: AppModeEnum.COMPLETION }),
createAppDetailFixture({ id: 'completion-app', mode: 'completion' as const }),
)
renderModal()
@@ -385,7 +384,7 @@ describe('CreateAppModal', () => {
expect(mockCreateApp).toHaveBeenCalledWith(
expect.objectContaining({
name: 'Completion App',
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
}),
)
})
@@ -416,7 +415,7 @@ describe('CreateAppModal', () => {
expect(mockCreateApp).toHaveBeenCalledTimes(1)
resolveCreate?.(createAppDetailFixture({ id: 'slow-app', mode: AppModeEnum.ADVANCED_CHAT }))
resolveCreate?.(createAppDetailFixture({ id: 'slow-app', mode: 'advanced-chat' as const }))
await waitFor(() => {
expect(mockGetRedirection).toHaveBeenCalledTimes(1)
})
@@ -4,7 +4,6 @@ import userEvent from '@testing-library/user-event'
import { DSLImportMode, DSLImportStatus } from '@/models/app'
import { httpErrorFromResponse } from '@/service/http-error'
import { renderWithConsoleQuery } from '@/test/console/query-data'
import { AppModeEnum } from '@/types/app'
import CreateFromDSLModal from '../index'
import { CreateFromDSLModalTab } from '../types'
@@ -163,7 +162,7 @@ describe('CreateFromDSLModal', () => {
id: 'package-import',
status: 'completed-with-warnings',
app_id: 'agent-app',
app_mode: AppModeEnum.AGENT,
app_mode: 'agent',
warnings: [
{ code: 'agent_skill_missing', path: 'skills.s_000001', message: 'Missing Skill' },
],
@@ -180,7 +179,7 @@ describe('CreateFromDSLModal', () => {
})
expect(mockResolveImportedAppRedirectionTarget).toHaveBeenCalledWith({
id: 'agent-app',
mode: AppModeEnum.AGENT,
mode: 'agent' as const,
permission_keys: undefined,
})
},
@@ -193,7 +192,7 @@ describe('CreateFromDSLModal', () => {
mockImportDSL.mockResolvedValue({
id: 'import',
status: 'completed',
app_mode: AppModeEnum.AGENT,
app_mode: 'agent',
})
render(<CreateFromDSLModal show onClose={vi.fn()} />)
const input = document.querySelector<HTMLInputElement>('input[type="file"]')
@@ -309,7 +308,7 @@ describe('CreateFromDSLModal', () => {
id: 'import-1',
status: DSLImportStatus.COMPLETED,
app_id: 'app-1',
app_mode: AppModeEnum.CHAT,
app_mode: 'chat',
permission_keys: ['app.acl.view_layout'],
})
@@ -336,7 +335,7 @@ describe('CreateFromDSLModal', () => {
})
expect(mockTrackCreateApp).toHaveBeenCalledWith({
source: 'studio_upload',
appMode: AppModeEnum.CHAT,
appMode: 'chat' as const,
})
expect(handleSuccess).toHaveBeenCalledTimes(1)
expect(handleClose).toHaveBeenCalledTimes(1)
@@ -358,7 +357,7 @@ describe('CreateFromDSLModal', () => {
id: 'import-no-permissions',
status: DSLImportStatus.COMPLETED,
app_id: 'app-no-permissions',
app_mode: AppModeEnum.WORKFLOW,
app_mode: 'workflow',
})
render(
@@ -375,7 +374,7 @@ describe('CreateFromDSLModal', () => {
})
expect(mockGetRedirection).toHaveBeenCalledWith(
{ id: 'app-no-permissions', mode: AppModeEnum.WORKFLOW, permission_keys: undefined },
{ id: 'app-no-permissions', mode: 'workflow' as const, permission_keys: undefined },
mockPush,
{
currentUserId: 'user-1',
@@ -391,7 +390,7 @@ describe('CreateFromDSLModal', () => {
id: 'import-2',
status: DSLImportStatus.COMPLETED_WITH_WARNINGS,
app_id: 'app-2',
app_mode: AppModeEnum.AGENT,
app_mode: 'agent',
permission_keys: ['app.acl.view_layout'],
warnings: [
{
@@ -446,7 +445,7 @@ describe('CreateFromDSLModal', () => {
id: 'import-file-in-flight',
status: DSLImportStatus.COMPLETED,
app_id: 'app-1',
app_mode: AppModeEnum.CHAT,
app_mode: 'chat',
permission_keys: ['app.acl.view_layout'],
})
@@ -517,7 +516,7 @@ describe('CreateFromDSLModal', () => {
mockImportDSLConfirm.mockResolvedValue({
status: DSLImportStatus.COMPLETED,
app_id: 'app-3',
app_mode: AppModeEnum.WORKFLOW,
app_mode: 'workflow',
permission_keys: ['app.acl.view_layout'],
})
@@ -548,10 +547,10 @@ describe('CreateFromDSLModal', () => {
})
expect(mockTrackCreateApp).toHaveBeenCalledWith({
source: 'studio_upload',
appMode: AppModeEnum.WORKFLOW,
appMode: 'workflow' as const,
})
expect(mockGetRedirection).toHaveBeenCalledWith(
{ id: 'app-3', mode: AppModeEnum.WORKFLOW, permission_keys: ['app.acl.view_layout'] },
{ id: 'app-3', mode: 'workflow' as const, permission_keys: ['app.acl.view_layout'] },
mockPush,
{
currentUserId: 'user-1',
@@ -569,7 +568,7 @@ describe('CreateFromDSLModal', () => {
const completed = {
status: DSLImportStatus.COMPLETED,
app_id: 'created-app',
app_mode: AppModeEnum.WORKFLOW,
app_mode: 'workflow',
}
mockImportDSL.mockResolvedValue(
confirmed ? { id: 'pending-import', status: DSLImportStatus.PENDING } : completed,
@@ -606,7 +605,7 @@ describe('CreateFromDSLModal', () => {
mockImportDSLConfirm.mockResolvedValue({
status: DSLImportStatus.COMPLETED_WITH_WARNINGS,
app_id: 'agent-app-1',
app_mode: AppModeEnum.AGENT,
app_mode: 'agent',
warnings: [
{
code: 'agent_tool_authorization_required',
@@ -618,7 +617,7 @@ describe('CreateFromDSLModal', () => {
})
mockResolveImportedAppRedirectionTarget.mockResolvedValueOnce({
id: 'agent-app-1',
mode: AppModeEnum.AGENT,
mode: 'agent' as const,
permission_keys: undefined,
bound_agent_id: 'agent-1',
})
@@ -645,13 +644,13 @@ describe('CreateFromDSLModal', () => {
})
expect(mockResolveImportedAppRedirectionTarget).toHaveBeenCalledWith({
id: 'agent-app-1',
mode: AppModeEnum.AGENT,
mode: 'agent' as const,
permission_keys: undefined,
})
expect(mockGetRedirection).toHaveBeenCalledWith(
expect.objectContaining({
id: 'agent-app-1',
mode: AppModeEnum.AGENT,
mode: 'agent' as const,
bound_agent_id: 'agent-1',
}),
mockPush,
@@ -771,7 +770,7 @@ describe('CreateFromDSLModal', () => {
id: 'import-in-flight',
status: DSLImportStatus.COMPLETED,
app_id: 'app-1',
app_mode: AppModeEnum.CHAT,
app_mode: 'chat',
permission_keys: ['app.acl.view_layout'],
})
})
@@ -834,7 +833,7 @@ describe('CreateFromDSLModal', () => {
id: 'import-confirming',
status: DSLImportStatus.COMPLETED,
app_id: 'app-confirming',
app_mode: AppModeEnum.WORKFLOW,
app_mode: 'workflow',
})
})
})
@@ -882,7 +881,7 @@ describe('CreateFromDSLModal', () => {
mockImportDSL.mockResolvedValue({
id: 'package-import',
status: 'completed',
app_mode: AppModeEnum.AGENT,
app_mode: 'agent',
})
render(<CreateFromDSLModal show onClose={vi.fn()} />)
expect(screen.getByText('apps-full')).toBeInTheDocument()
@@ -918,7 +917,7 @@ describe('CreateFromDSLModal', () => {
mockImportDSL.mockResolvedValue({
id: 'package-import',
status: 'completed',
app_mode: AppModeEnum.AGENT,
app_mode: 'agent',
})
render(<CreateFromDSLModal show onClose={vi.fn()} />)
expect(
@@ -33,7 +33,6 @@ import {
seedAppDetail,
} from '@/test/console/query-data'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import { AppACLPermission } from '@/utils/permission'
import AppDeploy from '..'
import { EnvironmentTable } from '../environment-table'
@@ -745,7 +744,7 @@ function render(
queryClient,
createAppDetailFixture({
...mockBuiltInEnvironment.appDetail,
mode: AppModeEnum.WORKFLOW,
mode: 'workflow',
permission_keys: appPermissionKeys,
}),
)
@@ -6,7 +6,6 @@ import { PageType } from '@/app/components/base/features/new-feature-panel/annot
import { consoleQuery } from '@/service/console'
import { createConsoleQueryClient, renderWithConsoleQuery } from '@/test/console/query-data'
import { createAppDetailFixture, createAppSiteFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import LogAnnotation from '../index'
vi.mock('@/app/components/app/annotation', () => ({
@@ -75,7 +74,7 @@ describe('LogAnnotation', () => {
it('should render log content without the old page tabs', () => {
// Arrange
appDetail = createMockApp({ mode: AppModeEnum.CHAT })
appDetail = createMockApp({ mode: 'chat' })
// Act
render(<LogAnnotation appId="app-123" pageType={PageType.log} />)
@@ -88,7 +87,7 @@ describe('LogAnnotation', () => {
it('should render completion logs without the old page tabs', () => {
// Arrange
appDetail = createMockApp({ mode: AppModeEnum.COMPLETION })
appDetail = createMockApp({ mode: 'completion' as const })
// Act
render(<LogAnnotation appId="app-123" pageType={PageType.log} />)
@@ -101,7 +100,7 @@ describe('LogAnnotation', () => {
it('should hide tabs and render workflow log in workflow mode', () => {
// Arrange
appDetail = createMockApp({ mode: AppModeEnum.WORKFLOW })
appDetail = createMockApp({ mode: 'workflow' as const })
// Act
render(<LogAnnotation appId="app-123" pageType={PageType.log} />)
@@ -116,7 +115,7 @@ describe('LogAnnotation', () => {
describe('Props', () => {
it('should render log content when page type is log', () => {
// Arrange
appDetail = createMockApp({ mode: AppModeEnum.CHAT })
appDetail = createMockApp({ mode: 'chat' })
// Act
render(<LogAnnotation appId="app-123" pageType={PageType.log} />)
@@ -128,7 +127,7 @@ describe('LogAnnotation', () => {
it('should render annotation content when page type is annotation', () => {
// Arrange
appDetail = createMockApp({ mode: AppModeEnum.CHAT })
appDetail = createMockApp({ mode: 'chat' })
// Act
render(<LogAnnotation appId="app-123" pageType={PageType.annotation} />)
@@ -1,8 +1,7 @@
import type { AppDetailWithSite } from '@dify/contracts/api/console/apps/types.gen'
import type { AppDetailWithSite, AppMode } from '@dify/contracts/api/console/apps/types.gen'
import { screen } from '@testing-library/react'
import { renderWithConsoleQuery as render } from '@/test/console/query-data'
import { createAppDetailFixture, createAppSiteFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import EmptyElement from '../empty-element'
vi.mock('react-i18next', async () => {
@@ -39,7 +38,7 @@ vi.mock('@/utils/var', () => ({
}))
describe('EmptyElement', () => {
const createMockAppDetail = (mode: AppModeEnum) =>
const createMockAppDetail = (mode: AppMode) =>
createAppDetailFixture({
id: 'test-app-id',
name: 'Test App',
@@ -59,14 +58,14 @@ describe('EmptyElement', () => {
describe('Rendering', () => {
it('should render empty element with title', () => {
const appDetail = createMockAppDetail(AppModeEnum.CHAT)
const appDetail = createMockAppDetail('chat')
render(<EmptyElement appDetail={appDetail} />)
expect(screen.getByText('table.empty.element.title')).toBeInTheDocument()
})
it('should render Trans component with i18n key', () => {
const appDetail = createMockAppDetail(AppModeEnum.CHAT)
const appDetail = createMockAppDetail('chat')
render(<EmptyElement appDetail={appDetail} />)
expect(screen.getByText('table.empty.element.content', { exact: false })).toBeInTheDocument()
@@ -75,7 +74,7 @@ describe('EmptyElement', () => {
describe('App Mode Handling', () => {
it('should use CHAT mode for chat apps', () => {
const appDetail = createMockAppDetail(AppModeEnum.CHAT)
const appDetail = createMockAppDetail('chat')
render(<EmptyElement appDetail={appDetail} />)
const link = screen.getAllByRole('link')[0]
@@ -83,7 +82,7 @@ describe('EmptyElement', () => {
})
it('should use COMPLETION mode for completion apps', () => {
const appDetail = createMockAppDetail(AppModeEnum.COMPLETION)
const appDetail = createMockAppDetail('completion')
render(<EmptyElement appDetail={appDetail} />)
const link = screen.getAllByRole('link')[0]
@@ -91,7 +90,7 @@ describe('EmptyElement', () => {
})
it('should use WORKFLOW mode for workflow apps', () => {
const appDetail = createMockAppDetail(AppModeEnum.WORKFLOW)
const appDetail = createMockAppDetail('workflow')
render(<EmptyElement appDetail={appDetail} />)
const link = screen.getAllByRole('link')[0]
@@ -99,7 +98,7 @@ describe('EmptyElement', () => {
})
it('should use CHAT mode for advanced-chat apps', () => {
const appDetail = createMockAppDetail(AppModeEnum.ADVANCED_CHAT)
const appDetail = createMockAppDetail('advanced-chat')
render(<EmptyElement appDetail={appDetail} />)
const link = screen.getAllByRole('link')[0]
@@ -107,7 +106,7 @@ describe('EmptyElement', () => {
})
it('should use CHAT mode for agent-chat apps', () => {
const appDetail = createMockAppDetail(AppModeEnum.AGENT_CHAT)
const appDetail = createMockAppDetail('agent-chat')
render(<EmptyElement appDetail={appDetail} />)
const link = screen.getAllByRole('link')[0]
@@ -128,7 +127,7 @@ describe('EmptyElement', () => {
describe('Links', () => {
it('should render share link with correct attributes', () => {
const appDetail = createMockAppDetail(AppModeEnum.CHAT)
const appDetail = createMockAppDetail('chat')
render(<EmptyElement appDetail={appDetail} />)
const links = screen.getAllByRole('link')
@@ -139,7 +138,7 @@ describe('EmptyElement', () => {
})
it('should render test link with redirection path', () => {
const appDetail = createMockAppDetail(AppModeEnum.CHAT)
const appDetail = createMockAppDetail('chat')
render(<EmptyElement appDetail={appDetail} />)
const links = screen.getAllByRole('link')
@@ -3,7 +3,6 @@ import { fireEvent, render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import dayjs from 'dayjs'
import { APP_PAGE_LIMIT } from '@/config'
import { AppModeEnum } from '@/types/app'
import Logs from '../index'
vi.mock('@/context/i18n', () => ({
@@ -94,7 +93,7 @@ describe('Logs', () => {
appDetail={
{
id: 'app-1',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
} as any
}
/>,
@@ -125,7 +124,7 @@ describe('Logs', () => {
appDetail={
{
id: 'app-2',
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
} as any
}
/>,
@@ -150,7 +149,7 @@ describe('Logs', () => {
appDetail={
{
id: 'app-3',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
} as any
}
/>,
@@ -174,7 +173,7 @@ describe('Logs', () => {
appDetail={
{
id: 'app-sandbox-last-30-days',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
} as any
}
/>,
@@ -200,7 +199,7 @@ describe('Logs', () => {
const user = userEvent.setup()
const appDetail = {
id: 'app-period-transition',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
} as any
mockUseChatConversations.mockReturnValue({
data: { total: 0 },
@@ -8,7 +8,6 @@ import { QueryClientTestProvider } from '@/test/console/query-provider'
import { render } from '@/test/console/render'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { renderWithNuqs } from '@/test/nuqs-testing'
import { AppModeEnum } from '@/types/app'
import ConversationList from '../list'
const mockFetchChatMessages = vi.fn()
@@ -304,7 +303,7 @@ const createChatMessage = (id: string, overrides: Record<string, unknown> = {})
})
const renderConversationList = ({
appDetail = { id: 'app-1', mode: AppModeEnum.CHAT } as any,
appDetail = { id: 'app-1', mode: 'chat' as const } as any,
logs = createLogs() as any,
searchParams = '?page=2',
}: {
@@ -518,36 +517,39 @@ describe('ConversationList', () => {
)
it.each([
['chatbot', AppModeEnum.CHAT, 'false'],
['agent', AppModeEnum.AGENT_CHAT, 'false'],
['chatflow', AppModeEnum.ADVANCED_CHAT, 'true'],
])('should expose run details only for %s conversation answers', async (_, mode, expected) => {
mockChatConversationDetail = {
id: 'conversation-1',
created_at: 1710000000,
model_config: {
model: 'gpt-4o',
configs: {
introduction: 'Hello there',
['chatbot', 'chat', 'false'],
['agent', 'agent-chat', 'false'],
['chatflow', 'advanced-chat', 'true'],
] as const)(
'should expose run details only for %s conversation answers',
async (_, mode, expected) => {
mockChatConversationDetail = {
id: 'conversation-1',
created_at: 1710000000,
model_config: {
model: 'gpt-4o',
configs: {
introduction: 'Hello there',
},
user_input_form: [],
},
user_input_form: [],
},
message: {
inputs: {},
},
}
mockFetchChatMessages.mockResolvedValue({
data: [createChatMessage('message-1')],
has_more: false,
})
message: {
inputs: {},
},
}
mockFetchChatMessages.mockResolvedValue({
data: [createChatMessage('message-1')],
has_more: false,
})
renderConversationList({
appDetail: { id: 'app-1', mode } as any,
searchParams: '?conversation_id=conversation-1',
})
renderConversationList({
appDetail: { id: 'app-1', mode } as any,
searchParams: '?conversation_id=conversation-1',
})
expect(await screen.findByTestId('chat-panel')).toHaveAttribute('data-log-enabled', expected)
})
expect(await screen.findByTestId('chat-panel')).toHaveAttribute('data-log-enabled', expected)
},
)
it('should render completion details and refetch after feedback updates', async () => {
mockCompletionConversationDetail = {
@@ -575,7 +577,7 @@ describe('ConversationList', () => {
}
renderConversationList({
appDetail: { id: 'app-1', mode: AppModeEnum.COMPLETION } as any,
appDetail: { id: 'app-1', mode: 'completion' as const } as any,
logs: createCompletionLogs() as any,
searchParams: '?page=2&conversation_id=conversation-1',
})
@@ -605,7 +607,7 @@ describe('ConversationList', () => {
it('should render chatflow status cells and feedback counters for advanced chat logs', () => {
renderConversationList({
appDetail: { id: 'app-1', mode: AppModeEnum.ADVANCED_CHAT } as any,
appDetail: { id: 'app-1', mode: 'advanced-chat' as const } as any,
logs: {
data: [
{
@@ -744,7 +746,7 @@ describe('ConversationList', () => {
})
})
it.each(['workflow', 'agent', 'prompt'])(
it.each(['workflow', 'agent', 'prompt'] as const)(
'should open and close the selected %s log within the conversation',
async (kind) => {
mockChatConversationDetail = {
@@ -753,7 +755,7 @@ describe('ConversationList', () => {
message: { inputs: {} },
}
renderConversationList({
appDetail: { id: 'app-1', mode: AppModeEnum.ADVANCED_CHAT },
appDetail: { id: 'app-1', mode: 'advanced-chat' as const },
searchParams: '?conversation_id=conversation-1',
})
const user = userEvent.setup()
@@ -776,7 +778,7 @@ describe('ConversationList', () => {
message: { inputs: {} },
}
renderConversationList({
appDetail: { id: 'app-1', mode: AppModeEnum.ADVANCED_CHAT },
appDetail: { id: 'app-1', mode: 'advanced-chat' as const },
searchParams: '?conversation_id=conversation-1',
})
const user = userEvent.setup()
@@ -791,7 +793,7 @@ describe('ConversationList', () => {
it('should clear a selected log when URL navigation changes or closes the conversation', async () => {
const user = userEvent.setup()
const queryClient = createAccountProfileQueryClient({ timezone: 'Asia/Shanghai' })
const appDetail = createAppDetailFixture({ mode: AppModeEnum.ADVANCED_CHAT })
const appDetail = createAppDetailFixture({ mode: 'advanced-chat' as const })
const view = (searchParams: string) => (
<NuqsTestingAdapter searchParams={searchParams} hasMemory>
<QueryClientTestProvider queryClient={queryClient}>
@@ -10,7 +10,6 @@ import {
renderWithConsoleQuery as renderWithoutPricing,
} from '@/test/console/query-data'
import { createAppSiteFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import { SettingsDialog } from '../index'
const onPricingUrlUpdate = vi.hoisted(() => vi.fn())
@@ -68,7 +67,7 @@ const mockAppInfo = {
show_workflow_steps: true,
use_icon_as_answer_icon: true,
},
mode: AppModeEnum.ADVANCED_CHAT,
mode: 'advanced-chat' as const,
} satisfies SettingsAppInfo
const triggerName = 'navigation.settings.settings'
@@ -5,7 +5,6 @@ import userEvent from '@testing-library/user-event'
import * as React from 'react'
import { renderWithConsoleQuery } from '@/test/console/query-data'
import { mockEmojiData } from '@/test/emoji-picker'
import { AppModeEnum } from '@/types/app'
import SwitchAppModal from '../index'
const mockPush = vi.fn()
@@ -84,7 +83,7 @@ const createMockApp = (overrides: Partial<AppPartial> = {}): AppPartial => ({
icon_background: '#FFEAD5',
icon_url: null,
use_icon_as_answer_icon: false,
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
created_at: Date.now(),
updated_at: Date.now(),
tags: [],
@@ -1,6 +1,6 @@
import type { AppMode } from '@dify/contracts/api/console/apps/types.gen'
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'
import * as React from 'react'
import { AppModeEnum } from '@/types/app'
import AppTypeSelector, { AppTypeLabel } from '../index'
describe('AppTypeSelector', () => {
@@ -21,16 +21,14 @@ describe('AppTypeSelector', () => {
// Covers prop-driven trigger variants (empty, single, multiple).
describe('Props', () => {
it('should render selected type label and clear button when a single type is selected', () => {
render(<AppTypeSelector value={[AppModeEnum.CHAT]} onChange={vi.fn()} />)
render(<AppTypeSelector value={['chat'] as const} onChange={vi.fn()} />)
expect(screen.getByText('app.typeSelector.chatbot')).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'common.operation.clear' })).toBeInTheDocument()
})
it('should render icon-only trigger when multiple types are selected', () => {
render(
<AppTypeSelector value={[AppModeEnum.CHAT, AppModeEnum.WORKFLOW]} onChange={vi.fn()} />,
)
render(<AppTypeSelector value={['chat', 'workflow'] as const} onChange={vi.fn()} />)
expect(screen.queryByText('app.typeSelector.all')).not.toBeInTheDocument()
expect(screen.queryByText('app.typeSelector.chatbot')).not.toBeInTheDocument()
@@ -65,12 +63,12 @@ describe('AppTypeSelector', () => {
within(screen.getByRole('list')).getByRole('button', { name: 'app.typeSelector.workflow' }),
)
expect(onChange).toHaveBeenCalledWith([AppModeEnum.WORKFLOW])
expect(onChange).toHaveBeenCalledWith(['workflow'] as const)
})
it('should call onChange with removed type when selecting an already-selected item', () => {
const onChange = vi.fn()
render(<AppTypeSelector value={[AppModeEnum.WORKFLOW]} onChange={onChange} />)
render(<AppTypeSelector value={['workflow'] as const} onChange={onChange} />)
fireEvent.click(screen.getByRole('button', { name: 'app.typeSelector.workflow' }))
fireEvent.click(
@@ -82,19 +80,19 @@ describe('AppTypeSelector', () => {
it('should call onChange with appended type when selecting an additional item', () => {
const onChange = vi.fn()
render(<AppTypeSelector value={[AppModeEnum.CHAT]} onChange={onChange} />)
render(<AppTypeSelector value={['chat'] as const} onChange={onChange} />)
fireEvent.click(screen.getByRole('button', { name: 'app.typeSelector.chatbot' }))
fireEvent.click(
within(screen.getByRole('list')).getByRole('button', { name: 'app.typeSelector.agent' }),
)
expect(onChange).toHaveBeenCalledWith([AppModeEnum.CHAT, AppModeEnum.AGENT_CHAT])
expect(onChange).toHaveBeenCalledWith(['chat', 'agent-chat'] as const)
})
it('should clear selection without opening the dropdown when clicking clear button', () => {
const onChange = vi.fn()
render(<AppTypeSelector value={[AppModeEnum.CHAT]} onChange={onChange} />)
render(<AppTypeSelector value={['chat'] as const} onChange={onChange} />)
fireEvent.click(screen.getByRole('button', { name: 'common.operation.clear' }))
@@ -111,11 +109,11 @@ describe('AppTypeLabel', () => {
// Covers label mapping for each supported app type.
it.each([
[AppModeEnum.CHAT, 'app.typeSelector.chatbot'],
[AppModeEnum.AGENT_CHAT, 'app.typeSelector.agent'],
[AppModeEnum.COMPLETION, 'app.typeSelector.completion'],
[AppModeEnum.ADVANCED_CHAT, 'app.typeSelector.advanced'],
[AppModeEnum.WORKFLOW, 'app.typeSelector.workflow'],
['chat', 'app.typeSelector.chatbot'],
['agent-chat', 'app.typeSelector.agent'],
['completion', 'app.typeSelector.completion'],
['advanced-chat', 'app.typeSelector.advanced'],
['workflow', 'app.typeSelector.workflow'],
] as const)('should render label %s for type %s', (_type, expectedLabel) => {
render(<AppTypeLabel type={_type} />)
expect(screen.getByText(expectedLabel)).toBeInTheDocument()
@@ -123,7 +121,7 @@ describe('AppTypeLabel', () => {
// Covers fallback behavior for unexpected app mode values.
it('should render empty label for unknown type', () => {
const { container } = render(<AppTypeLabel type={'unknown' as AppModeEnum} />)
const { container } = render(<AppTypeLabel type={'unknown' as AppMode} />)
expect(container.textContent).toBe('')
})
})
@@ -1,4 +1,4 @@
import type { AppDetailWithSite } from '@dify/contracts/api/console/apps/types.gen'
import type { AppDetailWithSite, AppMode } from '@dify/contracts/api/console/apps/types.gen'
import type { UseQueryResult } from '@tanstack/react-query'
/**
* Logs Container Component Tests
@@ -19,7 +19,6 @@ import type { MockedFunction } from 'vite-plus/test'
import type { CloudSandboxPlanState } from '../../log/cloud-sandbox-retention'
import type { ILogsProps } from '../index'
import type { WorkflowAppLogDetail, WorkflowLogsResponse, WorkflowRunDetail } from '@/models/log'
import type { AppModeEnum } from '@/types/app'
import { screen, waitFor, within } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import dayjs from 'dayjs'
@@ -612,7 +611,7 @@ describe('Logs Container', () => {
}),
)
const chatApp = createMockApp({ mode: 'advanced-chat' as AppModeEnum })
const chatApp = createMockApp({ mode: 'advanced-chat' as AppMode })
// Act
renderWithQueryClient(<Logs appDetail={chatApp} />)
@@ -1,4 +1,4 @@
import type { AppDetailWithSite } from '@dify/contracts/api/console/apps/types.gen'
import type { AppDetailWithSite, AppMode } from '@dify/contracts/api/console/apps/types.gen'
import type { WorkflowAppLogDetail, WorkflowLogsResponse, WorkflowRunDetail } from '@/models/log'
/**
* WorkflowAppLogList Component Tests
@@ -10,7 +10,6 @@ import type { WorkflowAppLogDetail, WorkflowLogsResponse, WorkflowRunDetail } fr
* - Drawer with run details
* - Loading states
*/
import type { AppModeEnum } from '@/types/app'
import { render, screen, waitFor, within } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { APP_PAGE_LIMIT } from '@/config'
@@ -203,7 +202,7 @@ describe('WorkflowAppLogList', () => {
it('should render trigger column for workflow apps', () => {
const logs = createMockLogsResponse([createMockWorkflowLog()])
const workflowApp = createMockApp({ mode: 'workflow' as AppModeEnum })
const workflowApp = createMockApp({ mode: 'workflow' as AppMode })
render(
<WorkflowAppLogList logs={logs} appDetail={workflowApp} onRefresh={defaultOnRefresh} />,
@@ -214,7 +213,7 @@ describe('WorkflowAppLogList', () => {
it('should not render trigger column for non-workflow apps', () => {
const logs = createMockLogsResponse([createMockWorkflowLog()])
const chatApp = createMockApp({ mode: 'advanced-chat' as AppModeEnum })
const chatApp = createMockApp({ mode: 'advanced-chat' as AppMode })
render(<WorkflowAppLogList logs={logs} appDetail={chatApp} onRefresh={defaultOnRefresh} />)
@@ -855,7 +854,7 @@ describe('WorkflowAppLogList', () => {
}),
}),
])
const chatApp = createMockApp({ mode: 'advanced-chat' as AppModeEnum })
const chatApp = createMockApp({ mode: 'advanced-chat' as AppMode })
render(<WorkflowAppLogList logs={logs} appDetail={chatApp} onRefresh={defaultOnRefresh} />)
@@ -6,7 +6,6 @@ import * as React from 'react'
import { STEP_BY_STEP_TOUR_TARGETS } from '@/app/components/step-by-step-tour/target-registry'
import { AccessMode } from '@/models/access-control'
import { renderWithConsoleQuery } from '@/test/console/query-data'
import { AppModeEnum } from '@/types/app'
import { AppACLPermission } from '@/utils/permission'
import { AppCard } from '../app-card'
import { StarredAppCard } from '../starred-app-card'
@@ -414,7 +413,7 @@ const createMockApp = (overrides: Partial<AppPartial> = {}): AppPartial => ({
id: 'test-app-id',
name: 'Test App',
description: 'Test app description',
mode: AppModeEnum.CHAT,
mode: 'chat',
icon: '🤖',
icon_type: 'emoji' as const,
icon_background: '#FFEAD5',
@@ -608,7 +607,7 @@ describe('AppCard', () => {
describe('Props', () => {
it('should handle different app modes', () => {
const workflowApp = { ...mockApp, mode: AppModeEnum.WORKFLOW }
const workflowApp = { ...mockApp, mode: 'workflow' as const }
render(<AppCard app={workflowApp} />)
expect(screen.getByRole('link', { name: 'Test App' })).toBeInTheDocument()
})
@@ -874,7 +873,7 @@ describe('AppCard', () => {
})
it('should show switch option for chat mode apps', async () => {
const chatApp = { ...mockApp, mode: AppModeEnum.CHAT }
const chatApp = { ...mockApp, mode: 'chat' as const }
render(<AppCard app={chatApp} />)
fireEvent.click(getOperationsTrigger())
@@ -889,7 +888,7 @@ describe('AppCard', () => {
const editableChatApp = createMockApp({
created_by: 'another-user',
maintainer: 'another-user',
mode: AppModeEnum.CHAT,
mode: 'chat',
permission_keys: [AppACLPermission.Edit],
})
render(<AppCard app={editableChatApp} />)
@@ -903,7 +902,7 @@ describe('AppCard', () => {
})
it('should show switch option for completion mode apps', async () => {
const completionApp = { ...mockApp, mode: AppModeEnum.COMPLETION }
const completionApp = { ...mockApp, mode: 'completion' as const }
render(<AppCard app={completionApp} />)
fireEvent.click(getOperationsTrigger())
@@ -914,7 +913,7 @@ describe('AppCard', () => {
})
it('should not show switch option for workflow mode apps', async () => {
const workflowApp = { ...mockApp, mode: AppModeEnum.WORKFLOW }
const workflowApp = { ...mockApp, mode: 'workflow' as const }
render(<AppCard app={workflowApp} />)
fireEvent.click(getOperationsTrigger())
@@ -1236,7 +1235,7 @@ describe('AppCard', () => {
describe('Switch Modal', () => {
it('should open switch modal when switch button is clicked', async () => {
const chatApp = { ...mockApp, mode: AppModeEnum.CHAT }
const chatApp = { ...mockApp, mode: 'chat' as const }
render(<AppCard app={chatApp} />)
fireEvent.click(getOperationsTrigger())
@@ -1250,7 +1249,7 @@ describe('AppCard', () => {
})
it('should close switch modal when close button is clicked', async () => {
const chatApp = { ...mockApp, mode: AppModeEnum.CHAT }
const chatApp = { ...mockApp, mode: 'chat' as const }
render(<AppCard app={chatApp} />)
fireEvent.click(getOperationsTrigger())
@@ -1270,7 +1269,7 @@ describe('AppCard', () => {
})
it('should open switch modal for completion mode apps', async () => {
const completionApp = { ...mockApp, mode: AppModeEnum.COMPLETION }
const completionApp = { ...mockApp, mode: 'completion' as const }
render(<AppCard app={completionApp} />)
fireEvent.click(getOperationsTrigger())
@@ -1313,7 +1312,7 @@ describe('AppCard', () => {
describe('Workflow Export with Environment Variables', () => {
it('should use the workflow export command for workflow apps', async () => {
const workflowApp = { ...mockApp, mode: AppModeEnum.WORKFLOW }
const workflowApp = { ...mockApp, mode: 'workflow' as const }
render(<AppCard app={workflowApp} />)
fireEvent.click(getOperationsTrigger())
@@ -1336,7 +1335,7 @@ describe('AppCard', () => {
secretEnvList: [{ value_type: 'secret', name: 'API_KEY', value: 'secret' }],
})
const workflowApp = { ...mockApp, mode: AppModeEnum.WORKFLOW }
const workflowApp = { ...mockApp, mode: 'workflow' as const }
render(<AppCard app={workflowApp} />)
fireEvent.click(getOperationsTrigger())
@@ -1350,7 +1349,7 @@ describe('AppCard', () => {
})
it('should not open a modal when the workflow command downloads directly', async () => {
const workflowApp = { ...mockApp, mode: AppModeEnum.WORKFLOW }
const workflowApp = { ...mockApp, mode: 'workflow' as const }
render(<AppCard app={workflowApp} />)
fireEvent.click(getOperationsTrigger())
@@ -1369,7 +1368,7 @@ describe('AppCard', () => {
})
it('should use the workflow export command for advanced chat apps', async () => {
const advancedChatApp = { ...mockApp, mode: AppModeEnum.ADVANCED_CHAT }
const advancedChatApp = { ...mockApp, mode: 'advanced-chat' as const }
render(<AppCard app={advancedChatApp} />)
fireEvent.click(getOperationsTrigger())
@@ -1391,7 +1390,7 @@ describe('AppCard', () => {
secretEnvList: [{ value_type: 'secret', name: 'API_KEY', value: 'secret' }],
})
const workflowApp = { ...mockApp, mode: AppModeEnum.WORKFLOW }
const workflowApp = { ...mockApp, mode: 'workflow' as const }
render(<AppCard app={workflowApp} />)
fireEvent.click(getOperationsTrigger())
@@ -1543,7 +1542,7 @@ describe('AppCard', () => {
describe('Open in Explore - No App Found', () => {
it('should tell workflow users to publish before opening in explore', async () => {
const workflowApp = createMockApp({
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
workflow: undefined,
})
render(<AppCard app={workflowApp} />)
@@ -1698,7 +1697,7 @@ describe('AppCard', () => {
mockUserCanAccessApp.result = false
mockUserCanAccessApp.isLoading = true
const workflowApp = createMockApp({
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
workflow: undefined,
})
+13 -16
View File
@@ -14,7 +14,6 @@ import {
import { createConsoleQueryWrapper } from '@/test/console/query-data'
import { seedRegisteredConsoleStateFixture } from '@/test/console/state-fixture'
import { renderWithNuqs } from '@/test/nuqs-testing'
import { AppModeEnum } from '@/types/app'
import { List } from '../list'
vi.mock('react-i18next', async () => {
@@ -185,7 +184,7 @@ const defaultAppData = {
id: 'app-1',
name: 'Test App 1',
description: 'Description 1',
mode: AppModeEnum.CHAT,
mode: 'chat',
icon: '🤖',
icon_type: 'emoji',
icon_background: '#FFEAD5',
@@ -198,7 +197,7 @@ const defaultAppData = {
id: 'app-2',
name: 'Test App 2',
description: 'Description 2',
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
icon: '⚙️',
icon_type: 'emoji',
icon_background: '#E4FBCC',
@@ -657,7 +656,7 @@ describe('List', () => {
id: 'starred-app-1',
name: 'Starred App',
description: 'Starred description',
mode: AppModeEnum.CHAT,
mode: 'chat',
icon: '⭐',
icon_type: 'emoji',
icon_background: '#FFEAD5',
@@ -712,7 +711,7 @@ describe('List', () => {
id: 'starred-app-1',
name: 'Starred App',
description: 'Starred description',
mode: AppModeEnum.CHAT,
mode: 'chat',
icon: '⭐',
icon_type: 'emoji',
icon_background: '#FFEAD5',
@@ -766,7 +765,7 @@ describe('List', () => {
id: 'starred-app-1',
name: 'Starred App',
description: 'Starred description',
mode: AppModeEnum.CHAT,
mode: 'chat',
icon: '⭐',
icon_type: 'emoji',
icon_background: '#FFEAD5',
@@ -1054,9 +1053,7 @@ describe('List', () => {
await user.click(await screen.findByRole('option', { name: 'app.types.workflow' }))
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
expect(onUrlUpdate.mock.calls.at(-1)?.[0].searchParams.get('category')).toBe(
AppModeEnum.WORKFLOW,
)
expect(onUrlUpdate.mock.calls.at(-1)?.[0].searchParams.get('category')).toBe('workflow')
})
it('should update category when all option is selected', async () => {
@@ -1123,7 +1120,7 @@ describe('List', () => {
sort_by: 'last_modified',
tag_ids: ['tag-1'],
creator_ids: ['creator-1'],
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
},
})
expect(options.getNextPageParam({ has_more: true, page: 2 })).toBe(3)
@@ -1149,7 +1146,7 @@ describe('List', () => {
sort_by: 'last_modified',
tag_ids: ['tag-1'],
creator_ids: ['creator-1'],
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
},
})
})
@@ -1233,11 +1230,11 @@ describe('List', () => {
describe('App Type Select Options', () => {
it('should update category for each app type option click', async () => {
const appTypeTexts = [
{ mode: AppModeEnum.WORKFLOW, text: 'app.types.workflow' },
{ mode: AppModeEnum.ADVANCED_CHAT, text: 'app.types.advanced' },
{ mode: AppModeEnum.CHAT, text: 'app.types.chatbot' },
{ mode: AppModeEnum.AGENT_CHAT, text: 'app.types.agent' },
{ mode: AppModeEnum.COMPLETION, text: 'app.newApp.completeApp' },
{ mode: 'workflow' as const, text: 'app.types.workflow' },
{ mode: 'advanced-chat' as const, text: 'app.types.advanced' },
{ mode: 'chat' as const, text: 'app.types.chatbot' },
{ mode: 'agent-chat' as const, text: 'app.types.agent' },
{ mode: 'completion' as const, text: 'app.newApp.completeApp' },
]
for (const { mode, text } of appTypeTexts) {
@@ -11,7 +11,7 @@ import { createFeaturesStore } from '@/app/components/base/features/store'
import { renderWithConsoleQuery as render } from '@/test/console/query-data'
import { createAppModelConfigFixture } from '@/test/fixtures/app'
import { withSelectorKey } from '@/test/i18n-mock'
import { AppModeEnum, ModelModeType } from '@/types/app'
import { ModelModeType } from '@/types/app'
import ModerationSettingModal from '../moderation-setting-modal'
const mockNotify = vi.fn()
@@ -1081,7 +1081,7 @@ describe('ModerationSettingModal', () => {
backendModelConfig,
collectionList: [],
datasetConfigs: buildConfigurationDatasetConfigs({ backendModelConfig, nextDataSets: [] }),
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
nextDataSets: [],
})
const publish = createPublishHandler({
@@ -1096,7 +1096,7 @@ describe('ModerationSettingModal', () => {
hasSetBlockStatus: { history: true, query: true },
isAdvancedMode: false,
isFunctionCall: false,
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
modelConfig: config.modelConfig,
promptEmpty: false,
promptMode: 'simple',
@@ -1,7 +1,6 @@
import { render, screen } from '@testing-library/react'
import * as React from 'react'
import { vi } from 'vite-plus/test'
import { AppModeEnum } from '@/types/app'
import LinkedAppsPanel from '../index'
vi.mock('@/next/link', () => ({
@@ -25,7 +24,7 @@ describe('LinkedAppsPanel Component', () => {
{
id: 'app-1',
name: 'Chatbot App',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
icon_type: 'emoji' as const,
icon: '🤖',
icon_background: '#FFEAD5',
@@ -34,7 +33,7 @@ describe('LinkedAppsPanel Component', () => {
{
id: 'app-2',
name: 'Workflow App',
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
icon_type: 'image' as const,
icon: 'file-id',
icon_background: '#E4FBCC',
@@ -43,7 +42,7 @@ describe('LinkedAppsPanel Component', () => {
{
id: 'app-3',
name: '',
mode: AppModeEnum.AGENT_CHAT,
mode: 'agent-chat' as const,
icon_type: 'emoji' as const,
icon: '🕵️',
icon_background: '#D3F8DF',
@@ -6,7 +6,6 @@ import userEvent from '@testing-library/user-event'
import * as React from 'react'
import { renderWithConsoleQuery } from '@/test/console/query-data'
import { mockEmojiData } from '@/test/emoji-picker'
import { AppModeEnum } from '@/types/app'
import CreateAppModal from '../index'
let deploymentEdition: 'CLOUD' | 'COMMUNITY' = 'COMMUNITY'
@@ -29,7 +28,7 @@ const setup = async (overrides: Partial<CreateAppModalProps> = {}) => {
appIcon: '🤖',
appIconBackground: '#FFEAD5',
appIconUrl: null,
appMode: AppModeEnum.CHAT,
appMode: 'chat' as const,
appUseIconAsAnswerIcon: false,
max_active_requests: null,
onConfirm,
@@ -124,7 +123,7 @@ describe('CreateAppModal', () => {
})
it('should render edit-only fields when editing a chat app', async () => {
await setup({ isEditModal: true, appMode: AppModeEnum.CHAT, max_active_requests: 5 })
await setup({ isEditModal: true, appMode: 'chat' as const, max_active_requests: 5 })
expect(screen.getByText('app.editAppTitle'))!.toBeInTheDocument()
expect(screen.getByRole('button', { name: /common\.operation\.save/ }))!.toBeInTheDocument()
@@ -132,7 +131,7 @@ describe('CreateAppModal', () => {
expect((screen.getByRole('spinbutton') as HTMLInputElement).value).toBe('5')
})
it.each([AppModeEnum.ADVANCED_CHAT, AppModeEnum.AGENT_CHAT])(
it.each(['advanced-chat', 'agent-chat'] as const)(
'should render answer icon switch when editing %s app',
async (mode) => {
await setup({ isEditModal: true, appMode: mode })
@@ -142,7 +141,7 @@ describe('CreateAppModal', () => {
)
it('should not render answer icon switch when editing a non-chat app', async () => {
await setup({ isEditModal: true, appMode: AppModeEnum.COMPLETION })
await setup({ isEditModal: true, appMode: 'completion' as const })
expect(screen.queryByRole('switch')).not.toBeInTheDocument()
})
@@ -587,7 +586,7 @@ describe('CreateAppModal', () => {
it('should include max_active_requests and updated answer icon when saving', async () => {
const { onConfirm } = await setup({
isEditModal: true,
appMode: AppModeEnum.CHAT,
appMode: 'chat' as const,
appUseIconAsAnswerIcon: false,
max_active_requests: 3,
})
@@ -170,7 +170,7 @@ describe('InstalledApp', () => {
})
})
it.each<AppMode>(['chat', 'advanced-chat', 'agent-chat'])(
it.each<AppMode>(['chat', 'advanced-chat', 'agent-chat'] as const)(
'renders chat for the supported %s mode',
async (mode) => {
mocks.getInstalledApp.mockResolvedValue(createInstalledApp(mode))
@@ -5,7 +5,6 @@ import { screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { trackEvent } from '@/app/components/base/amplitude'
import { renderWithConsoleQuery } from '@/test/console/query-data'
import { AppModeEnum } from '@/types/app'
import LearnDifyItem from '../item'
let deploymentEdition: DeploymentEdition = 'CLOUD'
@@ -19,7 +18,7 @@ vi.mock('@/app/components/base/amplitude', () => ({
const createApp = (overrides: Partial<RecommendedAppResponse> = {}): RecommendedAppResponse => ({
app: {
id: 'app-basic-id',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
icon_type: 'emoji',
icon: '😀',
icon_background: '#fff',
@@ -20,7 +20,7 @@ const createApp = (mode: string): TrialAppDetailResponse =>
}) as TrialAppDetailResponse
describe('TryApp', () => {
it.each(['chat', 'advanced-chat', 'agent-chat'])(
it.each(['chat', 'advanced-chat', 'agent-chat'] as const)(
'uses the chat experience for %s apps',
(mode) => {
render(<TryApp appId="app-id" appDetail={createApp(mode)} />)
@@ -32,7 +32,7 @@ describe('TryApp', () => {
it.each([
['completion', 'Completion preview'],
['workflow', 'Workflow preview'],
])('uses the text generation experience for %s apps', (mode, name) => {
] as const)('uses the text generation experience for %s apps', (mode, name) => {
render(<TryApp appId="app-id" appDetail={createApp(mode)} />)
expect(screen.getByRole('region', { name })).toBeInTheDocument()
@@ -19,7 +19,7 @@ describe('Preview', () => {
expect(screen.getByRole('region', { name: 'Basic app preview' })).toBeInTheDocument()
})
it.each(['workflow', 'advanced-chat'])('uses the flow preview for %s apps', (mode) => {
it.each(['workflow', 'advanced-chat'] as const)('uses the flow preview for %s apps', (mode) => {
render(<Preview appId="app-id" appDetail={createApp(mode)} />)
expect(screen.getByRole('region', { name: 'Flow app preview' })).toBeInTheDocument()
@@ -357,7 +357,7 @@ describe('getToolType', () => {
['workflow', ToolType.Workflow],
['mcp', ToolType.MCP],
['unknown', ToolType.BuiltIn],
])('returns correct ToolType for "%s"', (input, expected) => {
] as const)('returns correct ToolType for "%s"', (input, expected) => {
expect(getToolType(input)).toBe(expected)
})
})
@@ -37,7 +37,6 @@ import {
renderWithConsoleQuery as renderWithoutPricing,
} from '@/test/console/query-data'
import { seedRegisteredConsoleStateFixture } from '@/test/console/state-fixture'
import { AppModeEnum } from '@/types/app'
import { MainNav } from '../index'
const onPricingUrlUpdate = vi.hoisted(() => vi.fn())
@@ -471,7 +470,7 @@ const createInstalledApp = (
is_pinned: overrides.is_pinned ?? false,
app: {
id: overrides.app?.id ?? 'app-1',
mode: overrides.app?.mode ?? AppModeEnum.CHAT,
mode: overrides.app?.mode ?? 'chat',
icon_type: overrides.app?.icon_type ?? 'emoji',
icon: overrides.app?.icon ?? '🤖',
icon_background: overrides.app?.icon_background ?? '#fff',
@@ -2,7 +2,6 @@ import type { AppPartial } from '@dify/contracts/api/console/apps/types.gen'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { useState } from 'react'
import { AppModeEnum } from '@/types/app'
import { AppPicker } from '../app-picker'
vi.mock('@/app/components/base/app-icon', () => ({
@@ -12,7 +11,7 @@ vi.mock('@/app/components/base/app-icon', () => ({
const app = {
id: 'app-1',
name: 'Workflow App',
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
icon_type: 'emoji',
icon: 'W',
icon_background: '#FFFFFF',
@@ -6,14 +6,13 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { useState } from 'react'
import { beforeEach, describe, expect, it, vi } from 'vite-plus/test'
import { AppModeEnum } from '@/types/app'
import { AppSelector } from '../index'
const apps = [
{
id: 'app-1',
name: 'Support Bot',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
icon_type: 'emoji',
icon: '🤖',
icon_background: '#FFEAD5',
@@ -22,7 +21,7 @@ const apps = [
{
id: 'app-2',
name: 'Workflow App',
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
icon_type: 'emoji',
icon: '⚙️',
icon_background: '#E0EAFF',
@@ -31,7 +30,7 @@ const apps = [
{
id: 'app-3',
name: 'Sales Bot',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
icon_type: 'emoji',
icon: '💼',
icon_background: '#FEE4E2',
@@ -1,7 +1,7 @@
import { renderHook } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vite-plus/test'
import { BlockEnum, InputVarType, SupportUploadFileTypes } from '@/app/components/workflow/types'
import { AppModeEnum, Resolution } from '@/types/app'
import { Resolution } from '@/types/app'
import { useAppInputsFormSchema } from '../use-app-inputs-form-schema'
let mockAppDetailData: Record<string, unknown> | null = null
@@ -54,7 +54,7 @@ describe('useAppInputsFormSchema', () => {
it('should build basic app schemas and append image upload support', () => {
mockAppDetailData = {
id: 'app-1',
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
model_config: {
user_input_form: [
{
@@ -84,7 +84,7 @@ describe('useAppInputsFormSchema', () => {
useAppInputsFormSchema({
appDetail: {
id: 'app-1',
mode: AppModeEnum.COMPLETION,
mode: 'completion' as const,
} as never,
}),
)
@@ -108,7 +108,7 @@ describe('useAppInputsFormSchema', () => {
it('should build workflow schemas from start node variables', () => {
mockAppDetailData = {
id: 'app-2',
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
}
mockAppWorkflowData = {
graph: {
@@ -134,7 +134,7 @@ describe('useAppInputsFormSchema', () => {
useAppInputsFormSchema({
appDetail: {
id: 'app-2',
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
} as never,
}),
)
@@ -153,7 +153,7 @@ describe('useAppInputsFormSchema', () => {
useAppInputsFormSchema({
appDetail: {
id: 'missing-app',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
} as never,
}),
)
@@ -166,7 +166,7 @@ describe('useAppInputsFormSchema', () => {
mockFileUploadConfigIsFetching = true
mockAppDetailData = {
id: 'app-1',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
model_config: {
user_input_form: [
{
@@ -183,7 +183,7 @@ describe('useAppInputsFormSchema', () => {
useAppInputsFormSchema({
appDetail: {
id: 'app-1',
mode: AppModeEnum.CHAT,
mode: 'chat' as const,
} as never,
}),
)
@@ -203,7 +203,7 @@ describe('useAppInputsFormSchema', () => {
useAppInputsFormSchema({
appDetail: {
id: 'app-2',
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
} as never,
}),
)
@@ -68,16 +68,10 @@ vi.mock('../../hooks/use-pipeline-start-run', () => ({
}),
}))
vi.mock('../../hooks/use-configs-map', () => ({
useConfigsMap: () => ({
flowId: 'test-flow-id',
flowType: 'ragPipeline',
fileSettings: {},
}),
}))
vi.mock('../../hooks/use-file-settings', () => ({ useFileSettings: () => ({ enabled: true }) }))
vi.mock('../../hooks/use-inspect-vars-crud', () => ({
useInspectVarsCrud: () => ({
vi.mock('@/app/components/workflow/hooks/use-inspect-vars-crud-common', () => ({
useInspectVarsCrudCommon: () => ({
hasNodeInspectVars: vi.fn(),
hasSetInspectVar: vi.fn(),
fetchInspectVarValue: vi.fn(),
@@ -1,5 +1,6 @@
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
import { act, fireEvent, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vite-plus/test'
import { renderEditor as render } from '@/app/components/workflow/editor-resource/__tests__/render'
import { DSLImportStatus } from '@/models/app'
import UpdateDSLModal from '../update-dsl-modal'
@@ -133,43 +134,57 @@ describe('UpdateDSLModal', () => {
describe('rendering', () => {
it('should render title', () => {
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
expect(screen.getByText('workflow.common.importDSL')).toBeInTheDocument()
})
it('should render warning tip', () => {
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
expect(screen.getByText('workflow.common.importDSLTip')).toBeInTheDocument()
})
it('should render uploader', () => {
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
expect(screen.getByTestId('uploader')).toBeInTheDocument()
})
it('should render backup button', () => {
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
expect(screen.getByText('workflow.common.backupCurrentDraft')).toBeInTheDocument()
})
it('should render cancel button', () => {
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
expect(screen.getByText('app.newApp.Cancel')).toBeInTheDocument()
})
it('should render import button', () => {
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
expect(screen.getByText('workflow.common.overwriteAndImport')).toBeInTheDocument()
})
it('should render choose DSL section', () => {
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
expect(screen.getByText('workflow.common.chooseDSL')).toBeInTheDocument()
})
@@ -177,7 +192,9 @@ describe('UpdateDSLModal', () => {
describe('user interactions', () => {
it('should call onCancel when cancel button is clicked', () => {
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
const cancelButton = screen.getByText('app.newApp.Cancel')
fireEvent.click(cancelButton)
@@ -186,7 +203,9 @@ describe('UpdateDSLModal', () => {
})
it('should call onBackup when backup button is clicked', () => {
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
const backupButton = screen.getByText('workflow.common.backupCurrentDraft')
fireEvent.click(backupButton)
@@ -195,7 +214,9 @@ describe('UpdateDSLModal', () => {
})
it('should handle file upload', async () => {
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
const fileInput = screen.getByTestId('file-input')
const file = new File(['test content'], 'test.pipeline', { type: 'text/yaml' })
@@ -208,7 +229,9 @@ describe('UpdateDSLModal', () => {
})
it('should clear file when clear button is clicked', () => {
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
const clearButton = screen.getByTestId('clear-file')
fireEvent.click(clearButton)
@@ -219,14 +242,18 @@ describe('UpdateDSLModal', () => {
describe('import functionality', () => {
it('should show import button disabled when no file is selected', () => {
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
const importButton = screen.getByText('workflow.common.overwriteAndImport')
expect(importButton).toBeDisabled()
})
it('should enable import button when file is selected', async () => {
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
const fileInput = screen.getByTestId('file-input')
const file = new File(['test content'], 'test.pipeline', { type: 'text/yaml' })
@@ -240,7 +267,9 @@ describe('UpdateDSLModal', () => {
})
it('should disable import button after file is cleared', async () => {
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
const fileInput = screen.getByTestId('file-input')
const file = new File(['test content'], 'test.pipeline', { type: 'text/yaml' })
@@ -263,7 +292,9 @@ describe('UpdateDSLModal', () => {
describe('import flow', () => {
it('should call importDSL when import button is clicked with file content', async () => {
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
const fileInput = screen.getByTestId('file-input')
const file = new File(['test content'], 'test.pipeline', { type: 'text/yaml' })
@@ -289,7 +320,9 @@ describe('UpdateDSLModal', () => {
pipeline_id: 'test-pipeline-id',
})
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
const fileInput = screen.getByTestId('file-input')
const file = new File(['test content'], 'test.pipeline', { type: 'text/yaml' })
@@ -319,7 +352,9 @@ describe('UpdateDSLModal', () => {
pipeline_id: 'test-pipeline-id',
})
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
const fileInput = screen.getByTestId('file-input')
const file = new File(['test content'], 'test.pipeline', { type: 'text/yaml' })
@@ -345,7 +380,9 @@ describe('UpdateDSLModal', () => {
pipeline_id: 'test-pipeline-id',
})
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
const fileInput = screen.getByTestId('file-input')
const file = new File(['test content'], 'test.pipeline', { type: 'text/yaml' })
@@ -377,7 +414,9 @@ describe('UpdateDSLModal', () => {
pipeline_id: 'test-pipeline-id',
})
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
const fileInput = screen.getByTestId('file-input')
const file = new File(['test content'], 'test.pipeline', { type: 'text/yaml' })
@@ -407,7 +446,9 @@ describe('UpdateDSLModal', () => {
pipeline_id: 'test-pipeline-id',
})
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
const fileInput = screen.getByTestId('file-input')
const file = new File(['test content'], 'test.pipeline', { type: 'text/yaml' })
@@ -437,7 +478,9 @@ describe('UpdateDSLModal', () => {
pipeline_id: undefined,
})
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
const fileInput = screen.getByTestId('file-input')
const file = new File(['test content'], 'test.pipeline', { type: 'text/yaml' })
@@ -463,7 +506,9 @@ describe('UpdateDSLModal', () => {
it('should show error notification when import throws exception', async () => {
mockImportDSL.mockRejectedValue(new Error('Import failed'))
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
const fileInput = screen.getByTestId('file-input')
const file = new File(['test content'], 'test.pipeline', { type: 'text/yaml' })
@@ -493,7 +538,9 @@ describe('UpdateDSLModal', () => {
pipeline_id: 'test-pipeline-id',
})
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
const fileInput = screen.getByTestId('file-input')
const file = new File(['test content'], 'test.pipeline', { type: 'text/yaml' })
@@ -523,7 +570,9 @@ describe('UpdateDSLModal', () => {
pipeline_id: 'test-pipeline-id',
})
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
const fileInput = screen.getByTestId('file-input')
const file = new File(['test content'], 'test.pipeline', { type: 'text/yaml' })
@@ -553,7 +602,9 @@ describe('UpdateDSLModal', () => {
current_dsl_version: '2.0.0',
})
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
const fileInput = screen.getByTestId('file-input')
const file = new File(['test content'], 'test.pipeline', { type: 'text/yaml' })
@@ -588,7 +639,9 @@ describe('UpdateDSLModal', () => {
current_dsl_version: '2.0.0',
})
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
const fileInput = screen.getByTestId('file-input')
const file = new File(['test content'], 'test.pipeline', { type: 'text/yaml' })
@@ -620,7 +673,9 @@ describe('UpdateDSLModal', () => {
current_dsl_version: '2.0.0',
})
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
const fileInput = screen.getByTestId('file-input')
const file = new File(['test content'], 'test.pipeline', { type: 'text/yaml' })
@@ -665,7 +720,9 @@ describe('UpdateDSLModal', () => {
pipeline_id: 'test-pipeline-id',
})
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
const fileInput = screen.getByTestId('file-input')
const file = new File(['test content'], 'test.pipeline', { type: 'text/yaml' })
@@ -715,7 +772,9 @@ describe('UpdateDSLModal', () => {
pipeline_id: 'test-pipeline-id',
})
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
const fileInput = screen.getByTestId('file-input')
const file = new File(['test content'], 'test.pipeline', { type: 'text/yaml' })
@@ -762,7 +821,9 @@ describe('UpdateDSLModal', () => {
pipeline_id: 'test-pipeline-id',
})
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
const fileInput = screen.getByTestId('file-input')
const file = new File(['test content'], 'test.pipeline', { type: 'text/yaml' })
@@ -806,7 +867,9 @@ describe('UpdateDSLModal', () => {
mockImportDSLConfirm.mockRejectedValue(new Error('Confirm failed'))
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
const fileInput = screen.getByTestId('file-input')
const file = new File(['test content'], 'test.pipeline', { type: 'text/yaml' })
@@ -853,7 +916,9 @@ describe('UpdateDSLModal', () => {
pipeline_id: undefined,
})
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
const fileInput = screen.getByTestId('file-input')
const file = new File(['test content'], 'test.pipeline', { type: 'text/yaml' })
@@ -900,7 +965,9 @@ describe('UpdateDSLModal', () => {
pipeline_id: 'test-pipeline-id',
})
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
const fileInput = screen.getByTestId('file-input')
const file = new File(['test content'], 'test.pipeline', { type: 'text/yaml' })
@@ -945,7 +1012,9 @@ describe('UpdateDSLModal', () => {
pipeline_id: 'test-pipeline-id',
})
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
const fileInput = screen.getByTestId('file-input')
const file = new File(['test content'], 'test.pipeline', { type: 'text/yaml' })
@@ -990,7 +1059,9 @@ describe('UpdateDSLModal', () => {
current_dsl_version: undefined,
})
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
const fileInput = screen.getByTestId('file-input')
const file = new File(['test content'], 'test.pipeline', { type: 'text/yaml' })
@@ -1013,7 +1084,9 @@ describe('UpdateDSLModal', () => {
})
it('should not call importDSLConfirm when importId is not set', async () => {
render(<UpdateDSLModal {...defaultProps} />)
render(<UpdateDSLModal {...defaultProps} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
expect(mockImportDSLConfirm).not.toHaveBeenCalled()
})
@@ -1,5 +1,6 @@
import type { PanelProps } from '@/app/components/workflow/panel'
import { render, screen } from '@testing-library/react'
import { screen } from '@testing-library/react'
import { renderEditor as render } from '@/app/components/workflow/editor-resource/__tests__/render'
import RagPipelinePanel from '../index'
const { dynamicComponent } = vi.hoisted(() => {
@@ -54,7 +55,9 @@ vi.mock('@/app/components/workflow/panel', () => ({
describe('RagPipelinePanel', () => {
it('renders enabled panels and supplies pipeline version endpoints', () => {
render(<RagPipelinePanel />)
render(<RagPipelinePanel />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
expect(screen.getByText('Record panel')).toBeInTheDocument()
expect(screen.getByText('Test run panel')).toBeInTheDocument()
@@ -1,5 +1,6 @@
import type { Datasource } from '../../../test-run/types'
import { fireEvent, render, screen } from '@testing-library/react'
import { fireEvent, screen } from '@testing-library/react'
import { renderEditor as render } from '@/app/components/workflow/editor-resource/__tests__/render'
import DataSource from '../data-source'
const { mockOnSelect, mockUseDraftPipelinePreProcessingParams } = vi.hoisted(() => ({
@@ -48,7 +49,9 @@ describe('DataSource preview', () => {
})
it('should render the datasource selection step and forward selected values', () => {
render(<DataSource onSelect={mockOnSelect} dataSourceNodeId="node-1" />)
render(<DataSource onSelect={mockOnSelect} dataSourceNodeId="node-1" />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
fireEvent.click(screen.getByText('select datasource'))
@@ -1,8 +1,9 @@
import type { Datasource, DataSourceOption } from '../../../test-run/types'
import type { RAGPipelineVariable, RAGPipelineVariables } from '@/models/pipeline'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { fireEvent, screen, waitFor } from '@testing-library/react'
import * as React from 'react'
import { renderEditor as render } from '@/app/components/workflow/editor-resource/__tests__/render'
import { PipelineInputVarType } from '@/models/pipeline'
import DataSource from '../data-source'
import Form from '../form'
@@ -210,7 +211,10 @@ const TestWrapper = ({ children }: { children: React.ReactNode }) => {
}
const renderWithProviders = (ui: React.ReactElement) => {
return render(ui, { wrapper: TestWrapper })
return render(ui, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
wrapper: TestWrapper,
})
}
describe('PreviewPanel', () => {
@@ -1,4 +1,5 @@
import { render, screen } from '@testing-library/react'
import { screen } from '@testing-library/react'
import { renderEditor as render } from '@/app/components/workflow/editor-resource/__tests__/render'
import ProcessDocuments from '../process-documents'
const mockUseDraftPipelineProcessingParams = vi.hoisted(() =>
@@ -30,7 +31,9 @@ describe('ProcessDocuments preview', () => {
})
it('should render the processing step and its variables', () => {
render(<ProcessDocuments dataSourceNodeId="node-2" />)
render(<ProcessDocuments dataSourceNodeId="node-2" />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
expect(
screen.getByText('datasetPipeline.inputFieldPanel.preview.stepTwoTitle'),
@@ -1,4 +1,4 @@
import { renderHook } from '@testing-library/react'
import { renderEditorHook as renderHook } from '@/app/components/workflow/editor-resource/__tests__/render'
import { useInputVariables } from '../hooks'
const mockUseDraftPipelineProcessingParams = vi.hoisted(() =>
@@ -23,7 +23,9 @@ describe('useInputVariables', () => {
})
it('should query processing params with the current pipeline id and datasource node id', () => {
const { result } = renderHook(() => useInputVariables('datasource-node'))
const { result } = renderHook(() => useInputVariables('datasource-node'), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
expect(mockUseDraftPipelineProcessingParams).toHaveBeenCalledWith({
pipeline_id: 'pipeline-1',
@@ -7,9 +7,10 @@ import type {
RAGPipelineVariables,
} from '@/models/pipeline'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { fireEvent, render, screen } from '@testing-library/react'
import { fireEvent, screen } from '@testing-library/react'
import * as React from 'react'
import { BaseFieldType } from '@/app/components/base/form/form-scenarios/base/types'
import { renderEditor as render } from '@/app/components/workflow/editor-resource/__tests__/render'
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
import { PipelineInputVarType } from '@/models/pipeline'
import { expectLoadingButton } from '@/test/button'
@@ -193,7 +194,9 @@ const createQueryClient = () =>
const renderWithQueryClient = (component: React.ReactElement) => {
const queryClient = createQueryClient()
return render(<QueryClientProvider client={queryClient}>{component}</QueryClientProvider>)
return render(<QueryClientProvider client={queryClient}>{component}</QueryClientProvider>, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
}
describe('DocumentProcessing', () => {
@@ -475,7 +478,9 @@ describe('Actions', () => {
it('should render back button', () => {
const mockFormParams = createMockFormParams()
render(<Actions formParams={mockFormParams} onBack={vi.fn()} />)
render(<Actions formParams={mockFormParams} onBack={vi.fn()} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
expect(screen.getByText('datasetPipeline.operations.backToDataSource')).toBeInTheDocument()
})
@@ -483,7 +488,9 @@ describe('Actions', () => {
it('should render process button', () => {
const mockFormParams = createMockFormParams()
render(<Actions formParams={mockFormParams} onBack={vi.fn()} />)
render(<Actions formParams={mockFormParams} onBack={vi.fn()} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
expect(screen.getByText('datasetPipeline.operations.process')).toBeInTheDocument()
})
@@ -493,7 +500,9 @@ describe('Actions', () => {
it('should disable process button when runDisabled is true', () => {
const mockFormParams = createMockFormParams()
render(<Actions formParams={mockFormParams} runDisabled={true} onBack={vi.fn()} />)
render(<Actions formParams={mockFormParams} runDisabled={true} onBack={vi.fn()} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
const processButton = screen.getByText('datasetPipeline.operations.process').closest('button')
expect(processButton).toBeDisabled()
@@ -502,7 +511,9 @@ describe('Actions', () => {
it('should disable process button when isSubmitting is true', () => {
const mockFormParams = createMockFormParams({ isSubmitting: true })
render(<Actions formParams={mockFormParams} onBack={vi.fn()} />)
render(<Actions formParams={mockFormParams} onBack={vi.fn()} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
const processButton = screen.getByText('datasetPipeline.operations.process').closest('button')
expectLoadingButton(processButton)
@@ -511,7 +522,9 @@ describe('Actions', () => {
it('should disable process button when canSubmit is false', () => {
const mockFormParams = createMockFormParams({ canSubmit: false })
render(<Actions formParams={mockFormParams} onBack={vi.fn()} />)
render(<Actions formParams={mockFormParams} onBack={vi.fn()} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
const processButton = screen.getByText('datasetPipeline.operations.process').closest('button')
expect(processButton).toBeDisabled()
@@ -523,7 +536,9 @@ describe('Actions', () => {
}
const mockFormParams = createMockFormParams()
render(<Actions formParams={mockFormParams} onBack={vi.fn()} />)
render(<Actions formParams={mockFormParams} onBack={vi.fn()} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
const processButton = screen.getByText('datasetPipeline.operations.process').closest('button')
expectLoadingButton(processButton)
@@ -535,7 +550,9 @@ describe('Actions', () => {
}
const mockFormParams = createMockFormParams()
render(<Actions formParams={mockFormParams} runDisabled={false} onBack={vi.fn()} />)
render(<Actions formParams={mockFormParams} runDisabled={false} onBack={vi.fn()} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
const processButton = screen.getByText('datasetPipeline.operations.process').closest('button')
expect(processButton).not.toBeDisabled()
@@ -547,7 +564,9 @@ describe('Actions', () => {
const mockOnBack = vi.fn()
const mockFormParams = createMockFormParams()
render(<Actions formParams={mockFormParams} onBack={mockOnBack} />)
render(<Actions formParams={mockFormParams} onBack={mockOnBack} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
fireEvent.click(screen.getByText('datasetPipeline.operations.backToDataSource'))
@@ -558,7 +577,9 @@ describe('Actions', () => {
const mockSubmit = vi.fn()
const mockFormParams = createMockFormParams({ handleSubmit: mockSubmit })
render(<Actions formParams={mockFormParams} onBack={vi.fn()} />)
render(<Actions formParams={mockFormParams} onBack={vi.fn()} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
fireEvent.click(screen.getByText('datasetPipeline.operations.process'))
@@ -570,7 +591,9 @@ describe('Actions', () => {
it('should handle undefined runDisabled prop', () => {
const mockFormParams = createMockFormParams()
render(<Actions formParams={mockFormParams} onBack={vi.fn()} />)
render(<Actions formParams={mockFormParams} onBack={vi.fn()} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
const processButton = screen.getByText('datasetPipeline.operations.process').closest('button')
expect(processButton).not.toBeDisabled()
@@ -580,7 +603,9 @@ describe('Actions', () => {
mockWorkflowRunningData = undefined
const mockFormParams = createMockFormParams()
render(<Actions formParams={mockFormParams} onBack={vi.fn()} />)
render(<Actions formParams={mockFormParams} onBack={vi.fn()} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
const processButton = screen.getByText('datasetPipeline.operations.process').closest('button')
expect(processButton).not.toBeDisabled()
@@ -604,7 +629,9 @@ describe('Options', () => {
onSubmit: vi.fn(),
}
const { container } = render(<Options {...props} />)
const { container } = render(<Options {...props} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
expect(container.querySelector('form')).toBeInTheDocument()
})
@@ -622,7 +649,9 @@ describe('Options', () => {
onSubmit: vi.fn(),
}
render(<Options {...props} />)
render(<Options {...props} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
expect(screen.getByTestId('field-name')).toBeInTheDocument()
expect(screen.getByTestId('field-email')).toBeInTheDocument()
@@ -637,7 +666,9 @@ describe('Options', () => {
onSubmit: vi.fn(),
}
render(<Options {...props} />)
render(<Options {...props} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
expect(screen.getByTestId('custom-action')).toBeInTheDocument()
})
@@ -654,7 +685,9 @@ describe('Options', () => {
onSubmit: mockOnSubmit,
}
const { container } = render(<Options {...props} />)
const { container } = render(<Options {...props} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
const form = container.querySelector('form')!
const submitEvent = new Event('submit', { bubbles: true, cancelable: true })
const preventDefaultSpy = vi.spyOn(submitEvent, 'preventDefault')
@@ -674,7 +707,9 @@ describe('Options', () => {
onSubmit: mockOnSubmit,
}
const { container } = render(<Options {...props} />)
const { container } = render(<Options {...props} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
const form = container.querySelector('form')!
const submitEvent = new Event('submit', { bubbles: true, cancelable: true })
const stopPropagationSpy = vi.spyOn(submitEvent, 'stopPropagation')
@@ -694,7 +729,9 @@ describe('Options', () => {
onSubmit: mockOnSubmit,
}
const { container } = render(<Options {...props} />)
const { container } = render(<Options {...props} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
const form = container.querySelector('form')!
fireEvent.submit(form)
@@ -719,7 +756,9 @@ describe('Options', () => {
onSubmit: mockOnSubmit,
}
const { container } = render(<Options {...props} />)
const { container } = render(<Options {...props} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
const form = container.querySelector('form')!
fireEvent.submit(form)
@@ -743,7 +782,9 @@ describe('Options', () => {
onSubmit: vi.fn(),
}
const { container } = render(<Options {...props} />)
const { container } = render(<Options {...props} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
const form = container.querySelector('form')!
fireEvent.submit(form)
@@ -770,7 +811,9 @@ describe('Options', () => {
onSubmit: vi.fn(),
}
const { container } = render(<Options {...props} />)
const { container } = render(<Options {...props} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
const form = container.querySelector('form')!
fireEvent.submit(form)
@@ -801,7 +844,9 @@ describe('Options', () => {
onSubmit: vi.fn(),
}
const { container } = render(<Options {...props} />)
const { container } = render(<Options {...props} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
const form = container.querySelector('form')!
fireEvent.submit(form)
@@ -829,7 +874,9 @@ describe('Options', () => {
onSubmit: vi.fn(),
}
const { container } = render(<Options {...props} />)
const { container } = render(<Options {...props} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
const form = container.querySelector('form')!
fireEvent.submit(form)
@@ -855,7 +902,9 @@ describe('Options', () => {
onSubmit: vi.fn(),
}
render(<Options {...props} />)
render(<Options {...props} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
expect(screen.getByTestId('field-first')).toBeInTheDocument()
expect(screen.getByTestId('field-second')).toBeInTheDocument()
@@ -883,7 +932,9 @@ describe('Options', () => {
onSubmit: vi.fn(),
}
render(<Options {...props} />)
render(<Options {...props} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
expect(screen.getByTestId('field-label-test')).toHaveTextContent('Test Label')
expect(screen.getByTestId('field-type-test')).toHaveTextContent(BaseFieldType.textInput)
@@ -901,7 +952,9 @@ describe('Options', () => {
onSubmit: vi.fn(),
}
const { container } = render(<Options {...props} />)
const { container } = render(<Options {...props} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
expect(container.querySelector('form')).toBeInTheDocument()
})
@@ -915,7 +968,9 @@ describe('Options', () => {
onSubmit: vi.fn(),
}
render(<Options {...props} />)
render(<Options {...props} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
expect(screen.queryByTestId(/^field-/)).not.toBeInTheDocument()
})
@@ -942,7 +997,9 @@ describe('Options', () => {
onSubmit: vi.fn(),
}
render(<Options {...props} />)
render(<Options {...props} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
expect(screen.getByTestId('field-text')).toBeInTheDocument()
expect(screen.getByTestId('field-paragraph')).toBeInTheDocument()
@@ -963,7 +1020,9 @@ describe('Options', () => {
onSubmit: vi.fn(),
}
render(<Options {...props} />)
render(<Options {...props} />, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
})
expect(screen.getAllByTestId(/^field-field_/)).toHaveLength(20)
})
@@ -4,9 +4,9 @@ import { fireEvent, screen, waitFor } from '@testing-library/react'
import { NuqsTestingAdapter } from 'nuqs/adapters/testing'
import * as React from 'react'
import { beforeEach, describe, expect, it, vi } from 'vite-plus/test'
import { renderEditor as renderWithoutPricing } from '@/app/components/workflow/editor-resource/__tests__/render'
import { seedAccountProfileQuery } from '@/test/console/account-profile'
import { seedFeatures, seedSystemFeatures } from '@/test/console/query-data'
import { render as renderWithoutPricing } from '@/test/console/render'
import Publisher from '../index'
import { Popup } from '../popup'
@@ -219,7 +219,10 @@ const renderWithQueryClient = (ui: React.ReactElement) => {
function render(...args: Parameters<typeof renderWithoutPricing>) {
args[0] = <NuqsTestingAdapter onUrlUpdate={onPricingUrlUpdate}>{args[0]}</NuqsTestingAdapter>
return renderWithoutPricing(...args)
return renderWithoutPricing(args[0], {
...args[1],
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
}
vi.mock('@/next/navigation', () => ({ useParams: () => ({ datasetId: 'test-dataset-id' }) }))
@@ -556,35 +559,6 @@ describe('publisher', () => {
})
})
})
it('should publish as template with empty pipeline id fallback', async () => {
mockPublishedAt.mockReturnValue(1700000000)
mockPipelineId.mockReturnValue(undefined as unknown as string)
mockPublishAsCustomizedPipeline.mockResolvedValue({})
renderWithQueryClient(<Publisher />)
fireEvent.click(screen.getByText('workflow.common.publish'))
const publishAsButton = screen
.getAllByRole('button')
.find((btn) => btn.textContent?.includes('pipeline.common.publishAs'))
fireEvent.click(publishAsButton!)
await waitFor(() => {
expect(screen.getByTestId('publish-as-knowledge-pipeline-modal')).toBeInTheDocument()
})
fireEvent.click(screen.getByTestId('modal-confirm'))
await waitFor(() => {
expect(mockPublishAsCustomizedPipeline).toHaveBeenCalledWith({
pipelineId: '',
name: 'Test Pipeline',
icon_info: { type: 'emoji', emoji: '📚', background: '#fff' },
description: 'Test description',
})
})
})
})
describe('API Calls and Async Operations', () => {
@@ -1,8 +1,8 @@
import { fireEvent, screen, waitFor } from '@testing-library/react'
import { NuqsTestingAdapter } from 'nuqs/adapters/testing'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vite-plus/test'
import { renderEditor as renderWithConsoleState } from '@/app/components/workflow/editor-resource/__tests__/render'
import { createConsoleQueryWrapper } from '@/test/console/query-data'
import { render as renderWithConsoleState } from '@/test/console/render'
import { Popup } from '../popup'
const onPricingUrlUpdate = vi.hoisted(() => vi.fn())
@@ -14,7 +14,10 @@ const renderWithoutPricing = (ui: React.ReactElement) => {
systemFeatures: { deployment_edition: 'CLOUD' },
features: { knowledge_pipeline: { publish_enabled: mockIsAllowPublishAsCustom } },
})
return renderWithConsoleState(ui, { wrapper })
return renderWithConsoleState(ui, {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-1' },
wrapper,
})
}
const mockPublishWorkflow = vi.fn().mockResolvedValue({ created_at: '2024-01-01T00:00:00Z' })
@@ -1,8 +1,9 @@
import type { ReactNode } from 'react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { act, renderHook, waitFor } from '@testing-library/react'
import { act, waitFor } from '@testing-library/react'
import { createElement } from 'react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vite-plus/test'
import { renderEditorHook as renderHook } from '@/app/components/workflow/editor-resource/__tests__/render'
import { useDSLByCanEdit } from '../use-DSL'
const toastMocks = vi.hoisted(() => ({
@@ -47,7 +48,8 @@ vi.mock('@/app/components/workflow/store', () => ({
}))
const mockExportPipelineConfig = vi.fn()
vi.mock('@/service/console', () => ({
vi.mock('@/service/console', async (importOriginal) => ({
...(await importOriginal<typeof import('@/service/console')>()),
consoleClient: {
rag: {
pipelines: {
@@ -140,7 +142,10 @@ describe('useDSLByCanEdit', () => {
callback.onError()
return null
})
const { result } = renderHook(() => useDSLByCanEdit(true), { wrapper })
const { result } = renderHook(() => useDSLByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
wrapper,
})
await act(async () => {
expect(await result.current.handleExportDSL()).toBe(false)
})
@@ -152,7 +157,10 @@ describe('useDSLByCanEdit', () => {
it('allows export when draft synchronization is skipped for read-only access', async () => {
mockDoSyncWorkflowDraft.mockResolvedValue(null)
const { result } = renderHook(() => useDSLByCanEdit(false), { wrapper })
const { result } = renderHook(() => useDSLByCanEdit(false), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
wrapper,
})
await act(async () => {
expect(await result.current.handleExportDSL()).toBe(true)
})
@@ -160,20 +168,11 @@ describe('useDSLByCanEdit', () => {
})
describe('handleExportDSL', () => {
it('should return early when pipelineId is not set', async () => {
mockGetState.mockReturnValue({ pipelineId: null, knowledgeName: 'test' })
const { result } = renderHook(() => useDSLByCanEdit(true), { wrapper })
await act(async () => {
await result.current.handleExportDSL()
})
expect(mockDoSyncWorkflowDraft).not.toHaveBeenCalled()
})
it('should create and download file', async () => {
const { result } = renderHook(() => useDSLByCanEdit(true), { wrapper })
const { result } = renderHook(() => useDSLByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
wrapper,
})
await act(async () => {
await result.current.handleExportDSL()
@@ -183,7 +182,10 @@ describe('useDSLByCanEdit', () => {
})
it('should set correct download filename', async () => {
const { result } = renderHook(() => useDSLByCanEdit(true), { wrapper })
const { result } = renderHook(() => useDSLByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
wrapper,
})
await act(async () => {
await result.current.handleExportDSL()
@@ -197,7 +199,10 @@ describe('useDSLByCanEdit', () => {
})
it('should pass blob data to downloadBlob', async () => {
const { result } = renderHook(() => useDSLByCanEdit(true), { wrapper })
const { result } = renderHook(() => useDSLByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
wrapper,
})
await act(async () => {
await result.current.handleExportDSL()
@@ -213,7 +218,10 @@ describe('useDSLByCanEdit', () => {
it('should handle export error', async () => {
mockExportPipelineConfig.mockRejectedValue(new Error('Export failed'))
const { result } = renderHook(() => useDSLByCanEdit(true), { wrapper })
const { result } = renderHook(() => useDSLByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
wrapper,
})
await act(async () => {
expect(await result.current.handleExportDSL()).toBe(false)
@@ -228,7 +236,10 @@ describe('useDSLByCanEdit', () => {
})
it('should pass include parameter', async () => {
const { result } = renderHook(() => useDSLByCanEdit(true), { wrapper })
const { result } = renderHook(() => useDSLByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
wrapper,
})
await act(async () => {
await result.current.handleExportDSL(true)
@@ -244,22 +255,13 @@ describe('useDSLByCanEdit', () => {
})
describe('exportCheck', () => {
it('should return early when pipelineId is not set', async () => {
mockGetState.mockReturnValue({ pipelineId: null })
const { result } = renderHook(() => useDSLByCanEdit(true), { wrapper })
await act(async () => {
await result.current.exportCheck()
})
expect(mockFetchWorkflowDraft).not.toHaveBeenCalled()
})
it('should call handleExportDSL directly when no secret variables', async () => {
mockFetchWorkflowDraft.mockResolvedValue({ environment_variables: [] })
const { result } = renderHook(() => useDSLByCanEdit(true), { wrapper })
const { result } = renderHook(() => useDSLByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
wrapper,
})
await act(async () => {
await result.current.exportCheck()
@@ -277,7 +279,10 @@ describe('useDSLByCanEdit', () => {
const secretVars = [{ value_type: 'secret', name: 'API_KEY' }]
mockFetchWorkflowDraft.mockResolvedValue({ environment_variables: secretVars })
const { result } = renderHook(() => useDSLByCanEdit(true), { wrapper })
const { result } = renderHook(() => useDSLByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
wrapper,
})
await act(async () => {
await result.current.exportCheck()
@@ -296,7 +301,10 @@ describe('useDSLByCanEdit', () => {
it('should handle export check error', async () => {
mockFetchWorkflowDraft.mockRejectedValue(new Error('Fetch failed'))
const { result } = renderHook(() => useDSLByCanEdit(true), { wrapper })
const { result } = renderHook(() => useDSLByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
wrapper,
})
await act(async () => {
await result.current.exportCheck()
@@ -1,69 +0,0 @@
import { renderHook } from '@testing-library/react'
import { describe, expect, it, vi } from 'vite-plus/test'
import { useConfigsMap } from '../use-configs-map'
const mockPipelineId = 'pipeline-xyz'
const mockFileUploadConfig = { max_size: 10 }
vi.mock('@/app/components/workflow/store', () => ({
useStore: (selector: (state: Record<string, unknown>) => unknown) => {
const state = {
pipelineId: mockPipelineId,
fileUploadConfig: mockFileUploadConfig,
}
return selector(state)
},
}))
vi.mock('@/types/app', () => ({
Resolution: { high: 'high' },
TransferMethod: { local_file: 'local_file', remote_url: 'remote_url' },
}))
vi.mock('@/types/common', () => ({
FlowType: { ragPipeline: 'rag-pipeline' },
}))
describe('useConfigsMap', () => {
it('should return flowId from pipelineId', () => {
const { result } = renderHook(() => useConfigsMap())
expect(result.current.flowId).toBe('pipeline-xyz')
})
it('should return ragPipeline as flowType', () => {
const { result } = renderHook(() => useConfigsMap())
expect(result.current.flowType).toBe('rag-pipeline')
})
it('should include file settings with image disabled', () => {
const { result } = renderHook(() => useConfigsMap())
expect(result.current.fileSettings.image.enabled).toBe(false)
})
it('should set image detail to high resolution', () => {
const { result } = renderHook(() => useConfigsMap())
expect(result.current.fileSettings.image.detail).toBe('high')
})
it('should set image number_limits to 3', () => {
const { result } = renderHook(() => useConfigsMap())
expect(result.current.fileSettings.image.number_limits).toBe(3)
})
it('should include both transfer methods for image', () => {
const { result } = renderHook(() => useConfigsMap())
expect(result.current.fileSettings.image.transfer_methods).toEqual(['local_file', 'remote_url'])
})
it('should pass through fileUploadConfig from store', () => {
const { result } = renderHook(() => useConfigsMap())
expect(result.current.fileSettings.fileUploadConfig).toEqual({ max_size: 10 })
})
})
@@ -0,0 +1,53 @@
import { renderHook } from '@testing-library/react'
import { describe, expect, it, vi } from 'vite-plus/test'
import { useFileSettings } from '../use-file-settings'
const mockPipelineId = 'pipeline-xyz'
const mockFileUploadConfig = { max_size: 10 }
vi.mock('@/app/components/workflow/store', () => ({
useStore: (selector: (state: Record<string, unknown>) => unknown) => {
const state = {
pipelineId: mockPipelineId,
fileUploadConfig: mockFileUploadConfig,
}
return selector(state)
},
}))
vi.mock('@/types/app', () => ({
Resolution: { high: 'high' },
TransferMethod: { local_file: 'local_file', remote_url: 'remote_url' },
}))
describe('useFileSettings', () => {
it('should include file settings with image disabled', () => {
const { result } = renderHook(() => useFileSettings())
expect(result.current.image.enabled).toBe(false)
})
it('should set image detail to high resolution', () => {
const { result } = renderHook(() => useFileSettings())
expect(result.current.image.detail).toBe('high')
})
it('should set image number_limits to 3', () => {
const { result } = renderHook(() => useFileSettings())
expect(result.current.image.number_limits).toBe(3)
})
it('should include both transfer methods for image', () => {
const { result } = renderHook(() => useFileSettings())
expect(result.current.image.transfer_methods).toEqual(['local_file', 'remote_url'])
})
it('should pass through fileUploadConfig from store', () => {
const { result } = renderHook(() => useFileSettings())
expect(result.current.fileUploadConfig).toEqual({ max_size: 10 })
})
})
@@ -1,5 +1,5 @@
import { renderHook } from '@testing-library/react'
import { describe, expect, it, vi } from 'vite-plus/test'
import { renderEditorHook as renderHook } from '@/app/components/workflow/editor-resource/__tests__/render'
import { useGetRunAndTraceUrl } from '../use-get-run-and-trace-url'
vi.mock('@/app/components/workflow/store', () => ({
@@ -12,21 +12,27 @@ vi.mock('@/app/components/workflow/store', () => ({
describe('useGetRunAndTraceUrl', () => {
it('should generate correct runUrl', () => {
const { result } = renderHook(() => useGetRunAndTraceUrl())
const { result } = renderHook(() => useGetRunAndTraceUrl(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-test-123' },
})
const { runUrl } = result.current.getWorkflowRunAndTraceUrl('run-abc')
expect(runUrl).toBe('/rag/pipelines/pipeline-test-123/workflow-runs/run-abc')
})
it('should generate correct traceUrl', () => {
const { result } = renderHook(() => useGetRunAndTraceUrl())
const { result } = renderHook(() => useGetRunAndTraceUrl(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-test-123' },
})
const { traceUrl } = result.current.getWorkflowRunAndTraceUrl('run-abc')
expect(traceUrl).toBe('/rag/pipelines/pipeline-test-123/workflow-runs/run-abc/node-executions')
})
it('should handle different runIds', () => {
const { result } = renderHook(() => useGetRunAndTraceUrl())
const { result } = renderHook(() => useGetRunAndTraceUrl(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'pipeline-test-123' },
})
const r1 = result.current.getWorkflowRunAndTraceUrl('id-1')
const r2 = result.current.getWorkflowRunAndTraceUrl('id-2')
@@ -1,98 +0,0 @@
import { renderHook } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vite-plus/test'
import { useInspectVarsCrud } from '../use-inspect-vars-crud'
// Mock return value for useInspectVarsCrudCommon
const mockApis = {
hasNodeInspectVars: vi.fn(),
hasSetInspectVar: vi.fn(),
fetchInspectVarValue: vi.fn(),
editInspectVarValue: vi.fn(),
renameInspectVarName: vi.fn(),
appendNodeInspectVars: vi.fn(),
deleteInspectVar: vi.fn(),
deleteNodeInspectorVars: vi.fn(),
deleteAllInspectorVars: vi.fn(),
isInspectVarEdited: vi.fn(),
resetToLastRunVar: vi.fn(),
invalidateSysVarValues: vi.fn(),
resetConversationVar: vi.fn(),
invalidateConversationVarValues: vi.fn(),
}
const mockUseInspectVarsCrudCommon = vi.fn(() => mockApis)
vi.mock('@/app/components/workflow/hooks/use-inspect-vars-crud-common', () => ({
useInspectVarsCrudCommon: (...args: Parameters<typeof mockUseInspectVarsCrudCommon>) =>
mockUseInspectVarsCrudCommon(...args),
}))
const mockConfigsMap = {
flowId: 'pipeline-123',
flowType: 'rag_pipeline',
fileSettings: {
image: { enabled: false },
fileUploadConfig: {},
},
}
vi.mock('../use-configs-map', () => ({
useConfigsMap: () => mockConfigsMap,
}))
describe('useInspectVarsCrud', () => {
beforeEach(() => {
vi.clearAllMocks()
})
// Verify the hook composes useConfigsMap with useInspectVarsCrudCommon
describe('Composition', () => {
it('should pass configsMap to useInspectVarsCrudCommon', () => {
renderHook(() => useInspectVarsCrud())
expect(mockUseInspectVarsCrudCommon).toHaveBeenCalledWith(
expect.objectContaining({
flowId: 'pipeline-123',
flowType: 'rag_pipeline',
}),
)
})
it('should return all APIs from useInspectVarsCrudCommon', () => {
const { result } = renderHook(() => useInspectVarsCrud())
expect(result.current.hasNodeInspectVars).toBe(mockApis.hasNodeInspectVars)
expect(result.current.fetchInspectVarValue).toBe(mockApis.fetchInspectVarValue)
expect(result.current.editInspectVarValue).toBe(mockApis.editInspectVarValue)
expect(result.current.deleteInspectVar).toBe(mockApis.deleteInspectVar)
expect(result.current.deleteAllInspectorVars).toBe(mockApis.deleteAllInspectorVars)
expect(result.current.resetToLastRunVar).toBe(mockApis.resetToLastRunVar)
expect(result.current.resetConversationVar).toBe(mockApis.resetConversationVar)
})
})
// Verify the hook spreads all returned properties
describe('API Surface', () => {
it('should expose all expected API methods', () => {
const { result } = renderHook(() => useInspectVarsCrud())
const expectedKeys = [
'hasNodeInspectVars',
'hasSetInspectVar',
'fetchInspectVarValue',
'editInspectVarValue',
'renameInspectVarName',
'appendNodeInspectVars',
'deleteInspectVar',
'deleteNodeInspectorVars',
'deleteAllInspectorVars',
'isInspectVarEdited',
'resetToLastRunVar',
'invalidateSysVarValues',
'resetConversationVar',
'invalidateConversationVarValues',
]
for (const key of expectedKeys) expect(result.current).toHaveProperty(key)
})
})
})
@@ -1,6 +1,6 @@
import { renderHook } from '@testing-library/react'
import { act } from 'react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vite-plus/test'
import { renderEditorHook as renderHook } from '@/app/components/workflow/editor-resource/__tests__/render'
import { createHttpError } from '@/service/http-error'
import { useNodesSyncDraft } from '../use-nodes-sync-draft'
@@ -30,7 +30,7 @@ vi.mock('@/app/components/workflow/hooks/use-workflow', () => ({
const mockSyncWorkflowDraft = vi.fn()
// Stand in for the draft sync request at the client boundary: record the request it sends
// and validate rejections against the contract, as the console link does.
vi.mock('@/service/console', async () => {
vi.mock('@/service/console', async (importOriginal) => {
const { rag } = await import('@dify/contracts/api/console/rag/orpc.gen')
const { validateDefinedErrors } = await import('@/service/console/defined-errors')
const validate = validateDefinedErrors({ rag })
@@ -46,6 +46,7 @@ vi.mock('@/service/console', async () => {
}),
})
return {
...(await importOriginal<typeof import('@/service/console')>()),
consoleClient: { rag: { pipelines: { byPipelineId: { workflows: { draft: { post } } } } } },
}
})
@@ -57,12 +58,14 @@ vi.mock('../use-pipeline-refresh-draft', () => ({
}),
}))
vi.mock('@/config', () => ({
vi.mock('@/config', async (importOriginal) => ({
...(await importOriginal<typeof import('@/config')>()),
API_PREFIX: '/api',
}))
const mockPostWithKeepalive = vi.fn()
vi.mock('@/service/fetch', () => ({
vi.mock('@/service/fetch', async (importOriginal) => ({
...(await importOriginal<typeof import('@/service/fetch')>()),
postWithKeepalive: (...args: unknown[]) => mockPostWithKeepalive(...args),
}))
@@ -103,14 +106,18 @@ describe('useNodesSyncDraft', () => {
describe('hook initialization', () => {
it('should return doSyncWorkflowDraft function', () => {
const { result } = renderHook(() => useNodesSyncDraft())
const { result } = renderHook(() => useNodesSyncDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
expect(result.current.doSyncWorkflowDraft).toBeDefined()
expect(typeof result.current.doSyncWorkflowDraft).toBe('function')
})
it('should return syncWorkflowDraftWhenPageClose function', () => {
const { result } = renderHook(() => useNodesSyncDraft())
const { result } = renderHook(() => useNodesSyncDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
expect(result.current.syncWorkflowDraftWhenPageClose).toBeDefined()
expect(typeof result.current.syncWorkflowDraftWhenPageClose).toBe('function')
@@ -121,7 +128,9 @@ describe('useNodesSyncDraft', () => {
it('should not call postWithKeepalive when nodes are read only', () => {
mockGetNodesReadOnly.mockReturnValue(true)
const { result } = renderHook(() => useNodesSyncDraft())
const { result } = renderHook(() => useNodesSyncDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
act(() => {
result.current.syncWorkflowDraftWhenPageClose()
@@ -136,7 +145,9 @@ describe('useNodesSyncDraft', () => {
{ id: 'node-1', data: { type: 'start' }, position: { x: 0, y: 0 } },
])
const { result } = renderHook(() => useNodesSyncDraft())
const { result } = renderHook(() => useNodesSyncDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
act(() => {
result.current.syncWorkflowDraftWhenPageClose()
@@ -151,27 +162,12 @@ describe('useNodesSyncDraft', () => {
)
})
it('should not call postWithKeepalive when pipelineId is missing', () => {
mockWorkflowStoreGetState.mockReturnValue({
pipelineId: undefined,
environmentVariables: [],
syncWorkflowDraftHash: 'test-hash',
ragPipelineVariables: [],
})
const { result } = renderHook(() => useNodesSyncDraft())
act(() => {
result.current.syncWorkflowDraftWhenPageClose()
})
expect(mockPostWithKeepalive).not.toHaveBeenCalled()
})
it('should not call postWithKeepalive when nodes array is empty', () => {
mockGetNodes.mockReturnValue([])
const { result } = renderHook(() => useNodesSyncDraft())
const { result } = renderHook(() => useNodesSyncDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
act(() => {
result.current.syncWorkflowDraftWhenPageClose()
@@ -185,7 +181,9 @@ describe('useNodesSyncDraft', () => {
{ id: 'node-1', data: { type: 'start', _isTempNode: true }, position: { x: 0, y: 0 } },
])
const { result } = renderHook(() => useNodesSyncDraft())
const { result } = renderHook(() => useNodesSyncDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
act(() => {
result.current.syncWorkflowDraftWhenPageClose()
@@ -199,7 +197,9 @@ describe('useNodesSyncDraft', () => {
{ id: 'node-1', data: { type: 'start', _privateData: 'secret' }, position: { x: 0, y: 0 } },
])
const { result } = renderHook(() => useNodesSyncDraft())
const { result } = renderHook(() => useNodesSyncDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
act(() => {
result.current.syncWorkflowDraftWhenPageClose()
@@ -215,7 +215,9 @@ describe('useNodesSyncDraft', () => {
it('should not sync when nodes are read only', async () => {
mockGetNodesReadOnly.mockReturnValue(true)
const { result } = renderHook(() => useNodesSyncDraft())
const { result } = renderHook(() => useNodesSyncDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.doSyncWorkflowDraft()
@@ -230,7 +232,9 @@ describe('useNodesSyncDraft', () => {
{ id: 'node-1', data: { type: 'start' }, position: { x: 0, y: 0 } },
])
const { result } = renderHook(() => useNodesSyncDraft())
const { result } = renderHook(() => useNodesSyncDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.doSyncWorkflowDraft()
@@ -258,7 +262,9 @@ describe('useNodesSyncDraft', () => {
transform: [10, 20, 1.5],
})
const { result } = renderHook(() => useNodesSyncDraft())
const { result } = renderHook(() => useNodesSyncDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
let syncPromise!: ReturnType<typeof result.current.doSyncWorkflowDraft>
act(() => {
@@ -310,7 +316,9 @@ describe('useNodesSyncDraft', () => {
.mockResolvedValueOnce({ hash: 'hash-after-first-sync', updated_at: 'first-update' })
.mockResolvedValueOnce({ hash: 'hash-after-second-sync', updated_at: 'second-update' })
const { result } = renderHook(() => useNodesSyncDraft())
const { result } = renderHook(() => useNodesSyncDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
let firstSync!: ReturnType<typeof result.current.doSyncWorkflowDraft>
let secondSync!: ReturnType<typeof result.current.doSyncWorkflowDraft>
@@ -343,7 +351,9 @@ describe('useNodesSyncDraft', () => {
{ id: 'node-1', data: { type: 'start' }, position: { x: 0, y: 0 } },
])
const { result } = renderHook(() => useNodesSyncDraft())
const { result } = renderHook(() => useNodesSyncDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.doSyncWorkflowDraft()
@@ -365,7 +375,9 @@ describe('useNodesSyncDraft', () => {
])
const onSuccess = vi.fn()
const { result } = renderHook(() => useNodesSyncDraft())
const { result } = renderHook(() => useNodesSyncDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.doSyncWorkflowDraft(false, { onSuccess })
@@ -381,7 +393,9 @@ describe('useNodesSyncDraft', () => {
])
const onSettled = vi.fn()
const { result } = renderHook(() => useNodesSyncDraft())
const { result } = renderHook(() => useNodesSyncDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.doSyncWorkflowDraft(false, { onSettled })
@@ -398,7 +412,9 @@ describe('useNodesSyncDraft', () => {
mockSyncWorkflowDraft.mockRejectedValue(new Error('Sync failed'))
const onError = vi.fn()
const { result } = renderHook(() => useNodesSyncDraft())
const { result } = renderHook(() => useNodesSyncDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.doSyncWorkflowDraft(false, { onError })
@@ -424,7 +440,9 @@ describe('useNodesSyncDraft', () => {
setDraftUpdatedAt: mockSetDraftUpdatedAt,
})
const { result } = renderHook(() => useNodesSyncDraft())
const { result } = renderHook(() => useNodesSyncDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.doSyncWorkflowDraft()
@@ -447,7 +465,9 @@ describe('useNodesSyncDraft', () => {
})
mockSyncWorkflowDraft.mockRejectedValue(mockJsonError)
const { result } = renderHook(() => useNodesSyncDraft())
const { result } = renderHook(() => useNodesSyncDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.doSyncWorkflowDraft(false)
@@ -471,7 +491,9 @@ describe('useNodesSyncDraft', () => {
})
mockSyncWorkflowDraft.mockRejectedValue(mockJsonError)
const { result } = renderHook(() => useNodesSyncDraft())
const { result } = renderHook(() => useNodesSyncDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.doSyncWorkflowDraft(true)
@@ -494,7 +516,9 @@ describe('useNodesSyncDraft', () => {
{ id: 'node-1', data: { type: 'start' }, position: { x: 0, y: 0 } },
])
const { result } = renderHook(() => useNodesSyncDraft())
const { result } = renderHook(() => useNodesSyncDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
act(() => {
result.current.syncWorkflowDraftWhenPageClose()
@@ -517,7 +541,9 @@ describe('useNodesSyncDraft', () => {
{ id: 'node-1', data: { type: 'start' }, position: { x: 0, y: 0 } },
])
const { result } = renderHook(() => useNodesSyncDraft())
const { result } = renderHook(() => useNodesSyncDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
act(() => {
result.current.syncWorkflowDraftWhenPageClose()
@@ -540,7 +566,9 @@ describe('useNodesSyncDraft', () => {
{ id: 'node-1', data: { type: 'start' }, position: { x: 0, y: 0 } },
])
const { result } = renderHook(() => useNodesSyncDraft())
const { result } = renderHook(() => useNodesSyncDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
act(() => {
result.current.syncWorkflowDraftWhenPageClose()
@@ -555,7 +583,9 @@ describe('useNodesSyncDraft', () => {
{ id: 'node-1', data: { type: 'start' }, position: { x: 0, y: 0 } },
])
const { result } = renderHook(() => useNodesSyncDraft())
const { result } = renderHook(() => useNodesSyncDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
act(() => {
result.current.syncWorkflowDraftWhenPageClose()
@@ -582,7 +612,9 @@ describe('useNodesSyncDraft', () => {
{ id: 'node-1', data: { type: 'start' }, position: { x: 0, y: 0 } },
])
const { result } = renderHook(() => useNodesSyncDraft())
const { result } = renderHook(() => useNodesSyncDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
act(() => {
result.current.syncWorkflowDraftWhenPageClose()
@@ -4,6 +4,7 @@ import { act, renderHook, waitFor } from '@testing-library/react'
import { createElement } from 'react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vite-plus/test'
import { WorkflowContext } from '@/app/components/workflow/context'
import { EditorResourceScope } from '@/app/components/workflow/editor-resource/provider'
import { createWorkflowStore } from '@/app/components/workflow/store'
import { consoleQuery } from '@/service/console'
import { createDatasourceProvider } from '../../__tests__/datasource-fixtures'
@@ -20,20 +21,24 @@ vi.mock('@/service/use-workflow', () => ({ useWorkflowConfig: mocks.workflowConf
const catalog = consoleQuery.rag.pipelines.datasourcePlugins.get
describe('usePipelineConfig', () => {
let pipelineId: string
let client: QueryClient
let store: ReturnType<typeof createWorkflowStore>
const wrapper = ({ children }: PropsWithChildren) =>
createElement(
QueryClientProvider,
{ client },
createElement(WorkflowContext.Provider, { value: store }, children),
createElement(EditorResourceScope, {
identity: { kind: 'knowledge-pipeline', pipelineId },
children: createElement(WorkflowContext.Provider, { value: store }, children),
}),
)
beforeEach(() => {
vi.clearAllMocks()
client = new QueryClient({ defaultOptions: { queries: { retry: false, gcTime: Infinity } } })
store = createWorkflowStore({ injectWorkflowStoreSliceFn: createRagPipelineSliceSlice })
store.setState({ pipelineId: 'pipeline-1' })
pipelineId = 'pipeline-1'
mocks.request.mockResolvedValue(Response.json([]))
})
afterEach(() => client.clear())
@@ -74,7 +79,7 @@ describe('usePipelineConfig', () => {
})
it('does not request or hydrate datasource state before a pipeline is available', () => {
store.setState({ pipelineId: '' })
pipelineId = ''
client.setQueryData(catalog.queryKey(), [createDatasourceProvider()])
renderHook(usePipelineConfig, { wrapper })
@@ -84,12 +89,13 @@ describe('usePipelineConfig', () => {
})
it('fetches and synchronizes when the pipeline becomes available', async () => {
store.setState({ pipelineId: '' })
pipelineId = ''
const providers = [createDatasourceProvider()]
mocks.request.mockResolvedValue(Response.json(providers))
renderHook(usePipelineConfig, { wrapper })
const { rerender } = renderHook(usePipelineConfig, { wrapper })
act(() => store.setState({ pipelineId: 'pipeline-2' }))
pipelineId = 'pipeline-2'
rerender()
await waitFor(() => expect(store.getState().dataSourceList).toEqual(providers))
})
@@ -1,5 +1,6 @@
import { renderHook, waitFor } from '@testing-library/react'
import { waitFor } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vite-plus/test'
import { renderEditorHook as renderHook } from '@/app/components/workflow/editor-resource/__tests__/render'
import { createHttpError } from '@/service/http-error'
import { usePipelineInit } from '../use-pipeline-init'
@@ -90,17 +91,20 @@ describe('usePipelineInit', () => {
describe('hook initialization', () => {
it('should return data and isLoading', async () => {
const { result } = renderHook(() => usePipelineInit())
const { result } = renderHook(() => usePipelineInit(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
expect(result.current.isLoading).toBe(true)
expect(result.current.data).toBeUndefined()
})
it('should set pipelineId in workflow store on mount', () => {
renderHook(() => usePipelineInit())
it('should initialize the knowledge presentation metadata', () => {
renderHook(() => usePipelineInit(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
expect(mockWorkflowStoreSetState).toHaveBeenCalledWith({
pipelineId: 'test-pipeline-id',
knowledgeName: 'Test Knowledge',
knowledgeIcon: { icon: 'test-icon' },
})
@@ -109,7 +113,9 @@ describe('usePipelineInit', () => {
describe('data fetching', () => {
it('should fetch workflow draft on mount', async () => {
renderHook(() => usePipelineInit())
renderHook(() => usePipelineInit(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await waitFor(() => {
expect(mockFetchWorkflowDraft).toHaveBeenCalledWith(
@@ -119,7 +125,9 @@ describe('usePipelineInit', () => {
})
it('should set data after successful fetch', async () => {
const { result } = renderHook(() => usePipelineInit())
const { result } = renderHook(() => usePipelineInit(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await waitFor(() => {
expect(result.current.data).toBeDefined()
@@ -127,7 +135,9 @@ describe('usePipelineInit', () => {
})
it('should set isLoading to false after fetch', async () => {
const { result } = renderHook(() => usePipelineInit())
const { result } = renderHook(() => usePipelineInit(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await waitFor(() => {
expect(result.current.isLoading).toBe(false)
@@ -135,7 +145,9 @@ describe('usePipelineInit', () => {
})
it('should set draft updated at', async () => {
renderHook(() => usePipelineInit())
renderHook(() => usePipelineInit(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await waitFor(() => {
expect(mockSetDraftUpdatedAt).toHaveBeenCalledWith('2024-01-01T00:00:00Z')
@@ -143,7 +155,9 @@ describe('usePipelineInit', () => {
})
it('should set tool published status', async () => {
renderHook(() => usePipelineInit())
renderHook(() => usePipelineInit(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await waitFor(() => {
expect(mockSetToolPublished).toHaveBeenCalledWith(true)
@@ -151,7 +165,9 @@ describe('usePipelineInit', () => {
})
it('should set sync hash', async () => {
renderHook(() => usePipelineInit())
renderHook(() => usePipelineInit(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await waitFor(() => {
expect(mockSetSyncWorkflowDraftHash).toHaveBeenCalledWith('test-hash')
@@ -159,7 +175,9 @@ describe('usePipelineInit', () => {
})
it('should mark workflow data as loaded after a successful fetch', async () => {
renderHook(() => usePipelineInit())
renderHook(() => usePipelineInit(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await waitFor(() => {
expect(mockWorkflowStoreSetState).toHaveBeenCalledWith({ isWorkflowDataLoaded: true })
@@ -181,7 +199,9 @@ describe('usePipelineInit', () => {
rag_pipeline_variables: [],
})
renderHook(() => usePipelineInit())
renderHook(() => usePipelineInit(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await waitFor(() => {
expect(mockSetEnvSecrets).toHaveBeenCalledWith({ 'env-1': 'secret-value' })
@@ -201,7 +221,9 @@ describe('usePipelineInit', () => {
rag_pipeline_variables: [],
})
renderHook(() => usePipelineInit())
renderHook(() => usePipelineInit(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await waitFor(() => {
expect(mockSetEnvironmentVariables).toHaveBeenCalledWith([
@@ -221,7 +243,9 @@ describe('usePipelineInit', () => {
rag_pipeline_variables: [],
})
renderHook(() => usePipelineInit())
renderHook(() => usePipelineInit(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await waitFor(() => {
expect(mockSetEnvSecrets).toHaveBeenCalledWith({})
@@ -241,7 +265,9 @@ describe('usePipelineInit', () => {
rag_pipeline_variables: [{ variable: 'query', type: 'text-input' }],
})
renderHook(() => usePipelineInit())
renderHook(() => usePipelineInit(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await waitFor(() => {
expect(mockSetRagPipelineVariables).toHaveBeenCalledWith([
@@ -260,7 +286,9 @@ describe('usePipelineInit', () => {
rag_pipeline_variables: undefined,
})
renderHook(() => usePipelineInit())
renderHook(() => usePipelineInit(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await waitFor(() => {
expect(mockSetRagPipelineVariables).toHaveBeenCalledWith([])
@@ -287,7 +315,9 @@ describe('usePipelineInit', () => {
rag_pipeline_variables: [],
})
renderHook(() => usePipelineInit())
renderHook(() => usePipelineInit(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await waitFor(() => {
expect(mockWorkflowStoreSetState).toHaveBeenCalledWith({
@@ -306,7 +336,9 @@ describe('usePipelineInit', () => {
mockFetchWorkflowDraft.mockRejectedValueOnce(mockJsonError)
mockSyncWorkflowDraft.mockResolvedValue({ updated_at: '2024-01-02T00:00:00Z' })
renderHook(() => usePipelineInit())
renderHook(() => usePipelineInit(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await waitFor(() => {
expect(mockSyncWorkflowDraft).toHaveBeenCalledWith({
@@ -332,7 +364,9 @@ describe('usePipelineInit', () => {
},
)
renderHook(() => usePipelineInit())
renderHook(() => usePipelineInit(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await waitFor(() => {
expect(mockFetchWorkflowDraft).toHaveBeenCalled()
@@ -1,6 +1,7 @@
import { renderHook, waitFor } from '@testing-library/react'
import { waitFor } from '@testing-library/react'
import { act } from 'react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vite-plus/test'
import { renderEditorHook as renderHook } from '@/app/components/workflow/editor-resource/__tests__/render'
import { usePipelineRefreshDraft } from '../use-pipeline-refresh-draft'
const mockWorkflowStoreGetState = vi.fn()
@@ -66,7 +67,9 @@ describe('usePipelineRefreshDraft', () => {
describe('hook initialization', () => {
it('should return handleRefreshWorkflowDraft function', () => {
const { result } = renderHook(() => usePipelineRefreshDraft())
const { result } = renderHook(() => usePipelineRefreshDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
expect(result.current.handleRefreshWorkflowDraft).toBeDefined()
expect(typeof result.current.handleRefreshWorkflowDraft).toBe('function')
@@ -75,7 +78,9 @@ describe('usePipelineRefreshDraft', () => {
describe('handleRefreshWorkflowDraft', () => {
it('should set syncing state to true at start', async () => {
const { result } = renderHook(() => usePipelineRefreshDraft())
const { result } = renderHook(() => usePipelineRefreshDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
act(() => {
result.current.handleRefreshWorkflowDraft()
@@ -85,7 +90,9 @@ describe('usePipelineRefreshDraft', () => {
})
it('should fetch workflow draft with correct URL', async () => {
const { result } = renderHook(() => usePipelineRefreshDraft())
const { result } = renderHook(() => usePipelineRefreshDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
act(() => {
result.current.handleRefreshWorkflowDraft()
@@ -97,7 +104,9 @@ describe('usePipelineRefreshDraft', () => {
})
it('should update workflow canvas with response data', async () => {
const { result } = renderHook(() => usePipelineRefreshDraft())
const { result } = renderHook(() => usePipelineRefreshDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
act(() => {
result.current.handleRefreshWorkflowDraft()
@@ -109,7 +118,9 @@ describe('usePipelineRefreshDraft', () => {
})
it('should update sync hash after fetch', async () => {
const { result } = renderHook(() => usePipelineRefreshDraft())
const { result } = renderHook(() => usePipelineRefreshDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
act(() => {
result.current.handleRefreshWorkflowDraft()
@@ -132,7 +143,9 @@ describe('usePipelineRefreshDraft', () => {
rag_pipeline_variables: [{ variable: 'query', type: 'text-input' }],
})
const { result } = renderHook(() => usePipelineRefreshDraft())
const { result } = renderHook(() => usePipelineRefreshDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
act(() => {
result.current.handleRefreshWorkflowDraft()
@@ -146,7 +159,9 @@ describe('usePipelineRefreshDraft', () => {
})
it('should set syncing state to false after completion', async () => {
const { result } = renderHook(() => usePipelineRefreshDraft())
const { result } = renderHook(() => usePipelineRefreshDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
act(() => {
result.current.handleRefreshWorkflowDraft()
@@ -171,7 +186,9 @@ describe('usePipelineRefreshDraft', () => {
],
})
const { result } = renderHook(() => usePipelineRefreshDraft())
const { result } = renderHook(() => usePipelineRefreshDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
act(() => {
result.current.handleRefreshWorkflowDraft()
@@ -196,7 +213,9 @@ describe('usePipelineRefreshDraft', () => {
],
})
const { result } = renderHook(() => usePipelineRefreshDraft())
const { result } = renderHook(() => usePipelineRefreshDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
act(() => {
result.current.handleRefreshWorkflowDraft()
@@ -221,7 +240,9 @@ describe('usePipelineRefreshDraft', () => {
environment_variables: [],
})
const { result } = renderHook(() => usePipelineRefreshDraft())
const { result } = renderHook(() => usePipelineRefreshDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
act(() => {
result.current.handleRefreshWorkflowDraft()
@@ -244,7 +265,9 @@ describe('usePipelineRefreshDraft', () => {
environment_variables: undefined,
})
const { result } = renderHook(() => usePipelineRefreshDraft())
const { result } = renderHook(() => usePipelineRefreshDraft(), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
act(() => {
result.current.handleRefreshWorkflowDraft()
@@ -1,7 +1,7 @@
import type { VersionHistory } from '@/types/workflow'
import { renderHook } from '@testing-library/react'
import { act } from 'react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vite-plus/test'
import { renderEditorHook as renderHook } from '@/app/components/workflow/editor-resource/__tests__/render'
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
import { usePipelineRunByCanEdit } from '../use-pipeline-run'
@@ -80,16 +80,10 @@ vi.mock('@/service/workflow', () => ({
const mockInvalidAllLastRun = vi.fn()
const mockInvalidateRunHistory = vi.fn()
vi.mock('@/service/use-workflow', () => ({
useInvalidAllLastRun: () => mockInvalidAllLastRun,
useInvalidateAllLastRun: () => mockInvalidAllLastRun,
useInvalidateWorkflowRunHistory: () => mockInvalidateRunHistory,
}))
vi.mock('@/types/common', () => ({
FlowType: {
ragPipeline: 'rag-pipeline',
},
}))
describe('usePipelineRunByCanEdit', () => {
const mockSetNodes = vi.fn()
const mockGetNodes = vi.fn()
@@ -149,35 +143,45 @@ describe('usePipelineRunByCanEdit', () => {
describe('hook initialization', () => {
it('should return handleBackupDraft function', () => {
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
expect(result.current.handleBackupDraft).toBeDefined()
expect(typeof result.current.handleBackupDraft).toBe('function')
})
it('should return handleLoadBackupDraft function', () => {
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
expect(result.current.handleLoadBackupDraft).toBeDefined()
expect(typeof result.current.handleLoadBackupDraft).toBe('function')
})
it('should return handleRun function', () => {
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
expect(result.current.handleRun).toBeDefined()
expect(typeof result.current.handleRun).toBe('function')
})
it('should return handleStopRun function', () => {
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
expect(result.current.handleStopRun).toBeDefined()
expect(typeof result.current.handleStopRun).toBe('function')
})
it('should return handleRestoreFromPublishedWorkflow function', () => {
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
expect(result.current.handleRestoreFromPublishedWorkflow).toBeDefined()
expect(typeof result.current.handleRestoreFromPublishedWorkflow).toBe('function')
@@ -186,7 +190,9 @@ describe('usePipelineRunByCanEdit', () => {
describe('handleBackupDraft', () => {
it('should backup draft when no backup exists', () => {
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
act(() => {
result.current.handleBackupDraft()
@@ -207,7 +213,9 @@ describe('usePipelineRunByCanEdit', () => {
setWorkflowRunningData: mockSetWorkflowRunningData,
})
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
act(() => {
result.current.handleBackupDraft()
@@ -236,7 +244,9 @@ describe('usePipelineRunByCanEdit', () => {
setWorkflowRunningData: mockSetWorkflowRunningData,
})
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
act(() => {
result.current.handleLoadBackupDraft()
@@ -262,7 +272,9 @@ describe('usePipelineRunByCanEdit', () => {
setWorkflowRunningData: mockSetWorkflowRunningData,
})
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
act(() => {
result.current.handleLoadBackupDraft()
@@ -274,7 +286,9 @@ describe('usePipelineRunByCanEdit', () => {
describe('handleStopRun', () => {
it('should call stop workflow run service', () => {
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
act(() => {
result.current.handleStopRun('task-123')
@@ -298,7 +312,9 @@ describe('usePipelineRunByCanEdit', () => {
rag_pipeline_variables: [{ variable: 'input', type: 'text-input' }],
}
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
act(() => {
result.current.handleRestoreFromPublishedWorkflow(
@@ -324,7 +340,9 @@ describe('usePipelineRunByCanEdit', () => {
rag_pipeline_variables: [],
}
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
act(() => {
result.current.handleRestoreFromPublishedWorkflow(
@@ -346,7 +364,9 @@ describe('usePipelineRunByCanEdit', () => {
rag_pipeline_variables: [{ variable: 'query', type: 'text-input' }],
}
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
act(() => {
result.current.handleRestoreFromPublishedWorkflow(
@@ -370,7 +390,9 @@ describe('usePipelineRunByCanEdit', () => {
rag_pipeline_variables: undefined,
}
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
act(() => {
result.current.handleRestoreFromPublishedWorkflow(
@@ -385,7 +407,9 @@ describe('usePipelineRunByCanEdit', () => {
describe('handleRun', () => {
it('should sync workflow draft before running', async () => {
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.handleRun({ inputs: {} })
@@ -395,7 +419,9 @@ describe('usePipelineRunByCanEdit', () => {
})
it('should reset node selection and running status', async () => {
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.handleRun({ inputs: {} })
@@ -405,7 +431,9 @@ describe('usePipelineRunByCanEdit', () => {
})
it('should clear history workflow data', async () => {
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.handleRun({ inputs: {} })
@@ -415,7 +443,9 @@ describe('usePipelineRunByCanEdit', () => {
})
it('should set initial running data', async () => {
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.handleRun({ inputs: {} })
@@ -434,7 +464,9 @@ describe('usePipelineRunByCanEdit', () => {
})
it('should call ssePost with correct URL', async () => {
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.handleRun({ inputs: { query: 'test' } })
@@ -455,7 +487,9 @@ describe('usePipelineRunByCanEdit', () => {
capturedCallbacks = callbacks
})
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.handleRun({ inputs: {} }, { onWorkflowStarted })
@@ -477,7 +511,9 @@ describe('usePipelineRunByCanEdit', () => {
capturedCallbacks = callbacks
})
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.handleRun({ inputs: {} }, { onWorkflowFinished })
@@ -499,7 +535,9 @@ describe('usePipelineRunByCanEdit', () => {
capturedCallbacks = callbacks
})
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.handleRun({ inputs: {} }, { onError })
@@ -521,7 +559,9 @@ describe('usePipelineRunByCanEdit', () => {
capturedCallbacks = callbacks
})
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.handleRun({ inputs: {} }, { onNodeStarted })
@@ -542,7 +582,9 @@ describe('usePipelineRunByCanEdit', () => {
capturedCallbacks = callbacks
})
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.handleRun({ inputs: {} }, { onNodeFinished })
@@ -563,7 +605,9 @@ describe('usePipelineRunByCanEdit', () => {
capturedCallbacks = callbacks
})
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.handleRun({ inputs: {} }, { onIterationStart })
@@ -584,7 +628,9 @@ describe('usePipelineRunByCanEdit', () => {
capturedCallbacks = callbacks
})
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.handleRun({ inputs: {} }, { onIterationNext })
@@ -605,7 +651,9 @@ describe('usePipelineRunByCanEdit', () => {
capturedCallbacks = callbacks
})
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.handleRun({ inputs: {} }, { onIterationFinish })
@@ -626,7 +674,9 @@ describe('usePipelineRunByCanEdit', () => {
capturedCallbacks = callbacks
})
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.handleRun({ inputs: {} }, { onLoopStart })
@@ -647,7 +697,9 @@ describe('usePipelineRunByCanEdit', () => {
capturedCallbacks = callbacks
})
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.handleRun({ inputs: {} }, { onLoopNext })
@@ -668,7 +720,9 @@ describe('usePipelineRunByCanEdit', () => {
capturedCallbacks = callbacks
})
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.handleRun({ inputs: {} }, { onLoopFinish })
@@ -689,7 +743,9 @@ describe('usePipelineRunByCanEdit', () => {
capturedCallbacks = callbacks
})
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.handleRun({ inputs: {} }, { onNodeRetry })
@@ -710,7 +766,9 @@ describe('usePipelineRunByCanEdit', () => {
capturedCallbacks = callbacks
})
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.handleRun({ inputs: {} }, { onAgentLog })
@@ -730,7 +788,9 @@ describe('usePipelineRunByCanEdit', () => {
capturedCallbacks = callbacks
})
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.handleRun({ inputs: {} })
@@ -750,7 +810,9 @@ describe('usePipelineRunByCanEdit', () => {
capturedCallbacks = callbacks
})
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.handleRun({ inputs: {} })
@@ -771,7 +833,9 @@ describe('usePipelineRunByCanEdit', () => {
capturedCallbacks = callbacks
})
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.handleRun({ inputs: {} }, {
@@ -789,7 +853,9 @@ describe('usePipelineRunByCanEdit', () => {
capturedCallbacks = callbacks
})
const { result } = renderHook(() => usePipelineRunByCanEdit(true))
const { result } = renderHook(() => usePipelineRunByCanEdit(true), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
await act(async () => {
await result.current.handleRun({ inputs: {} })
@@ -1,5 +1,6 @@
import { act, renderHook } from '@testing-library/react'
import { act } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vite-plus/test'
import { renderEditorHook as renderHook } from '@/app/components/workflow/editor-resource/__tests__/render'
import { DSLImportMode, DSLImportStatus } from '@/models/app'
import { httpErrorFromResponse } from '@/service/http-error'
import { useUpdateDSLModal } from '../use-update-dsl-modal'
@@ -100,11 +101,13 @@ describe('useUpdateDSLModal', () => {
const mockOnImport = vi.fn()
const renderUpdateDSLModal = (overrides?: { onImport?: () => void }) =>
renderHook(() =>
useUpdateDSLModal({
onCancel: mockOnCancel,
onImport: overrides?.onImport ?? mockOnImport,
}),
renderHook(
() =>
useUpdateDSLModal({
onCancel: mockOnCancel,
onImport: overrides?.onImport ?? mockOnImport,
}),
{ resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' } },
)
beforeEach(() => {
@@ -599,7 +602,9 @@ describe('useUpdateDSLModal', () => {
describe('optional onImport', () => {
it('should work without onImport callback', async () => {
const { result } = renderHook(() => useUpdateDSLModal({ onCancel: mockOnCancel }))
const { result } = renderHook(() => useUpdateDSLModal({ onCancel: mockOnCancel }), {
resource: { kind: 'knowledge-pipeline', pipelineId: 'test-pipeline-id' },
})
act(() => {
result.current.handleFile(createFile())
@@ -15,13 +15,9 @@ vi.mock('../hooks/use-snippet-init', () => ({
}))
vi.mock('../components/snippet-main', () => ({
default: ({
hasPublishedWorkflow,
snippetId,
}: {
hasPublishedWorkflow: boolean
snippetId: string
}) => <div data-testid="snippet-main">{`${snippetId}:${String(hasPublishedWorkflow)}`}</div>,
default: ({ hasPublishedWorkflow }: { hasPublishedWorkflow: boolean }) => (
<div data-testid="snippet-main">{String(hasPublishedWorkflow)}</div>
),
}))
vi.mock('@/next/navigation', () => ({
@@ -163,7 +159,8 @@ describe('SnippetPage', () => {
render(<SnippetPage snippetId="snippet-1" />)
expect(screen.getByTestId('workflow-default-context')).toBeInTheDocument()
expect(screen.getByTestId('snippet-main')).toHaveTextContent('snippet-1')
expect(mockUseSnippetInit).toHaveBeenCalledWith('snippet-1')
expect(screen.getByTestId('snippet-main')).toHaveTextContent('true')
expect(screen.queryByRole('main')).not.toBeInTheDocument()
})
@@ -176,7 +173,7 @@ describe('SnippetPage', () => {
expect(capturedWorkflowDefaultContextProps?.edges).toEqual(
mockPublishedSnippetDetail.graph.edges,
)
expect(screen.getByTestId('snippet-main')).toHaveTextContent('snippet-1:true')
expect(screen.getByTestId('snippet-main')).toHaveTextContent('true')
})
it('should initialize workflow context with draft graph data when the published workflow is empty', () => {
@@ -198,7 +195,7 @@ describe('SnippetPage', () => {
expect(capturedWorkflowDefaultContextProps?.nodes).toEqual(mockDraftSnippetDetail.graph.nodes)
expect(capturedWorkflowDefaultContextProps?.edges).toEqual(mockDraftSnippetDetail.graph.edges)
expect(screen.getByTestId('snippet-main')).toHaveTextContent('snippet-1:false')
expect(screen.getByTestId('snippet-main')).toHaveTextContent('false')
})
it('should render loading fallback when orchestrate data is unavailable', () => {
@@ -83,13 +83,7 @@ vi.mock('@/service/use-snippet-workflows', () => ({
}),
}))
vi.mock('../../hooks/use-configs-map', () => ({
useConfigsMap: () => ({
flowId: 'snippet-1',
flowType: 'snippet',
fileSettings: {},
}),
}))
vi.mock('../../hooks/use-file-settings', () => ({ useFileSettings: () => ({ enabled: true }) }))
vi.mock('@/app/components/workflow/hooks/use-fetch-workflow-inspect-vars', () => ({
useSetWorkflowVarsWithValue: () => ({
@@ -103,12 +97,12 @@ vi.mock('@/app/components/workflow/hooks/use-checklist', () => ({
}),
}))
vi.mock('@/app/components/workflow-app/hooks/use-available-nodes-meta-data', () => ({
useAvailableNodesMetaData: () => mockUseAvailableNodesMetaData(),
vi.mock('@/app/components/workflow/node-catalog', () => ({
useWorkflowNodeCatalog: () => mockUseAvailableNodesMetaData(),
}))
vi.mock('../../hooks/use-inspect-vars-crud', () => ({
useInspectVarsCrud: () => mockInspectVarsCrud,
vi.mock('@/app/components/workflow/hooks/use-inspect-vars-crud-common', () => ({
useInspectVarsCrudCommon: () => mockInspectVarsCrud,
}))
vi.mock('../../hooks/use-nodes-sync-draft', () => ({
@@ -234,7 +228,6 @@ const renderSnippetMain = ({
draftPayload={payload}
hasInitialDraftChanges={hasInitialDraftChanges}
hasPublishedWorkflow={hasPublishedWorkflow}
snippetId="snippet-1"
nodes={workflowNodes}
edges={[] as WorkflowProps['edges']}
viewport={{ x: 0, y: 0, zoom: 1 }}
@@ -243,6 +236,7 @@ const renderSnippetMain = ({
draftViewport={{ x: 0, y: 0, zoom: 1 }}
/>,
{
resource: { kind: 'snippet', snippetId: 'snippet-1' },
initialStoreState: {
nodes: currentNodes,
},
@@ -1,51 +0,0 @@
import type { FileUploadConfigResponse } from '@/models/common'
import { renderWorkflowHook } from '@/app/components/workflow/__tests__/workflow-test-env'
import { Resolution, TransferMethod } from '@/types/app'
import { FlowType } from '@/types/common'
import { useConfigsMap } from '../use-configs-map'
describe('useConfigsMap', () => {
it('should build snippet workflow configs from the snippet id and workflow file upload config', () => {
const fileUploadConfig = {
batch_count_limit: 5,
image_file_batch_limit: 3,
file_size_limit: 15,
workflow_file_upload_limit: 10,
} as FileUploadConfigResponse
const { result, rerender } = renderWorkflowHook(
({ snippetId }: { snippetId: string }) => useConfigsMap(snippetId),
{
initialProps: { snippetId: 'snippet-1' },
initialStoreState: {
fileUploadConfig,
},
},
)
expect(result.current).toEqual({
flowId: 'snippet-1',
flowType: FlowType.snippet,
fileSettings: {
image: {
enabled: false,
detail: Resolution.high,
number_limits: 3,
transfer_methods: [TransferMethod.local_file, TransferMethod.remote_url],
},
fileUploadConfig,
},
})
const firstConfigs = result.current
rerender({ snippetId: 'snippet-1' })
expect(result.current).toBe(firstConfigs)
rerender({ snippetId: 'snippet-2' })
expect(result.current.flowId).toBe('snippet-2')
expect(result.current).not.toBe(firstConfigs)
})
})
@@ -0,0 +1,28 @@
import type { FileUploadConfigResponse } from '@/models/common'
import { act } from '@testing-library/react'
import { renderWorkflowHook } from '@/app/components/workflow/__tests__/workflow-test-env'
import { Resolution, TransferMethod } from '@/types/app'
import { useFileSettings } from '../use-file-settings'
describe('useFileSettings', () => {
it('updates upload limits from the editor while keeping snippet image inputs disabled', () => {
const fileUploadConfig = { workflow_file_upload_limit: 10 } as FileUploadConfigResponse
const { result, store } = renderWorkflowHook(useFileSettings, {
resource: { kind: 'snippet', snippetId: 'snippet-1' },
initialStoreState: { fileUploadConfig },
})
expect(result.current).toEqual({
image: {
enabled: false,
detail: Resolution.high,
number_limits: 3,
transfer_methods: [TransferMethod.local_file, TransferMethod.remote_url],
},
fileUploadConfig,
})
act(() =>
store.setState({ fileUploadConfig: { ...fileUploadConfig, workflow_file_upload_limit: 20 } }),
)
expect(result.current.fileUploadConfig?.workflow_file_upload_limit).toBe(20)
})
})
@@ -1,95 +0,0 @@
import { renderHook } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vite-plus/test'
import { useInspectVarsCrud } from '../use-inspect-vars-crud'
const mockApis = {
hasNodeInspectVars: vi.fn(),
hasSetInspectVar: vi.fn(),
fetchInspectVarValue: vi.fn(),
editInspectVarValue: vi.fn(),
renameInspectVarName: vi.fn(),
appendNodeInspectVars: vi.fn(),
deleteInspectVar: vi.fn(),
deleteNodeInspectorVars: vi.fn(),
deleteAllInspectorVars: vi.fn(),
isInspectVarEdited: vi.fn(),
resetToLastRunVar: vi.fn(),
invalidateSysVarValues: vi.fn(),
resetConversationVar: vi.fn(),
invalidateConversationVarValues: vi.fn(),
}
const mockUseInspectVarsCrudCommon = vi.fn(() => mockApis)
vi.mock('@/app/components/workflow/hooks/use-inspect-vars-crud-common', () => ({
useInspectVarsCrudCommon: (...args: Parameters<typeof mockUseInspectVarsCrudCommon>) =>
mockUseInspectVarsCrudCommon(...args),
}))
const mockConfigsMap = {
flowId: 'snippet-123',
flowType: 'snippet',
fileSettings: {
image: { enabled: false },
fileUploadConfig: {},
},
}
vi.mock('../use-configs-map', () => ({
useConfigsMap: () => mockConfigsMap,
}))
describe('useInspectVarsCrud', () => {
beforeEach(() => {
vi.clearAllMocks()
})
describe('Composition', () => {
it('should pass configsMap to useInspectVarsCrudCommon', () => {
renderHook(() => useInspectVarsCrud('snippet-123'))
expect(mockUseInspectVarsCrudCommon).toHaveBeenCalledWith(
expect.objectContaining({
flowId: 'snippet-123',
flowType: 'snippet',
}),
)
})
it('should return all APIs from useInspectVarsCrudCommon', () => {
const { result } = renderHook(() => useInspectVarsCrud('snippet-123'))
expect(result.current.hasNodeInspectVars).toBe(mockApis.hasNodeInspectVars)
expect(result.current.fetchInspectVarValue).toBe(mockApis.fetchInspectVarValue)
expect(result.current.editInspectVarValue).toBe(mockApis.editInspectVarValue)
expect(result.current.deleteInspectVar).toBe(mockApis.deleteInspectVar)
expect(result.current.deleteAllInspectorVars).toBe(mockApis.deleteAllInspectorVars)
expect(result.current.resetToLastRunVar).toBe(mockApis.resetToLastRunVar)
expect(result.current.resetConversationVar).toBe(mockApis.resetConversationVar)
})
})
describe('API Surface', () => {
it('should expose all expected API methods', () => {
const { result } = renderHook(() => useInspectVarsCrud('snippet-123'))
const expectedKeys = [
'hasNodeInspectVars',
'hasSetInspectVar',
'fetchInspectVarValue',
'editInspectVarValue',
'renameInspectVarName',
'appendNodeInspectVars',
'deleteInspectVar',
'deleteNodeInspectorVars',
'deleteAllInspectorVars',
'isInspectVarEdited',
'resetToLastRunVar',
'invalidateSysVarValues',
'resetConversationVar',
'invalidateConversationVarValues',
]
for (const key of expectedKeys) expect(result.current).toHaveProperty(key)
})
})
})
@@ -1,4 +1,5 @@
import { act, renderHook } from '@testing-library/react'
import { act } from '@testing-library/react'
import { renderEditorHook as renderHook } from '@/app/components/workflow/editor-resource/__tests__/render'
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
import { useSnippetRun } from '../use-snippet-run'
@@ -104,7 +105,7 @@ vi.mock('@/service/base', () => ({
}))
vi.mock('@/service/use-workflow', () => ({
useInvalidAllLastRun: () => mocks.mockInvalidateAllLastRun,
useInvalidateAllLastRun: () => mocks.mockInvalidateAllLastRun,
useInvalidateWorkflowRunHistory: () => mocks.mockInvalidateRunHistory,
}))
@@ -139,7 +140,9 @@ describe('useSnippetRun', () => {
it('stops a snippet workflow with the provided task id', () => {
mocks.workflowStoreState.workflowRunningData = createRunningData()
const { result } = renderHook(() => useSnippetRun('snippet-1'))
const { result } = renderHook(() => useSnippetRun(), {
resource: { kind: 'snippet', snippetId: 'snippet-1' },
})
act(() => {
result.current.handleStopRun('task-1')
@@ -155,7 +158,9 @@ describe('useSnippetRun', () => {
it('does not fall back to the workflow running task id when stop is called without one', () => {
mocks.workflowStoreState.workflowRunningData = createRunningData('task-from-store')
const { result } = renderHook(() => useSnippetRun('snippet-1'))
const { result } = renderHook(() => useSnippetRun(), {
resource: { kind: 'snippet', snippetId: 'snippet-1' },
})
act(() => {
result.current.handleStopRun('')
@@ -169,7 +174,9 @@ describe('useSnippetRun', () => {
it('does not call the stop endpoint when task id is unavailable', () => {
mocks.workflowStoreState.workflowRunningData = createRunningData()
const { result } = renderHook(() => useSnippetRun('snippet-1'))
const { result } = renderHook(() => useSnippetRun(), {
resource: { kind: 'snippet', snippetId: 'snippet-1' },
})
act(() => {
result.current.handleStopRun('')
@@ -7,7 +7,6 @@ import { createQueryClientWrapper } from '@/test/console/query-client'
import { seedAppDetail } from '@/test/console/query-data'
import { render as renderWithConsoleState } from '@/test/console/render'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import { AppACLPermission } from '@/utils/permission'
import WorkflowApp from '../index'
@@ -229,7 +228,7 @@ describe('WorkflowApp', () => {
mockIsWorkflowDataLoaded = true
appDetail = createAppDetailFixture({
id: 'app-1',
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
permission_keys: [AppACLPermission.TestAndRun],
})
workflowInitState = {
@@ -359,7 +358,7 @@ describe('WorkflowApp', () => {
searchParamsValue = 'run-1'
appDetail = createAppDetailFixture({
id: 'app-1',
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
permission_keys: [AppACLPermission.ViewLayout],
})
@@ -6,7 +6,6 @@ import { createQueryClientWrapper } from '@/test/console/query-client'
import { seedAppDetail } from '@/test/console/query-data'
import { render as renderWithConsoleState } from '@/test/console/render'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import WorkflowApp from '../index'
const render = (ui: ReactElement) => {
@@ -16,7 +15,7 @@ const render = (ui: ReactElement) => {
createAppDetailFixture({
id: 'app-1',
name: 'Workflow App',
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
permission_keys: [],
}),
)
@@ -66,11 +65,11 @@ vi.mock('../hooks/use-workflow-init', async () => {
return {
useWorkflowInit: () => {
useStore((state) => state.appId)
useStore((state) => state.syncWorkflowDraftHash)
const workflowStore = useWorkflowStore()
React.useEffect(() => {
workflowStore.setState({ appId: 'initialized-app' })
workflowStore.setState({ syncWorkflowDraftHash: 'initialized-draft' })
}, [workflowStore])
return {
@@ -115,7 +114,7 @@ vi.mock('../components/workflow-main', async () => {
await import('@/app/components/workflow/workflow-history-store')
const WorkflowMainProbe = () => {
const appId = useStore((state) => state.appId)
const appId = useStore((state) => state.syncWorkflowDraftHash)
const hasWorkflowSlice = useStore((state) => typeof state.setNotInitialWorkflow === 'function')
const { store } = useWorkflowHistoryStore()
@@ -139,6 +138,8 @@ describe('WorkflowApp provider contract', () => {
</StrictMode>,
)
expect(await screen.findByText('app:initialized-app history:1 slice:true')).toBeInTheDocument()
expect(
await screen.findByText('app:initialized-draft history:1 slice:true'),
).toBeInTheDocument()
})
})
@@ -1,7 +1,8 @@
import { act, render, screen } from '@testing-library/react'
import { act, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import * as React from 'react'
import { DSL_EXPORT_CHECK } from '@/app/components/workflow/constants'
import { renderEditor as render } from '@/app/components/workflow/editor-resource/__tests__/render'
import { BlockEnum } from '@/app/components/workflow/types'
import WorkflowChildren from '../workflow-children'
@@ -135,8 +136,8 @@ vi.mock('@/app/components/workflow/utils', async (importOriginal) => {
}
})
vi.mock('../../hooks/use-available-nodes-meta-data', () => ({
useAvailableNodesMetaData: () => ({
vi.mock('@/app/components/workflow/node-catalog', () => ({
useWorkflowNodeCatalog: () => ({
nodesMap: {
[BlockEnum.Start]: {
defaultValue: {
@@ -160,9 +161,12 @@ vi.mock('../../hooks/use-available-nodes-meta-data', () => ({
}),
}))
vi.mock('../../hooks/use-is-chat-mode', () => ({
useIsChatMode: () => false,
}))
vi.mock('@/app/components/workflow/editor-resource/state', async (importOriginal) => {
const actual =
await importOriginal<typeof import('@/app/components/workflow/editor-resource/state')>()
const { atom } = await import('jotai')
return { ...actual, isChatflowAtom: atom(() => false) }
})
vi.mock('../../hooks/use-auto-onboarding', () => ({
useAutoOnboarding: () => ({
@@ -366,7 +370,7 @@ describe('WorkflowChildren', () => {
showImportDSLModal: true,
}
render(<WorkflowChildren />)
render(<WorkflowChildren />, { resource: { kind: 'app', appId: 'app-1', mode: 'workflow' } })
expect(screen.getByTestId('plugin-dependency')).toBeInTheDocument()
expect(screen.getByTestId('workflow-header')).toBeInTheDocument()
@@ -386,7 +390,7 @@ describe('WorkflowChildren', () => {
it('should react to DSL export check events by showing the confirm modal and closing it', async () => {
const user = userEvent.setup()
render(<WorkflowChildren />)
render(<WorkflowChildren />, { resource: { kind: 'app', appId: 'app-1', mode: 'workflow' } })
await act(async () => {
eventSubscription?.({
@@ -410,7 +414,7 @@ describe('WorkflowChildren', () => {
})
it('should ignore unrelated workflow events when listening for DSL export checks', async () => {
render(<WorkflowChildren />)
render(<WorkflowChildren />, { resource: { kind: 'app', appId: 'app-1', mode: 'workflow' } })
await act(async () => {
eventSubscription?.({
@@ -431,7 +435,7 @@ describe('WorkflowChildren', () => {
showOnboarding: true,
}
render(<WorkflowChildren />)
render(<WorkflowChildren />, { resource: { kind: 'app', appId: 'app-1', mode: 'workflow' } })
expect(await screen.findByTestId('workflow-onboarding-modal')).toBeInTheDocument()
@@ -447,7 +451,7 @@ describe('WorkflowChildren', () => {
showOnboarding: true,
}
render(<WorkflowChildren />)
render(<WorkflowChildren />, { resource: { kind: 'app', appId: 'app-1', mode: 'workflow' } })
expect(screen.queryByTestId('workflow-onboarding-modal')).not.toBeInTheDocument()
expect(mockSetNodes).not.toHaveBeenCalled()
@@ -461,7 +465,7 @@ describe('WorkflowChildren', () => {
showOnboarding: true,
}
render(<WorkflowChildren />)
render(<WorkflowChildren />, { resource: { kind: 'app', appId: 'app-1', mode: 'workflow' } })
await user.click(await screen.findByRole('button', { name: /^select-start-node$/i }))
@@ -490,7 +494,7 @@ describe('WorkflowChildren', () => {
showOnboarding: true,
}
render(<WorkflowChildren />)
render(<WorkflowChildren />, { resource: { kind: 'app', appId: 'app-1', mode: 'workflow' } })
await user.click(await screen.findByRole('button', { name: /select-start-node-with-config/i }))
@@ -514,7 +518,7 @@ describe('WorkflowChildren', () => {
showOnboarding: true,
}
render(<WorkflowChildren />)
render(<WorkflowChildren />, { resource: { kind: 'app', appId: 'app-1', mode: 'workflow' } })
await user.click(await screen.findByRole('button', { name: /^select-trigger-plugin$/i }))
@@ -550,7 +554,7 @@ describe('WorkflowChildren', () => {
showOnboarding: true,
}
render(<WorkflowChildren />)
render(<WorkflowChildren />, { resource: { kind: 'app', appId: 'app-1', mode: 'workflow' } })
await user.click(await screen.findByRole('button', { name: /select-trigger-plugin-fallback/i }))
@@ -1,20 +1,14 @@
import type { AppDetailWithSite } from '@dify/contracts/api/console/apps/types.gen'
import type { ReactElement, ReactNode } from 'react'
import type { WorkflowProps } from '@/app/components/workflow'
import {
act,
fireEvent,
render as renderWithQueryClient,
screen,
waitFor,
} from '@testing-library/react'
import { act, fireEvent, screen, waitFor } from '@testing-library/react'
import { renderEditor as renderWithQueryClient } from '@/app/components/workflow/editor-resource/__tests__/render'
import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type'
import { BlockEnum } from '@/app/components/workflow/types'
import { createAccountProfileQueryClient } from '@/test/console/account-profile'
import { createQueryClientWrapper } from '@/test/console/query-client'
import { seedAppDetail } from '@/test/console/query-data'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import { AppACLPermission } from '@/utils/permission'
import WorkflowMain from '../workflow-main'
@@ -23,7 +17,10 @@ let appDetail: AppDetailWithSite
const render = (ui: ReactElement) => {
const queryClient = createAccountProfileQueryClient()
seedAppDetail(queryClient, appDetail)
return renderWithQueryClient(ui, { wrapper: createQueryClientWrapper(queryClient) })
return renderWithQueryClient(ui, {
resource: { kind: 'app', appId: 'app-1', mode: 'workflow' },
wrapper: createQueryClientWrapper(queryClient),
})
}
vi.mock('@/next/navigation', async (importOriginal) => ({
@@ -129,6 +126,8 @@ vi.mock('@/context/workspace-state', async () => {
})
vi.mock('@/app/components/base/features/hooks', () => ({
useFeatures: (selector: (state: { features: { file: { enabled: boolean } } }) => unknown) =>
selector({ features: { file: { enabled: true } } }),
useFeaturesStore: () => ({
getState: () => ({
setFeatures: mockSetFeatures,
@@ -302,17 +301,13 @@ vi.mock('@/app/components/workflow', () => ({
},
}))
vi.mock('../../hooks/use-available-nodes-meta-data', () => ({
useAvailableNodesMetaData: () => ({
vi.mock('@/app/components/workflow/node-catalog', () => ({
useWorkflowNodeCatalog: () => ({
nodes: [{ id: 'start' }],
nodesMap: { start: { id: 'start' } },
}),
}))
vi.mock('../../hooks/use-configs-map', () => ({
useConfigsMap: () => ({ flowId: 'app-1', flowType: 'app-flow', fileSettings: { enabled: true } }),
}))
vi.mock('../../hooks/use-DSL', () => ({
useDSL: () => ({ exportCheck: hookFns.exportCheck, handleExportDSL: hookFns.handleExportDSL }),
useDSLByCanEdit: () => ({
@@ -325,8 +320,8 @@ vi.mock('../../hooks/use-get-run-and-trace-url', () => ({
useGetRunAndTraceUrl: () => ({ getWorkflowRunAndTraceUrl: hookFns.getWorkflowRunAndTraceUrl }),
}))
vi.mock('../../hooks/use-inspect-vars-crud', () => ({
useInspectVarsCrud: () => ({
vi.mock('@/app/components/workflow/hooks/use-inspect-vars-crud-common', () => ({
useInspectVarsCrudCommon: () => ({
hasNodeInspectVars: hookFns.hasNodeInspectVars,
hasSetInspectVar: hookFns.hasSetInspectVar,
fetchInspectVarValue: hookFns.fetchInspectVarValue,
@@ -472,7 +467,7 @@ describe('WorkflowMain', () => {
mockReplaceGraphFromReactFlow.mockReturnValue(true)
hookFns.doSyncWorkflowDraft.mockResolvedValue({ hash: 'saved-hash', updatedAt: 2 })
hookFns.handleRefreshWorkflowDraft.mockResolvedValue(true)
appDetail = createAppDetailFixture({ id: 'app-1', mode: AppModeEnum.WORKFLOW })
appDetail = createAppDetailFixture({ id: 'app-1', mode: 'workflow' as const })
})
it('should render the inner workflow context with children and forwarded graph props', () => {
@@ -567,7 +562,7 @@ describe('WorkflowMain', () => {
exportCheck: hookFns.exportCheck,
handleExportDSL: hookFns.handleExportDSL,
fetchInspectVars: hookFns.fetchInspectVars,
configsMap: { flowId: 'app-1', flowType: 'app-flow', fileSettings: { enabled: true } },
fileSettings: { enabled: true },
})
})
@@ -616,7 +611,7 @@ describe('WorkflowMain', () => {
it('disables collaboration for view-only apps', () => {
appDetail = createAppDetailFixture({
id: 'app-1',
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
permission_keys: [AppACLPermission.ViewLayout],
})
@@ -1,3 +1,4 @@
import type { AppMode } from '@dify/contracts/api/console/apps/types.gen'
import type { ReactNode } from 'react'
import type { Shape } from '@/app/components/workflow/store/workflow'
import { act, screen, waitFor } from '@testing-library/react'
@@ -6,7 +7,6 @@ import * as React from 'react'
import { renderWorkflowComponent } from '@/app/components/workflow/__tests__/workflow-test-env'
import { createConsoleQueryWrapper, seedAppDetail } from '@/test/console/query-data'
import { createAppDetailFixture } from '@/test/fixtures/app'
import { AppModeEnum } from '@/types/app'
import WorkflowPanel from '../workflow-panel'
type WorkflowStoreState = Partial<Shape>
@@ -26,11 +26,15 @@ const renderPanel = (initialStoreState: WorkflowStoreState) => {
queryClient,
createAppDetailFixture({
id: 'app-123',
mode: AppModeEnum.WORKFLOW,
mode: 'workflow' as const,
workflow: { id: 'workflow-version-id' },
}),
)
return renderWorkflowComponent(<WorkflowPanel />, { initialStoreState, queryClient })
return renderWorkflowComponent(<WorkflowPanel />, {
initialStoreState,
queryClient,
resource: { kind: 'app', appId: 'app-123', mode: 'workflow' },
})
}
vi.mock('@/app/components/workflow/panel', () => ({
@@ -48,7 +52,7 @@ vi.mock('@/app/components/workflow/panel', () => ({
restoreVersionUrl: (versionId: string) => string
updateVersionUrl: (versionId: string) => string
latestVersionId?: string
appMode?: AppModeEnum
appMode?: AppMode
}
}) => (
<div
@@ -108,9 +112,12 @@ vi.mock('@/app/components/workflow/panel/global-variable-panel', () => ({
default: () => <div data-testid="global-variable-panel">global-variable</div>,
}))
vi.mock('../../hooks/use-is-chat-mode', () => ({
useIsChatMode: () => mockUseIsChatMode(),
}))
vi.mock('@/app/components/workflow/editor-resource/state', async (importOriginal) => {
const actual =
await importOriginal<typeof import('@/app/components/workflow/editor-resource/state')>()
const { atom } = await import('jotai')
return { ...actual, isChatflowAtom: atom(() => mockUseIsChatMode()) }
})
describe('WorkflowPanel', () => {
beforeEach(() => {
@@ -136,7 +143,7 @@ describe('WorkflowPanel', () => {
)
expect(panel).toHaveAttribute('data-update-version-url', '/apps/app-123/workflows/version-1')
expect(panel).toHaveAttribute('data-latest-version-id', 'workflow-version-id')
expect(panel).toHaveAttribute('data-app-mode', AppModeEnum.WORKFLOW)
expect(panel).toHaveAttribute('data-app-mode', 'workflow')
})
it('should close the selected log and reopen on the default detail tab', async () => {

Some files were not shown because too many files have changed in this diff Show More