feat: enhance configuration and environment setup for SSH sandbox and Creators Platform; update local excludes and improve component logic in the web app

This commit is contained in:
Novice
2026-03-24 17:05:56 +08:00
parent a844936759
commit d594365a45
29 changed files with 208 additions and 194 deletions

View File

@ -261,6 +261,32 @@ vi.mock('../hooks/use-workflow-search', () => ({
useWorkflowSearch: workflowHookMocks.useWorkflowSearch,
}))
vi.mock('../hooks/use-workflow-comment', () => ({
useWorkflowComment: () => ({
comments: [],
loading: false,
pendingComment: null,
activeComment: null,
activeCommentLoading: false,
replySubmitting: false,
replyUpdating: false,
handleCommentSubmit: vi.fn(),
handleCommentCancel: vi.fn(),
handleCommentIconClick: vi.fn(),
handleActiveCommentClose: vi.fn(),
handleCommentResolve: vi.fn(),
handleCommentDelete: vi.fn(),
handleCommentNavigate: vi.fn(),
handleCommentReply: vi.fn(),
handleCommentReplyUpdate: vi.fn(),
handleCommentReplyDelete: vi.fn(),
handleCommentPositionUpdate: vi.fn(),
refreshActiveComment: vi.fn(),
handleCreateComment: vi.fn(),
loadComments: vi.fn(),
}),
}))
vi.mock('../nodes/_base/components/variable/use-match-schema-type', () => ({
default: () => ({
schemaTypeDefinitions: undefined,

View File

@ -36,7 +36,7 @@ vi.mock('@/context/app-context', () => ({
}))
vi.mock('@/app/components/base/avatar', () => ({
default: ({ name }: { name: string }) => <div data-testid="avatar">{name}</div>,
Avatar: ({ name }: { name: string }) => <div data-testid="avatar">{name}</div>,
}))
vi.mock('./mention-input', () => ({

View File

@ -172,9 +172,10 @@ export const useEdgesInteractions = () => {
}
})
setEdges(newEdges)
workflowStore.setState({ edgeMenu: undefined })
handleSyncWorkflowDraft()
saveStateToHistory(WorkflowHistoryEvent.EdgeDelete)
}, [getNodesReadOnly, collaborativeWorkflow, handleSyncWorkflowDraft, saveStateToHistory])
}, [getNodesReadOnly, collaborativeWorkflow, workflowStore, handleSyncWorkflowDraft, saveStateToHistory])
const handleEdgeDeleteById = useCallback((edgeId: string) => {
if (getNodesReadOnly())

View File

@ -17,7 +17,7 @@ const ComputerUseTip: FC<Props> = ({
React.useEffect(() => {
if (!visible)
// eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
// eslint-disable-next-line react/set-state-in-effect
setDismissed(false)
}, [visible])

View File

@ -129,7 +129,7 @@ const LoopResultPanel: FC<Props> = ({
)}
>
{
loopVariableMap?.[index] && (
!!loopVariableMap?.[index] && (
<div className="p-2 pb-0">
<CodeEditor
readOnly

View File

@ -255,7 +255,7 @@ const NodePanel: FC<Props> = ({
</StatusContainer>
)}
</div>
{nodeInfo.inputs && (
{!!nodeInfo.inputs && (
<div className={cn('mb-1')}>
<CodeEditor
readOnly
@ -267,7 +267,7 @@ const NodePanel: FC<Props> = ({
/>
</div>
)}
{nodeInfo.process_data && (
{!!nodeInfo.process_data && (
<div className={cn('mb-1')}>
<CodeEditor
readOnly

View File

@ -29,7 +29,7 @@ const ResultText: FC<ResultTextProps> = ({
allFiles,
}) => {
const { t } = useTranslation()
const generationContentRenderIsUsed = llmGenerationItems?.length && llmGenerationItems.some((item) => {
const generationContentRenderIsUsed = !!llmGenerationItems?.length && llmGenerationItems.some((item) => {
return item.type === 'tool' || item.type === 'thought'
})

View File

@ -134,7 +134,7 @@ const FileReferenceBlock = ({ nodeKey, resourceId }: FileReferenceBlockProps) =>
const width = 400
const gap = 4
const left = Math.max(8, rect.left - gap - width)
// eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
// eslint-disable-next-line react/set-state-in-effect
setPreviewStyle(_prev => ({
position: 'fixed',
top: rect.top,

View File

@ -293,14 +293,14 @@ const ToolBlockComponent = ({
if (!configuredToolValue)
return
if (!toolValue || toolValue.tool_name !== configuredToolValue.tool_name || toolValue.provider_name !== configuredToolValue.provider_name)
// eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
// eslint-disable-next-line react/set-state-in-effect
setToolValue(configuredToolValue)
}, [configuredToolValue, toolValue])
useEffect(() => {
if (!isSettingOpen || !configuredToolValue)
return
// eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
// eslint-disable-next-line react/set-state-in-effect
setToolValue(configuredToolValue)
}, [configuredToolValue, isSettingOpen])
@ -311,7 +311,7 @@ const ToolBlockComponent = ({
const fallbackContainer = document.querySelector('[data-skill-editor-root="true"]') as HTMLElement | null
const container = containerFromRef || fallbackContainer
if (container)
// eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
// eslint-disable-next-line react/set-state-in-effect
setPortalContainer(container)
}, [ref, useModalValue])

View File

@ -368,7 +368,7 @@ const ToolGroupBlockComponent = ({
if (!configuredToolValue)
return
if (!toolValue || toolValue.tool_name !== configuredToolValue.tool_name || toolValue.provider_name !== configuredToolValue.provider_name)
// eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
// eslint-disable-next-line react/set-state-in-effect
setToolValue(configuredToolValue)
}, [configuredToolValue, toolValue])
@ -376,14 +376,14 @@ const ToolGroupBlockComponent = ({
if (expandedToolId)
return
if (toolValue)
// eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
// eslint-disable-next-line react/set-state-in-effect
setToolValue(null)
}, [expandedToolId, toolValue])
useEffect(() => {
if (!isSettingOpen || !configuredToolValue)
return
// eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
// eslint-disable-next-line react/set-state-in-effect
setToolValue(configuredToolValue)
}, [configuredToolValue, isSettingOpen])
@ -394,7 +394,7 @@ const ToolGroupBlockComponent = ({
const fallbackContainer = document.querySelector('[data-skill-editor-root="true"]') as HTMLElement | null
const container = containerFromRef || fallbackContainer
if (container)
// eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
// eslint-disable-next-line react/set-state-in-effect
setPortalContainer(container)
}, [ref, useModalValue])

View File

@ -11,12 +11,14 @@ const {
mockUploadMutateAsync,
mockPrepareSkillUploadFile,
mockEmitTreeUpdate,
mockToastNotify,
mockToastSuccess,
mockToastError,
} = vi.hoisted(() => ({
mockUploadMutateAsync: vi.fn(),
mockPrepareSkillUploadFile: vi.fn(),
mockEmitTreeUpdate: vi.fn(),
mockToastNotify: vi.fn(),
mockToastSuccess: vi.fn(),
mockToastError: vi.fn(),
}))
vi.mock('@/service/use-app-asset', () => ({
@ -34,9 +36,10 @@ vi.mock('../data/use-skill-tree-collaboration', () => ({
useSkillTreeUpdateEmitter: () => mockEmitTreeUpdate,
}))
vi.mock('@/app/components/base/toast', () => ({
default: {
notify: mockToastNotify,
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
success: mockToastSuccess,
error: mockToastError,
},
}))
@ -169,10 +172,7 @@ describe('useFileDrop', () => {
expect(mockPrepareSkillUploadFile).not.toHaveBeenCalled()
expect(mockUploadMutateAsync).not.toHaveBeenCalled()
expect(mockToastNotify).toHaveBeenCalledWith({
type: 'error',
message: 'workflow.skillSidebar.menu.folderDropNotSupported',
})
expect(mockToastError).toHaveBeenCalledWith('workflow.skillSidebar.menu.folderDropNotSupported')
expect(store.getState().currentDragType).toBeNull()
expect(store.getState().dragOverFolderId).toBeNull()
})
@ -201,14 +201,8 @@ describe('useFileDrop', () => {
parentId: 'folder-mixed',
})
expect(mockEmitTreeUpdate).toHaveBeenCalledTimes(1)
expect(mockToastNotify).toHaveBeenNthCalledWith(1, {
type: 'error',
message: 'workflow.skillSidebar.menu.folderDropNotSupported',
})
expect(mockToastNotify).toHaveBeenNthCalledWith(2, {
type: 'success',
message: 'workflow.skillSidebar.menu.filesUploaded:{"count":1}',
})
expect(mockToastError).toHaveBeenCalledWith('workflow.skillSidebar.menu.folderDropNotSupported')
expect(mockToastSuccess).toHaveBeenCalledWith('workflow.skillSidebar.menu.filesUploaded:{"count":1}')
})
})
@ -245,10 +239,7 @@ describe('useFileDrop', () => {
parentId: 'folder-9',
})
expect(mockEmitTreeUpdate).toHaveBeenCalledTimes(1)
expect(mockToastNotify).toHaveBeenCalledWith({
type: 'success',
message: 'workflow.skillSidebar.menu.filesUploaded:{"count":2}',
})
expect(mockToastSuccess).toHaveBeenCalledWith('workflow.skillSidebar.menu.filesUploaded:{"count":2}')
})
})
@ -269,10 +260,7 @@ describe('useFileDrop', () => {
expect(mockUploadMutateAsync).toHaveBeenCalledTimes(1)
expect(mockEmitTreeUpdate).not.toHaveBeenCalled()
expect(mockToastNotify).toHaveBeenCalledWith({
type: 'error',
message: 'workflow.skillSidebar.menu.uploadError',
})
expect(mockToastError).toHaveBeenCalledWith('workflow.skillSidebar.menu.uploadError')
})
})
})

View File

@ -31,11 +31,13 @@ const createDeferred = <T,>(): Deferred<T> => {
const {
mockGetFileDownloadUrl,
mockDownloadUrl,
mockToastNotify,
mockToastSuccess,
mockToastError,
} = vi.hoisted(() => ({
mockGetFileDownloadUrl: vi.fn<(request: DownloadRequest) => Promise<DownloadResponse>>(),
mockDownloadUrl: vi.fn<(payload: { url: string, fileName?: string }) => void>(),
mockToastNotify: vi.fn<(payload: { type: string, message: string }) => void>(),
mockToastSuccess: vi.fn(),
mockToastError: vi.fn(),
}))
vi.mock('@/service/client', () => ({
@ -50,9 +52,10 @@ vi.mock('@/utils/download', () => ({
downloadUrl: mockDownloadUrl,
}))
vi.mock('@/app/components/base/toast', () => ({
default: {
notify: mockToastNotify,
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
success: mockToastSuccess,
error: mockToastError,
},
}))
@ -109,7 +112,8 @@ describe('useDownloadOperation', () => {
url: 'https://example.com/file.txt',
fileName: 'notes.md',
})
expect(mockToastNotify).not.toHaveBeenCalled()
expect(mockToastSuccess).not.toHaveBeenCalled()
expect(mockToastError).not.toHaveBeenCalled()
expect(result.current.isDownloading).toBe(false)
})
@ -163,10 +167,8 @@ describe('useDownloadOperation', () => {
expect(onClose).toHaveBeenCalledTimes(1)
expect(mockDownloadUrl).not.toHaveBeenCalled()
expect(mockToastNotify).toHaveBeenCalledWith({
type: 'error',
message: 'workflow.skillSidebar.menu.downloadError',
})
expect(mockToastError).toHaveBeenCalledWith('workflow.skillSidebar.menu.downloadError')
expect(mockToastSuccess).not.toHaveBeenCalled()
expect(result.current.isDownloading).toBe(false)
})
})

View File

@ -16,7 +16,8 @@ const mocks = vi.hoisted(() => ({
deletePending: false,
deleteMutateAsync: vi.fn<(payload: DeleteMutationPayload) => Promise<void>>(),
emitTreeUpdate: vi.fn<() => void>(),
toastNotify: vi.fn<(payload: { type: string, message: string }) => void>(),
toastSuccess: vi.fn<(message: string) => void>(),
toastError: vi.fn<(message: string) => void>(),
getAllDescendantFileIds: vi.fn<(nodeId: string, nodes: TreeNodeData[]) => string[]>(),
}))
@ -35,9 +36,10 @@ vi.mock('../../../utils/tree-utils', () => ({
getAllDescendantFileIds: mocks.getAllDescendantFileIds,
}))
vi.mock('@/app/components/base/toast', () => ({
default: {
notify: mocks.toastNotify,
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
success: mocks.toastSuccess,
error: mocks.toastError,
},
}))
@ -234,10 +236,7 @@ describe('useModifyOperations', () => {
expect(clearDraftContent).toHaveBeenNthCalledWith(2, 'desc-2')
expect(clearDraftContent).toHaveBeenNthCalledWith(3, 'file-7')
expect(mocks.toastNotify).toHaveBeenCalledWith({
type: 'success',
message: 'workflow.skillSidebar.menu.fileDeleted',
})
expect(mocks.toastSuccess).toHaveBeenCalledWith('workflow.skillSidebar.menu.fileDeleted')
expect(result.current.showDeleteConfirm).toBe(false)
expect(onClose).toHaveBeenCalledTimes(1)
})
@ -269,10 +268,7 @@ describe('useModifyOperations', () => {
expect(clearDraftContent).toHaveBeenCalledWith('file-in-folder')
expect(closeTab).not.toHaveBeenCalledWith('folder-9')
expect(clearDraftContent).not.toHaveBeenCalledWith('folder-9')
expect(mocks.toastNotify).toHaveBeenCalledWith({
type: 'success',
message: 'workflow.skillSidebar.menu.deleted',
})
expect(mocks.toastSuccess).toHaveBeenCalledWith('workflow.skillSidebar.menu.deleted')
})
})
@ -303,10 +299,7 @@ describe('useModifyOperations', () => {
expect(mocks.emitTreeUpdate).not.toHaveBeenCalled()
expect(closeTab).not.toHaveBeenCalled()
expect(clearDraftContent).not.toHaveBeenCalled()
expect(mocks.toastNotify).toHaveBeenCalledWith({
type: 'error',
message: 'workflow.skillSidebar.menu.deleteError',
})
expect(mocks.toastError).toHaveBeenCalledWith('workflow.skillSidebar.menu.deleteError')
expect(result.current.showDeleteConfirm).toBe(false)
expect(onClose).toHaveBeenCalledTimes(1)
})
@ -329,10 +322,7 @@ describe('useModifyOperations', () => {
})
expect(mocks.getAllDescendantFileIds).not.toHaveBeenCalled()
expect(mocks.toastNotify).toHaveBeenCalledWith({
type: 'error',
message: 'workflow.skillSidebar.menu.fileDeleteError',
})
expect(mocks.toastError).toHaveBeenCalledWith('workflow.skillSidebar.menu.fileDeleteError')
})
})
})

View File

@ -22,7 +22,8 @@ const mocks = vi.hoisted(() => ({
movePending: false,
moveMutateAsync: vi.fn<(payload: MoveMutationPayload) => Promise<void>>(),
emitTreeUpdate: vi.fn<() => void>(),
toastNotify: vi.fn<(payload: { type: string, message: string }) => void>(),
toastSuccess: vi.fn<(message: string) => void>(),
toastError: vi.fn<(message: string) => void>(),
toApiParentId: vi.fn<(folderId: string | null | undefined) => string | null>(),
}))
@ -45,9 +46,10 @@ vi.mock('../../../utils/tree-utils', () => ({
toApiParentId: mocks.toApiParentId,
}))
vi.mock('@/app/components/base/toast', () => ({
default: {
notify: mocks.toastNotify,
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
success: mocks.toastSuccess,
error: mocks.toastError,
},
}))
@ -90,10 +92,7 @@ describe('useNodeMove', () => {
},
})
expect(mocks.emitTreeUpdate).toHaveBeenCalledTimes(1)
expect(mocks.toastNotify).toHaveBeenCalledWith({
type: 'success',
message: 'workflow.skillSidebar.menu.moved',
})
expect(mocks.toastSuccess).toHaveBeenCalledWith('workflow.skillSidebar.menu.moved')
})
it('should use empty appId when app detail is unavailable', async () => {
@ -126,10 +125,7 @@ describe('useNodeMove', () => {
})
expect(mocks.emitTreeUpdate).not.toHaveBeenCalled()
expect(mocks.toastNotify).toHaveBeenCalledWith({
type: 'error',
message: 'workflow.skillSidebar.menu.moveError',
})
expect(mocks.toastError).toHaveBeenCalledWith('workflow.skillSidebar.menu.moveError')
})
})
})

