fix: resolve import migrations and test failures after segment 3 merge

- Migrate core.model_runtime -> dify_graph.model_runtime across 20+ files
- Migrate core.workflow.file -> dify_graph.file across 15+ files
- Migrate core.workflow.enums -> dify_graph.enums in service files
- Fix SandboxContext phantom import in dify_graph/context/__init__.py
- Fix core.app.workflow.node_factory -> core.workflow.node_factory
- Fix toast import paths (useToastContext from toast/context)
- Fix app-info.tsx import paths for relocated app-operations
- Fix 15 frontend test files for API changes, missing QueryClientProvider,
  i18n key renames, and component behavior changes

Made-with: Cursor
This commit is contained in:
Novice
2026-03-23 10:31:11 +08:00
parent 94b01f6821
commit 6b75188ddc
58 changed files with 242 additions and 172 deletions

View File

@ -3,11 +3,19 @@ import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import * as React from 'react'
import { AppModeEnum } from '@/types/app'
import AppInfo from '..'
import AppInfo from '../index'
let mockIsCurrentWorkspaceEditor = true
const mockSetPanelOpen = vi.fn()
vi.mock('next/navigation', () => ({
useRouter: () => ({ replace: vi.fn() }),
}))
vi.mock('@/service/use-apps', () => ({
useInvalidateAppList: () => vi.fn(),
}))
vi.mock('@/context/app-context', () => ({
useAppContext: () => ({
isCurrentWorkspaceEditor: mockIsCurrentWorkspaceEditor,

View File

@ -263,11 +263,10 @@ describe('AppCard', () => {
})
it('should render app icon', () => {
// AppIcon component renders the emoji icon from app data
const { container } = render(<AppCard app={mockApp} />)
// Check that the icon container is rendered (AppIcon renders within the card)
const iconElement = container.querySelector('[class*="icon"]') || container.querySelector('img')
expect(iconElement || screen.getByText(mockApp.icon)).toBeTruthy()
const emojiElement = container.querySelector('em-emoji')
expect(emojiElement).toBeTruthy()
expect(emojiElement?.getAttribute('id')).toBe(mockApp.icon)
})
it('should render app type icon', () => {

View File

@ -20,6 +20,11 @@ vi.mock('@/app/education-apply/hooks', () => ({
},
}))
vi.mock('next/navigation', () => ({
useRouter: () => ({ replace: vi.fn() }),
useSearchParams: () => new URLSearchParams(),
}))
vi.mock('@/hooks/use-import-dsl', () => ({
useImportDSL: () => ({
handleImportDSL: vi.fn(),

View File

@ -1,3 +1,4 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { act, fireEvent, screen } from '@testing-library/react'
import * as React from 'react'
import { useStore as useTagStore } from '@/app/components/base/tag-management/store'
@ -200,9 +201,17 @@ beforeAll(() => {
} as unknown as typeof IntersectionObserver
})
// Render helper wrapping with shared nuqs testing helper.
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
})
const renderList = (searchParams = '') => {
return renderWithNuqs(<List />, { searchParams })
return renderWithNuqs(
<QueryClientProvider client={queryClient}>
<List />
</QueryClientProvider>,
{ searchParams },
)
}
describe('List', () => {
@ -399,10 +408,14 @@ describe('List', () => {
describe('Edge Cases', () => {
it('should handle multiple renders without issues', () => {
const { rerender } = renderWithNuqs(<List />)
const { rerender } = renderWithNuqs(
<QueryClientProvider client={queryClient}><List /></QueryClientProvider>,
)
expect(screen.getByText('app.types.all')).toBeInTheDocument()
rerender(<List />)
rerender(
<QueryClientProvider client={queryClient}><List /></QueryClientProvider>,
)
expect(screen.getByText('app.types.all')).toBeInTheDocument()
})

View File

@ -71,7 +71,7 @@ describe('CreateAppCard', () => {
expect(screen.getByText('app.newApp.startFromBlank')).toBeInTheDocument()
expect(screen.getByText('app.newApp.startFromTemplate')).toBeInTheDocument()
expect(screen.getByText('app.importDSL')).toBeInTheDocument()
expect(screen.getByText('app.importApp')).toBeInTheDocument()
})
it('should render all buttons as clickable', () => {
@ -190,7 +190,7 @@ describe('CreateAppCard', () => {
it('should open DSL modal when clicking Import DSL', () => {
render(<CreateAppCard ref={defaultRef} />)
fireEvent.click(screen.getByText('app.importDSL'))
fireEvent.click(screen.getByText('app.importApp'))
expect(screen.getByTestId('create-dsl-modal')).toBeInTheDocument()
})
@ -198,7 +198,7 @@ describe('CreateAppCard', () => {
it('should close DSL modal when clicking close button', () => {
render(<CreateAppCard ref={defaultRef} />)
fireEvent.click(screen.getByText('app.importDSL'))
fireEvent.click(screen.getByText('app.importApp'))
expect(screen.getByTestId('create-dsl-modal')).toBeInTheDocument()
fireEvent.click(screen.getByTestId('close-dsl-modal'))
@ -209,7 +209,7 @@ describe('CreateAppCard', () => {
const mockOnSuccess = vi.fn()
render(<CreateAppCard ref={defaultRef} onSuccess={mockOnSuccess} />)
fireEvent.click(screen.getByText('app.importDSL'))
fireEvent.click(screen.getByText('app.importApp'))
fireEvent.click(screen.getByTestId('success-dsl-modal'))
expect(mockOnPlanInfoChanged).toHaveBeenCalled()
@ -245,7 +245,7 @@ describe('CreateAppCard', () => {
fireEvent.click(screen.getByText('app.newApp.startFromTemplate'))
fireEvent.click(screen.getByTestId('close-template-dialog'))
fireEvent.click(screen.getByText('app.importDSL'))
fireEvent.click(screen.getByText('app.importApp'))
fireEvent.click(screen.getByTestId('close-dsl-modal'))
expect(screen.queryByTestId('create-app-modal')).not.toBeInTheDocument()

View File

@ -14,6 +14,7 @@ const getPromptEditor = () => {
vi.mock('@/utils/var', () => ({
checkKeys: (_keys: string[]) => ({ isValid: true }),
getNewVar: (key: string, type: string) => ({ key, name: key, type, required: true }),
basePath: '',
}))
vi.mock('@/app/components/app/configuration/config-prompt/confirm-add-var', () => ({

View File

@ -68,6 +68,7 @@ vi.mock('lexical', async (importOriginal) => {
getChildren: () => mocks.rootLines.map(line => ({
getTextContent: () => line,
})),
getAllTextNodes: () => [],
}),
TextNode: class TextNode {
__text: string

View File

@ -77,12 +77,16 @@ const { mockConfig, mockEnv } = vi.hoisted(() => ({
},
},
}))
vi.mock('@/config', () => ({
get IS_CLOUD_EDITION() { return mockConfig.IS_CLOUD_EDITION },
get ZENDESK_WIDGET_KEY() { return mockConfig.ZENDESK_WIDGET_KEY },
IS_DEV: false,
IS_CE_EDITION: false,
}))
vi.mock('@/config', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/config')>()
return {
...actual,
get IS_CLOUD_EDITION() { return mockConfig.IS_CLOUD_EDITION },
get ZENDESK_WIDGET_KEY() { return mockConfig.ZENDESK_WIDGET_KEY },
IS_DEV: false,
IS_CE_EDITION: false,
}
})
vi.mock('@/env', () => mockEnv)
const baseAppContextValue: AppContextValue = {

View File

@ -55,8 +55,20 @@ vi.mock('@/service/workflow', () => ({
syncWorkflowDraft: (p: unknown) => mockSyncWorkflowDraft(p),
}))
vi.mock('@/service/fetch', () => ({ postWithKeepalive: vi.fn() }))
vi.mock('@/config', () => ({ API_PREFIX: '/api' }))
vi.mock('@/service/fetch', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/service/fetch')>()
return {
...actual,
postWithKeepalive: vi.fn(),
}
})
vi.mock('@/config', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/config')>()
return {
...actual,
API_PREFIX: '/api',
}
})
const mockHandleRefreshWorkflowDraft = vi.fn()
vi.mock('@/app/components/workflow-app/hooks', () => ({

View File

@ -11,7 +11,7 @@ vi.mock('@/app/components/workflow/store', () => ({
getState: () => ({
appId: 'app-1',
isWorkflowDataLoaded: true,
debouncedSyncWorkflowDraft: undefined,
debouncedSyncWorkflowDraft: { cancel: vi.fn() },
setSyncWorkflowDraftHash: mockSetSyncWorkflowDraftHash,
setIsSyncingWorkflowDraft: vi.fn(),
setEnvironmentVariables: vi.fn(),

View File

@ -26,6 +26,7 @@ vi.mock('../use-workflow', () => ({
vi.mock('../../utils', () => ({
getNodesConnectedSourceOrTargetHandleIdsMap: vi.fn(() => ({})),
genNodeMetaData: vi.fn(({ type, sort }: { type: string, sort: number }) => ({ type, sort })),
}))
// useNodesSyncDraft is used REAL — via renderWorkflowHook + hooksStoreProps

View File

@ -53,7 +53,7 @@ describe('useWorkflowTextChunk', () => {
},
})
result.current.handleWorkflowTextChunk({ data: { text: ' World' } } as TextChunkResponse)
result.current.handleWorkflowTextChunk({ data: { text: ' World', chunk_type: 'text' } } as TextChunkResponse)
const state = store.getState().workflowRunningData!
expect(state.resultText).toBe('Hello World')

View File

@ -172,9 +172,9 @@ describe('createWorkflowStore', () => {
expect(store.getState().controlMode).toBe('pointer')
})
it('should default controlMode to hand when localStorage has no value', () => {
it('should default controlMode to pointer when localStorage has no value', () => {
const store = createStore()
expect(store.getState().controlMode).toBe('hand')
expect(store.getState().controlMode).toBe('pointer')
})
it('should read panelWidth from localStorage', () => {