View File

@ -22,7 +22,8 @@ const mocks = vi.hoisted(() => ({
reorderPending: false,
reorderMutateAsync: vi.fn<(payload: ReorderMutationPayload) => Promise<void>>(),
emitTreeUpdate: vi.fn<() => void>(),
toastNotify: vi.fn<(payload: { type: string, message: string }) => void>(),
toastSuccess: vi.fn<(message: string) => void>(),
toastError: vi.fn<(message: string) => void>(),
}))
vi.mock('@/app/components/app/store', () => ({
@ -40,9 +41,10 @@ vi.mock('../data/use-skill-tree-collaboration', () => ({
useSkillTreeUpdateEmitter: () => mocks.emitTreeUpdate,
}))
vi.mock('@/app/components/base/toast', () => ({
default: {
notify: mocks.toastNotify,
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
success: mocks.toastSuccess,
error: mocks.toastError,
},
}))
@ -82,10 +84,7 @@ describe('useNodeReorder', () => {
},
})
expect(mocks.emitTreeUpdate).toHaveBeenCalledTimes(1)
expect(mocks.toastNotify).toHaveBeenCalledWith({
type: 'success',
message: 'workflow.skillSidebar.menu.moved',
})
expect(mocks.toastSuccess).toHaveBeenCalledWith('workflow.skillSidebar.menu.moved')
})
it('should use empty appId when app detail is missing', async () => {
@ -117,10 +116,7 @@ describe('useNodeReorder', () => {
})
expect(mocks.emitTreeUpdate).not.toHaveBeenCalled()
expect(mocks.toastNotify).toHaveBeenCalledWith({
type: 'error',
message: 'workflow.skillSidebar.menu.moveError',
})
expect(mocks.toastError).toHaveBeenCalledWith('workflow.skillSidebar.menu.moveError')
})
})
})

View File

@ -56,7 +56,8 @@ const mocks = vi.hoisted(() => ({
movePending: false,
moveMutateAsync: vi.fn<(payload: MoveMutationPayload) => Promise<void>>(),
emitTreeUpdate: vi.fn<() => void>(),
toastNotify: vi.fn<(payload: { type: string, message: string }) => void>(),
toastSuccess: vi.fn<(message: string) => void>(),
toastError: vi.fn<(message: string) => void>(),
getTargetFolderIdFromSelection: vi.fn<(selectedId: string | null, nodes: TreeNodeData[]) => string>(),
toApiParentId: vi.fn<(folderId: string | null | undefined) => string | null>(),
findNodeById: vi.fn<(nodes: TreeNodeData[], nodeId: string) => TreeNodeData | null>(),
@ -89,9 +90,10 @@ vi.mock('../../../utils/tree-utils', () => ({
findNodeById: mocks.findNodeById,
}))
vi.mock('@/app/components/base/toast', () => ({
default: {
notify: mocks.toastNotify,
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
success: mocks.toastSuccess,
error: mocks.toastError,
},
}))
@ -151,7 +153,8 @@ describe('usePasteOperation', () => {
expect(mocks.getTargetFolderIdFromSelection).not.toHaveBeenCalled()
expect(mocks.moveMutateAsync).not.toHaveBeenCalled()
expect(mocks.toastNotify).not.toHaveBeenCalled()
expect(mocks.toastSuccess).not.toHaveBeenCalled()
expect(mocks.toastError).not.toHaveBeenCalled()
})
it('should no-op when clipboard has no node ids', async () => {
@ -188,10 +191,7 @@ describe('usePasteOperation', () => {
})
expect(mocks.moveMutateAsync).not.toHaveBeenCalled()
expect(mocks.toastNotify).toHaveBeenCalledWith({
type: 'error',
message: 'workflow.skillSidebar.menu.cannotMoveToSelf',
})
expect(mocks.toastError).toHaveBeenCalledWith('workflow.skillSidebar.menu.cannotMoveToSelf')
})
})
@ -232,10 +232,7 @@ describe('usePasteOperation', () => {
})
expect(mocks.workflowState.clearClipboard).toHaveBeenCalledTimes(1)
expect(mocks.emitTreeUpdate).toHaveBeenCalledTimes(1)
expect(mocks.toastNotify).toHaveBeenCalledWith({
type: 'success',
message: 'workflow.skillSidebar.menu.moved',
})
expect(mocks.toastSuccess).toHaveBeenCalledWith('workflow.skillSidebar.menu.moved')
})
it('should fallback to selectedTreeNodeId when tree has no selected node', async () => {
@ -280,10 +277,7 @@ describe('usePasteOperation', () => {
expect(mocks.workflowState.clearClipboard).not.toHaveBeenCalled()
expect(mocks.emitTreeUpdate).not.toHaveBeenCalled()
expect(mocks.toastNotify).toHaveBeenCalledWith({
type: 'error',
message: 'workflow.skillSidebar.menu.moveError',
})
expect(mocks.toastError).toHaveBeenCalledWith('workflow.skillSidebar.menu.moveError')
})
it('should prevent re-entrant paste while a paste is in progress', async () => {

View File

@ -8,9 +8,10 @@ import { START_TAB_ID } from '../constants'
import { useSkillSaveManager } from './skill-save-context'
import { SkillSaveProvider } from './use-skill-save-manager'
const { mockMutateAsync, mockToastNotify } = vi.hoisted(() => ({
const { mockMutateAsync, mockToastSuccess, mockToastError } = vi.hoisted(() => ({
mockMutateAsync: vi.fn(),
mockToastNotify: vi.fn(),
mockToastSuccess: vi.fn(),
mockToastError: vi.fn(),
}))
vi.mock('@/service/use-app-asset', () => ({
@ -19,9 +20,10 @@ vi.mock('@/service/use-app-asset', () => ({
}),
}))
vi.mock('@/app/components/base/toast', () => ({
default: {
notify: mockToastNotify,
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
success: mockToastSuccess,
error: mockToastError,
},
}))
@ -507,11 +509,9 @@ describe('useSkillSaveManager', () => {
// Assert
await waitFor(() => {
expect(mockToastNotify).toHaveBeenCalledWith({
type: 'success',
message: 'common.api.saved',
})
expect(mockToastSuccess).toHaveBeenCalledWith('common.api.saved')
})
expect(mockToastError).not.toHaveBeenCalled()
})
it('should show error toast when save fails', async () => {
@ -531,11 +531,9 @@ describe('useSkillSaveManager', () => {
// Assert
await waitFor(() => {
expect(mockToastNotify).toHaveBeenCalledWith({
type: 'error',
message: 'Network error',
})
expect(mockToastError).toHaveBeenCalledWith('Network error')
})
expect(mockToastSuccess).not.toHaveBeenCalled()
})
it('should use registered fallback content for keyboard save', async () => {

View File

@ -13,7 +13,8 @@ const mocks = vi.hoisted(() => ({
mutateAsync: vi.fn(),
emitTreeUpdate: vi.fn(),
prepareSkillUploadFile: vi.fn(),
toastNotify: vi.fn(),
toastSuccess: vi.fn(),
toastError: vi.fn(),
existingNames: new Set<string>(),
workflowState: {
setUploadStatus: vi.fn(),
@ -48,9 +49,10 @@ vi.mock('@/app/components/workflow/store', () => ({
}),
}))
vi.mock('@/app/components/base/toast', () => ({
default: {
notify: (...args: unknown[]) => mocks.toastNotify(...args),
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
success: (...args: unknown[]) => mocks.toastSuccess(...args),
error: (...args: unknown[]) => mocks.toastError(...args),
},
}))
@ -121,10 +123,8 @@ describe('CreateBlankSkillModal', () => {
expect(mocks.workflowState.setUploadProgress).toHaveBeenCalledWith({ uploaded: 1, total: 1, failed: 0 })
expect(mocks.emitTreeUpdate).toHaveBeenCalledTimes(1)
expect(mocks.workflowState.openTab).toHaveBeenCalledWith('skill-md-id', { pinned: true })
expect(mocks.toastNotify).toHaveBeenCalledWith({
type: 'success',
message: 'workflow.skill.startTab.createSuccess:{"name":"new-skill"}',
})
expect(mocks.toastSuccess).toHaveBeenCalledWith('workflow.skill.startTab.createSuccess:{"name":"new-skill"}')
expect(mocks.toastError).not.toHaveBeenCalled()
expect(onClose).toHaveBeenCalledTimes(1)
expect(screen.getByRole('textbox')).toHaveValue('')
})
@ -141,10 +141,8 @@ describe('CreateBlankSkillModal', () => {
expect(mocks.workflowState.setUploadStatus).toHaveBeenCalledWith('partial_error')
})
expect(mocks.toastNotify).toHaveBeenCalledWith({
type: 'error',
message: 'workflow.skill.startTab.createError',
})
expect(mocks.toastError).toHaveBeenCalledWith('workflow.skill.startTab.createError')
expect(mocks.toastSuccess).not.toHaveBeenCalled()
expect(onClose).not.toHaveBeenCalled()
expect(screen.getByRole('textbox')).toHaveValue('')
})

View File

@ -15,7 +15,8 @@ const mocks = vi.hoisted(() => ({
buildUploadDataFromZip: vi.fn(),
mutateAsync: vi.fn(),
emitTreeUpdate: vi.fn(),
toastNotify: vi.fn(),
toastSuccess: vi.fn(),
toastError: vi.fn(),
existingNames: new Set<string>(),
workflowState: {
setUploadStatus: vi.fn(),
@ -67,9 +68,10 @@ vi.mock('@/app/components/workflow/store', () => ({
}),
}))
vi.mock('@/app/components/base/toast', () => ({
default: {
notify: (...args: unknown[]) => mocks.toastNotify(...args),
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
success: (...args: unknown[]) => mocks.toastSuccess(...args),
error: (...args: unknown[]) => mocks.toastError(...args),
},
}))
@ -116,10 +118,8 @@ describe('ImportSkillModal', () => {
selectFile(new File(['readme'], 'README.md', { type: 'text/markdown' }))
expect(mocks.toastNotify).toHaveBeenCalledWith({
type: 'error',
message: 'workflow.skill.startTab.importModal.invalidFileType',
})
expect(mocks.toastError).toHaveBeenCalledWith('workflow.skill.startTab.importModal.invalidFileType')
expect(mocks.toastSuccess).not.toHaveBeenCalled()
expect(screen.getByRole('button', { name: /workflow\.skill\.startTab\.importModal\.importButton/i })).toBeDisabled()
})
@ -210,10 +210,8 @@ describe('ImportSkillModal', () => {
expect(mocks.workflowState.setUploadProgress).toHaveBeenCalledWith({ uploaded: 1, total: 1, failed: 0 })
expect(mocks.emitTreeUpdate).toHaveBeenCalledTimes(1)
expect(mocks.workflowState.openTab).toHaveBeenCalledWith('skill-md-id', { pinned: true })
expect(mocks.toastNotify).toHaveBeenCalledWith({
type: 'success',
message: 'workflow.skill.startTab.importModal.importSuccess:{"name":"new-skill"}',
})
expect(mocks.toastSuccess).toHaveBeenCalledWith('workflow.skill.startTab.importModal.importSuccess:{"name":"new-skill"}')
expect(mocks.toastError).not.toHaveBeenCalled()
expect(onClose).toHaveBeenCalledTimes(1)
})
@ -232,10 +230,8 @@ describe('ImportSkillModal', () => {
expect(mocks.workflowState.setUploadStatus).toHaveBeenCalledWith('partial_error')
})
expect(mocks.toastNotify).toHaveBeenCalledWith({
type: 'error',
message: 'workflow.skill.startTab.importModal.nameDuplicate',
})
expect(mocks.toastError).toHaveBeenCalledWith('workflow.skill.startTab.importModal.nameDuplicate')
expect(mocks.toastSuccess).not.toHaveBeenCalled()
expect(mocks.buildUploadDataFromZip).not.toHaveBeenCalled()
expect(mocks.mutateAsync).not.toHaveBeenCalled()
})
@ -264,10 +260,8 @@ describe('ImportSkillModal', () => {
expect(mocks.workflowState.setUploadStatus).toHaveBeenCalledWith('partial_error')
})
expect(mocks.toastNotify).toHaveBeenCalledWith({
type: 'error',
message: 'workflow.skill.startTab.importModal.errorEmptyZip',
})
expect(mocks.toastError).toHaveBeenCalledWith('workflow.skill.startTab.importModal.errorEmptyZip')
expect(mocks.toastSuccess).not.toHaveBeenCalled()
})
it('should fallback to raw error message when zip validation code is unknown', async () => {
@ -283,10 +277,8 @@ describe('ImportSkillModal', () => {
expect(mocks.workflowState.setUploadStatus).toHaveBeenCalledWith('partial_error')
})
expect(mocks.toastNotify).toHaveBeenCalledWith({
type: 'error',
message: 'custom zip error',
})
expect(mocks.toastError).toHaveBeenCalledWith('custom zip error')
expect(mocks.toastSuccess).not.toHaveBeenCalled()
})
it('should fallback to invalid zip error when import fails with non-validation error', async () => {
@ -300,10 +292,8 @@ describe('ImportSkillModal', () => {
expect(mocks.workflowState.setUploadStatus).toHaveBeenCalledWith('partial_error')
})
expect(mocks.toastNotify).toHaveBeenCalledWith({
type: 'error',
message: 'workflow.skill.startTab.importModal.errorInvalidZip',
})
expect(mocks.toastError).toHaveBeenCalledWith('workflow.skill.startTab.importModal.errorInvalidZip')
expect(mocks.toastSuccess).not.toHaveBeenCalled()
})
})
})

View File

@ -30,7 +30,7 @@ const UnsupportedFileDownload = ({ name, size, downloadUrl }: UnsupportedFileDow
<FileTypeIcon type={FileAppearanceTypeEnum.custom} size="xl" className="size-16 text-text-tertiary" />
<div className="flex flex-col items-center gap-1 text-center">
<p className="text-text-secondary system-md-medium">{name}</p>
{fileSize && (
{!!fileSize && (
<p className="text-text-tertiary system-xs-regular">{fileSize}</p>
)}
</div>