test(workflow): add helper specs and raise targeted workflow coverage (#33995)

Co-authored-by: CodingOnStar <hanxujiang@dify.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Coding On Star
2026-03-24 17:51:07 +08:00
committed by GitHub
parent 67d5c9d148
commit a408a5d87e
75 changed files with 9402 additions and 2507 deletions

View File

@ -0,0 +1,172 @@
import type { IfElseNodeType } from '../types'
import { BlockEnum, VarType } from '@/app/components/workflow/types'
import { LogicalOperator } from '../types'
import {
addCase,
addCondition,
addSubVariableCondition,
filterAllVars,
filterNumberVars,
getVarsIsVarFileAttribute,
removeCase,
removeCondition,
removeSubVariableCondition,
sortCases,
toggleConditionLogicalOperator,
toggleSubVariableConditionLogicalOperator,
updateCondition,
updateSubVariableCondition,
} from '../use-config.helpers'
type TestIfElseInputs = ReturnType<typeof createInputs>
const createInputs = (): IfElseNodeType => ({
title: 'If/Else',
desc: '',
type: BlockEnum.IfElse,
cases: [{
case_id: 'case-1',
logical_operator: LogicalOperator.and,
conditions: [{
id: 'condition-1',
varType: VarType.string,
variable_selector: ['node', 'value'],
comparison_operator: 'contains',
value: '',
}],
}],
_targetBranches: [
{ id: 'case-1', name: 'Case 1' },
{ id: 'false', name: 'Else' },
],
} as unknown as IfElseNodeType)
describe('if-else use-config helpers', () => {
it('filters vars and derives file attribute flags', () => {
expect(filterAllVars()).toBe(true)
expect(filterNumberVars({ type: VarType.number } as never)).toBe(true)
expect(filterNumberVars({ type: VarType.string } as never)).toBe(false)
expect(getVarsIsVarFileAttribute(createInputs().cases, selector => selector[1] === 'value')).toEqual({
'condition-1': true,
})
})
it('adds, removes and sorts cases while keeping target branches aligned', () => {
const added = addCase(createInputs())
expect(added.cases).toHaveLength(2)
expect(added._targetBranches?.map(branch => branch.id)).toContain('false')
const removed = removeCase(added, 'case-1')
expect(removed.cases?.some(item => item.case_id === 'case-1')).toBe(false)
const sorted = sortCases(createInputs(), [
{ id: 'display-2', case_id: 'case-2', logical_operator: LogicalOperator.or, conditions: [] },
{ id: 'display-1', case_id: 'case-1', logical_operator: LogicalOperator.and, conditions: [] },
] as unknown as Parameters<typeof sortCases>[1])
expect(sorted.cases?.map(item => item.case_id)).toEqual(['case-2', 'case-1'])
expect(sorted._targetBranches?.map(branch => branch.id)).toEqual(['case-2', 'case-1', 'false'])
})
it('adds, updates, toggles and removes conditions and sub-conditions', () => {
const withCondition = addCondition({
inputs: createInputs(),
caseId: 'case-1',
valueSelector: ['node', 'flag'],
variable: { type: VarType.boolean } as never,
isVarFileAttribute: false,
})
expect(withCondition.cases?.[0]?.conditions).toHaveLength(2)
expect(withCondition.cases?.[0]?.conditions[1]).toEqual(expect.objectContaining({
value: false,
variable_selector: ['node', 'flag'],
}))
const updatedCondition = updateCondition(withCondition, 'case-1', 'condition-1', {
id: 'condition-1',
value: 'next',
comparison_operator: '=',
} as Parameters<typeof updateCondition>[3])
expect(updatedCondition.cases?.[0]?.conditions[0]).toEqual(expect.objectContaining({
value: 'next',
comparison_operator: '=',
}))
const toggled = toggleConditionLogicalOperator(updatedCondition, 'case-1')
expect(toggled.cases?.[0]?.logical_operator).toBe(LogicalOperator.or)
const withSubCondition = addSubVariableCondition(toggled, 'case-1', 'condition-1', 'name')
expect(withSubCondition.cases?.[0]?.conditions[0]?.sub_variable_condition?.conditions[0]).toEqual(expect.objectContaining({
key: 'name',
value: '',
}))
const firstSubConditionId = withSubCondition.cases?.[0]?.conditions[0]?.sub_variable_condition?.conditions[0]?.id
expect(firstSubConditionId).toBeTruthy()
const updatedSubCondition = updateSubVariableCondition(
withSubCondition,
'case-1',
'condition-1',
firstSubConditionId!,
{ key: 'size', comparison_operator: '>', value: '10' } as TestIfElseInputs['cases'][number]['conditions'][number],
)
expect(updatedSubCondition.cases?.[0]?.conditions[0]?.sub_variable_condition?.conditions[0]).toEqual(expect.objectContaining({
key: 'size',
value: '10',
}))
const toggledSub = toggleSubVariableConditionLogicalOperator(updatedSubCondition, 'case-1', 'condition-1')
expect(toggledSub.cases?.[0]?.conditions[0]?.sub_variable_condition?.logical_operator).toBe(LogicalOperator.or)
const removedSub = removeSubVariableCondition(
toggledSub,
'case-1',
'condition-1',
firstSubConditionId!,
)
expect(removedSub.cases?.[0]?.conditions[0]?.sub_variable_condition?.conditions).toEqual([])
const removedCondition = removeCondition(removedSub, 'case-1', 'condition-1')
expect(removedCondition.cases?.[0]?.conditions.some(item => item.id === 'condition-1')).toBe(false)
})
it('keeps inputs unchanged when guard branches short-circuit helper updates', () => {
const unchangedWithoutCases = addCase({
...createInputs(),
cases: undefined,
} as unknown as IfElseNodeType)
expect(unchangedWithoutCases.cases).toBeUndefined()
const withoutTargetBranches = addCase({
...createInputs(),
_targetBranches: undefined,
})
expect(withoutTargetBranches._targetBranches).toBeUndefined()
const withoutElseBranch = addCase({
...createInputs(),
_targetBranches: [{ id: 'case-1', name: 'Case 1' }],
})
expect(withoutElseBranch._targetBranches).toEqual([{ id: 'case-1', name: 'Case 1' }])
const unchangedWhenConditionMissing = addSubVariableCondition(createInputs(), 'case-1', 'missing-condition', 'name')
expect(unchangedWhenConditionMissing).toEqual(createInputs())
const unchangedWhenSubConditionMissing = removeSubVariableCondition(createInputs(), 'case-1', 'condition-1', 'missing-sub')
expect(unchangedWhenSubConditionMissing).toEqual(createInputs())
const unchangedWhenCaseIsMissingForCondition = addCondition({
inputs: createInputs(),
caseId: 'missing-case',
valueSelector: ['node', 'value'],
variable: { type: VarType.string } as never,
isVarFileAttribute: false,
})
expect(unchangedWhenCaseIsMissingForCondition).toEqual(createInputs())
const unchangedWhenCaseMissing = toggleConditionLogicalOperator(createInputs(), 'missing-case')
expect(unchangedWhenCaseMissing).toEqual(createInputs())
const unchangedWhenSubVariableGroupMissing = toggleSubVariableConditionLogicalOperator(createInputs(), 'case-1', 'condition-1')
expect(unchangedWhenSubVariableGroupMissing).toEqual(createInputs())
})
})

View File

@ -0,0 +1,266 @@
import type { IfElseNodeType } from '../types'
import { renderHook } from '@testing-library/react'
import { BlockEnum, VarType } from '@/app/components/workflow/types'
import {
createNodeCrudModuleMock,
createUuidModuleMock,
} from '../../__tests__/use-config-test-utils'
import { ComparisonOperator, LogicalOperator } from '../types'
import useConfig from '../use-config'
const mockSetInputs = vi.hoisted(() => vi.fn())
const mockHandleEdgeDeleteByDeleteBranch = vi.hoisted(() => vi.fn())
const mockUpdateNodeInternals = vi.hoisted(() => vi.fn())
const mockGetIsVarFileAttribute = vi.hoisted(() => vi.fn())
const mockUuid = vi.hoisted(() => vi.fn(() => 'generated-id'))
vi.mock('uuid', () => ({
...createUuidModuleMock(mockUuid),
}))
vi.mock('reactflow', async () => {
const actual = await vi.importActual<typeof import('reactflow')>('reactflow')
return {
...actual,
useUpdateNodeInternals: () => mockUpdateNodeInternals,
}
})
vi.mock('@/app/components/workflow/hooks', () => ({
useNodesReadOnly: () => ({ nodesReadOnly: false }),
useEdgesInteractions: () => ({
handleEdgeDeleteByDeleteBranch: (...args: unknown[]) => mockHandleEdgeDeleteByDeleteBranch(...args),
}),
}))
vi.mock('@/app/components/workflow/nodes/_base/hooks/use-node-crud', () => ({
...createNodeCrudModuleMock<IfElseNodeType>(mockSetInputs),
}))
vi.mock('@/app/components/workflow/nodes/_base/hooks/use-available-var-list', () => ({
__esModule: true,
default: (_id: string, { filterVar }: { filterVar: (value: { type: VarType }) => boolean }) => ({
availableVars: filterVar({ type: VarType.number })
? [{ nodeId: 'node-1', title: 'Start', vars: [{ variable: 'score', type: VarType.number }] }]
: [{ nodeId: 'node-1', title: 'Start', vars: [{ variable: 'answer', type: VarType.string }] }],
availableNodesWithParent: [],
}),
}))
vi.mock('../use-is-var-file-attribute', () => ({
__esModule: true,
default: () => ({
getIsVarFileAttribute: (...args: unknown[]) => mockGetIsVarFileAttribute(...args),
}),
}))
const createPayload = (overrides: Partial<IfElseNodeType> = {}): IfElseNodeType => ({
title: 'If Else',
desc: '',
type: BlockEnum.IfElse,
isInIteration: false,
isInLoop: false,
cases: [{
case_id: 'case-1',
logical_operator: LogicalOperator.and,
conditions: [{
id: 'condition-1',
varType: VarType.string,
variable_selector: ['node-1', 'answer'],
comparison_operator: ComparisonOperator.contains,
value: 'hello',
}],
}],
_targetBranches: [
{ id: 'case-1', name: 'IF' },
{ id: 'false', name: 'ELSE' },
],
...overrides,
})
describe('useConfig', () => {
beforeEach(() => {
vi.clearAllMocks()
mockGetIsVarFileAttribute.mockReturnValue(false)
})
it('should expose derived vars and file-attribute flags', () => {
const { result } = renderHook(() => useConfig('if-node', createPayload()))
expect(result.current.readOnly).toBe(false)
expect(result.current.filterVar()).toBe(true)
expect(result.current.filterNumberVar({ type: VarType.number } as never)).toBe(true)
expect(result.current.filterNumberVar({ type: VarType.string } as never)).toBe(false)
expect(result.current.nodesOutputVars).toHaveLength(1)
expect(result.current.nodesOutputNumberVars).toHaveLength(1)
expect(result.current.varsIsVarFileAttribute).toEqual({ 'condition-1': false })
})
it('should manage cases and conditions', () => {
const { result } = renderHook(() => useConfig('if-node', createPayload()))
result.current.handleAddCase()
result.current.handleRemoveCase('generated-id')
result.current.handleAddCondition('case-1', ['node-1', 'score'], { type: VarType.number } as never)
result.current.handleUpdateCondition('case-1', 'condition-1', {
id: 'condition-1',
varType: VarType.number,
variable_selector: ['node-1', 'score'],
comparison_operator: ComparisonOperator.largerThan,
value: '3',
})
result.current.handleRemoveCondition('case-1', 'condition-1')
result.current.handleToggleConditionLogicalOperator('case-1')
result.current.handleSortCase([{
id: 'sortable-1',
case_id: 'case-1',
logical_operator: LogicalOperator.or,
conditions: [],
}])
expect(mockSetInputs).toHaveBeenCalledWith(expect.objectContaining({
cases: expect.arrayContaining([
expect.objectContaining({
case_id: 'generated-id',
logical_operator: LogicalOperator.and,
}),
]),
}))
expect(mockSetInputs).toHaveBeenCalledWith(expect.objectContaining({
cases: [
expect.objectContaining({
case_id: 'case-1',
logical_operator: LogicalOperator.or,
}),
],
_targetBranches: [
{ id: 'case-1', name: 'IF' },
{ id: 'false', name: 'ELSE' },
],
}))
expect(mockSetInputs).toHaveBeenCalledWith(expect.objectContaining({
cases: expect.arrayContaining([
expect.objectContaining({
conditions: expect.arrayContaining([
expect.objectContaining({
id: 'generated-id',
variable_selector: ['node-1', 'score'],
}),
]),
}),
]),
}))
expect(mockSetInputs).toHaveBeenCalledWith(expect.objectContaining({
cases: expect.arrayContaining([
expect.objectContaining({
conditions: expect.arrayContaining([
expect.objectContaining({
id: 'condition-1',
comparison_operator: ComparisonOperator.largerThan,
value: '3',
}),
]),
}),
]),
}))
expect(mockSetInputs).toHaveBeenCalledWith(expect.objectContaining({
cases: expect.arrayContaining([
expect.objectContaining({
logical_operator: LogicalOperator.or,
}),
]),
}))
expect(mockHandleEdgeDeleteByDeleteBranch).toHaveBeenCalledWith('if-node', 'generated-id')
expect(mockUpdateNodeInternals).toHaveBeenCalledWith('if-node')
})
it('should manage sub-variable conditions', () => {
const payload = createPayload({
cases: [{
case_id: 'case-1',
logical_operator: LogicalOperator.and,
conditions: [{
id: 'condition-1',
varType: VarType.file,
variable_selector: ['node-1', 'files'],
comparison_operator: ComparisonOperator.exists,
value: '',
sub_variable_condition: {
case_id: 'sub-case-1',
logical_operator: LogicalOperator.and,
conditions: [{
id: 'sub-1',
key: 'name',
varType: VarType.string,
comparison_operator: ComparisonOperator.contains,
value: '',
}],
},
}],
}],
})
const { result } = renderHook(() => useConfig('if-node', payload))
result.current.handleAddSubVariableCondition('case-1', 'condition-1', 'name')
result.current.handleUpdateSubVariableCondition('case-1', 'condition-1', 'sub-1', {
id: 'sub-1',
key: 'size',
varType: VarType.string,
comparison_operator: ComparisonOperator.is,
value: '2',
})
result.current.handleRemoveSubVariableCondition('case-1', 'condition-1', 'sub-1')
result.current.handleToggleSubVariableConditionLogicalOperator('case-1', 'condition-1')
expect(mockSetInputs).toHaveBeenCalledWith(expect.objectContaining({
cases: expect.arrayContaining([
expect.objectContaining({
conditions: expect.arrayContaining([
expect.objectContaining({
sub_variable_condition: expect.objectContaining({
conditions: expect.arrayContaining([
expect.objectContaining({
id: 'generated-id',
key: 'name',
}),
]),
}),
}),
]),
}),
]),
}))
expect(mockSetInputs).toHaveBeenCalledWith(expect.objectContaining({
cases: expect.arrayContaining([
expect.objectContaining({
conditions: expect.arrayContaining([
expect.objectContaining({
sub_variable_condition: expect.objectContaining({
conditions: expect.arrayContaining([
expect.objectContaining({
id: 'sub-1',
key: 'size',
value: '2',
}),
]),
}),
}),
]),
}),
]),
}))
expect(mockSetInputs).toHaveBeenCalledWith(expect.objectContaining({
cases: expect.arrayContaining([
expect.objectContaining({
conditions: expect.arrayContaining([
expect.objectContaining({
sub_variable_condition: expect.objectContaining({
logical_operator: LogicalOperator.or,
}),
}),
]),
}),
]),
}))
})
})

View File

@ -0,0 +1,237 @@
import type { Branch, Var } from '../../types'
import type { CaseItem, Condition, IfElseNodeType } from './types'
import { produce } from 'immer'
import { v4 as uuid4 } from 'uuid'
import { VarType } from '../../types'
import { LogicalOperator } from './types'
import {
branchNameCorrect,
getOperators,
} from './utils'
export const filterAllVars = () => true
export const filterNumberVars = (varPayload: Var) => varPayload.type === VarType.number
export const getVarsIsVarFileAttribute = (
cases: IfElseNodeType['cases'],
getIsVarFileAttribute: (valueSelector: string[]) => boolean,
) => {
const conditions: Record<string, boolean> = {}
cases?.forEach((caseItem) => {
caseItem.conditions.forEach((condition) => {
if (condition.variable_selector)
conditions[condition.id] = getIsVarFileAttribute(condition.variable_selector)
})
})
return conditions
}
const getTargetBranchesWithNewCase = (targetBranches: Branch[] | undefined, caseId: string) => {
if (!targetBranches)
return targetBranches
const elseCaseIndex = targetBranches.findIndex(branch => branch.id === 'false')
if (elseCaseIndex < 0)
return targetBranches
return branchNameCorrect([
...targetBranches.slice(0, elseCaseIndex),
{
id: caseId,
name: '',
},
...targetBranches.slice(elseCaseIndex),
])
}
export const addCase = (inputs: IfElseNodeType) => produce(inputs, (draft) => {
if (!draft.cases)
return
const caseId = uuid4()
draft.cases.push({
case_id: caseId,
logical_operator: LogicalOperator.and,
conditions: [],
})
draft._targetBranches = getTargetBranchesWithNewCase(draft._targetBranches, caseId)
})
export const removeCase = (
inputs: IfElseNodeType,
caseId: string,
) => produce(inputs, (draft) => {
draft.cases = draft.cases?.filter(item => item.case_id !== caseId)
if (draft._targetBranches)
draft._targetBranches = branchNameCorrect(draft._targetBranches.filter(branch => branch.id !== caseId))
})
export const sortCases = (
inputs: IfElseNodeType,
newCases: (CaseItem & { id: string })[],
) => produce(inputs, (draft) => {
draft.cases = newCases.filter(Boolean).map(item => ({
id: item.id,
case_id: item.case_id,
logical_operator: item.logical_operator,
conditions: item.conditions,
}))
draft._targetBranches = branchNameCorrect([
...newCases.filter(Boolean).map(item => ({ id: item.case_id, name: '' })),
{ id: 'false', name: '' },
])
})
export const addCondition = ({
inputs,
caseId,
valueSelector,
variable,
isVarFileAttribute,
}: {
inputs: IfElseNodeType
caseId: string
valueSelector: string[]
variable: Var
isVarFileAttribute: boolean
}) => produce(inputs, (draft) => {
const targetCase = draft.cases?.find(item => item.case_id === caseId)
if (!targetCase)
return
targetCase.conditions.push({
id: uuid4(),
varType: variable.type,
variable_selector: valueSelector,
comparison_operator: getOperators(variable.type, isVarFileAttribute ? { key: valueSelector.slice(-1)[0] } : undefined)[0],
value: (variable.type === VarType.boolean || variable.type === VarType.arrayBoolean) ? false : '',
})
})
export const removeCondition = (
inputs: IfElseNodeType,
caseId: string,
conditionId: string,
) => produce(inputs, (draft) => {
const targetCase = draft.cases?.find(item => item.case_id === caseId)
if (targetCase)
targetCase.conditions = targetCase.conditions.filter(item => item.id !== conditionId)
})
export const updateCondition = (
inputs: IfElseNodeType,
caseId: string,
conditionId: string,
nextCondition: Condition,
) => produce(inputs, (draft) => {
const targetCondition = draft.cases
?.find(item => item.case_id === caseId)
?.conditions
.find(item => item.id === conditionId)
if (targetCondition)
Object.assign(targetCondition, nextCondition)
})
export const toggleConditionLogicalOperator = (
inputs: IfElseNodeType,
caseId: string,
) => produce(inputs, (draft) => {
const targetCase = draft.cases?.find(item => item.case_id === caseId)
if (!targetCase)
return
targetCase.logical_operator = targetCase.logical_operator === LogicalOperator.and
? LogicalOperator.or
: LogicalOperator.and
})
export const addSubVariableCondition = (
inputs: IfElseNodeType,
caseId: string,
conditionId: string,
key?: string,
) => produce(inputs, (draft) => {
const condition = draft.cases
?.find(item => item.case_id === caseId)
?.conditions
.find(item => item.id === conditionId)
if (!condition)
return
if (!condition.sub_variable_condition) {
condition.sub_variable_condition = {
case_id: uuid4(),
logical_operator: LogicalOperator.and,
conditions: [],
}
}
condition.sub_variable_condition.conditions.push({
id: uuid4(),
key: key || '',
varType: VarType.string,
comparison_operator: undefined,
value: '',
})
})
export const removeSubVariableCondition = (
inputs: IfElseNodeType,
caseId: string,
conditionId: string,
subConditionId: string,
) => produce(inputs, (draft) => {
const subVariableCondition = draft.cases
?.find(item => item.case_id === caseId)
?.conditions
.find(item => item.id === conditionId)
?.sub_variable_condition
if (!subVariableCondition)
return
subVariableCondition.conditions = subVariableCondition.conditions.filter(item => item.id !== subConditionId)
})
export const updateSubVariableCondition = (
inputs: IfElseNodeType,
caseId: string,
conditionId: string,
subConditionId: string,
nextCondition: Condition,
) => produce(inputs, (draft) => {
const targetSubCondition = draft.cases
?.find(item => item.case_id === caseId)
?.conditions
.find(item => item.id === conditionId)
?.sub_variable_condition
?.conditions
.find(item => item.id === subConditionId)
if (targetSubCondition)
Object.assign(targetSubCondition, nextCondition)
})
export const toggleSubVariableConditionLogicalOperator = (
inputs: IfElseNodeType,
caseId: string,
conditionId: string,
) => produce(inputs, (draft) => {
const targetSubVariableCondition = draft.cases
?.find(item => item.case_id === caseId)
?.conditions
.find(item => item.id === conditionId)
?.sub_variable_condition
if (!targetSubVariableCondition)
return
targetSubVariableCondition.logical_operator = targetSubVariableCondition.logical_operator === LogicalOperator.and
? LogicalOperator.or
: LogicalOperator.and
})

View File

@ -12,33 +12,48 @@ import type {
HandleUpdateSubVariableCondition,
IfElseNodeType,
} from './types'
import { produce } from 'immer'
import { useCallback, useMemo } from 'react'
import {
useCallback,
useMemo,
useRef,
} from 'react'
import { useUpdateNodeInternals } from 'reactflow'
import { v4 as uuid4 } from 'uuid'
import {
useEdgesInteractions,
useNodesReadOnly,
} from '@/app/components/workflow/hooks'
import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list'
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
import { VarType } from '../../types'
import { LogicalOperator } from './types'
import useIsVarFileAttribute from './use-is-var-file-attribute'
import {
branchNameCorrect,
getOperators,
} from './utils'
addCase,
addCondition,
addSubVariableCondition,
filterAllVars,
filterNumberVars,
getVarsIsVarFileAttribute,
removeCase,
removeCondition,
removeSubVariableCondition,
sortCases,
toggleConditionLogicalOperator,
toggleSubVariableConditionLogicalOperator,
updateCondition,
updateSubVariableCondition,
} from './use-config.helpers'
import useIsVarFileAttribute from './use-is-var-file-attribute'
const useConfig = (id: string, payload: IfElseNodeType) => {
const updateNodeInternals = useUpdateNodeInternals()
const { nodesReadOnly: readOnly } = useNodesReadOnly()
const { handleEdgeDeleteByDeleteBranch } = useEdgesInteractions()
const { inputs, setInputs } = useNodeCrud<IfElseNodeType>(id, payload)
const inputsRef = useRef(inputs)
const handleInputsChange = useCallback((newInputs: IfElseNodeType) => {
inputsRef.current = newInputs
setInputs(newInputs)
}, [setInputs])
const filterVar = useCallback(() => {
return true
}, [])
const filterVar = useCallback(() => filterAllVars(), [])
const {
availableVars,
@ -48,9 +63,7 @@ const useConfig = (id: string, payload: IfElseNodeType) => {
filterVar,
})
const filterNumberVar = useCallback((varPayload: Var) => {
return varPayload.type === VarType.number
}, [])
const filterNumberVar = useCallback((varPayload: Var) => filterNumberVars(varPayload), [])
const {
getIsVarFileAttribute,
@ -61,13 +74,7 @@ const useConfig = (id: string, payload: IfElseNodeType) => {
})
const varsIsVarFileAttribute = useMemo(() => {
const conditions: Record<string, boolean> = {}
inputs.cases?.forEach((c) => {
c.conditions.forEach((condition) => {
conditions[condition.id] = getIsVarFileAttribute(condition.variable_selector!)
})
})
return conditions
return getVarsIsVarFileAttribute(inputs.cases, getIsVarFileAttribute)
}, [inputs.cases, getIsVarFileAttribute])
const {
@ -79,177 +86,56 @@ const useConfig = (id: string, payload: IfElseNodeType) => {
})
const handleAddCase = useCallback(() => {
const newInputs = produce(inputs, (draft) => {
if (draft.cases) {
const case_id = uuid4()
draft.cases.push({
case_id,
logical_operator: LogicalOperator.and,
conditions: [],
})
if (draft._targetBranches) {
const elseCaseIndex = draft._targetBranches.findIndex(branch => branch.id === 'false')
if (elseCaseIndex > -1) {
draft._targetBranches = branchNameCorrect([
...draft._targetBranches.slice(0, elseCaseIndex),
{
id: case_id,
name: '',
},
...draft._targetBranches.slice(elseCaseIndex),
])
}
}
}
})
setInputs(newInputs)
}, [inputs, setInputs])
handleInputsChange(addCase(inputsRef.current))
}, [handleInputsChange])
const handleRemoveCase = useCallback((caseId: string) => {
const newInputs = produce(inputs, (draft) => {
draft.cases = draft.cases?.filter(item => item.case_id !== caseId)
if (draft._targetBranches)
draft._targetBranches = branchNameCorrect(draft._targetBranches.filter(branch => branch.id !== caseId))
handleEdgeDeleteByDeleteBranch(id, caseId)
})
setInputs(newInputs)
}, [inputs, setInputs, id, handleEdgeDeleteByDeleteBranch])
handleEdgeDeleteByDeleteBranch(id, caseId)
handleInputsChange(removeCase(inputsRef.current, caseId))
}, [handleEdgeDeleteByDeleteBranch, handleInputsChange, id])
const handleSortCase = useCallback((newCases: (CaseItem & { id: string })[]) => {
const newInputs = produce(inputs, (draft) => {
draft.cases = newCases.filter(Boolean).map(item => ({
id: item.id,
case_id: item.case_id,
logical_operator: item.logical_operator,
conditions: item.conditions,
}))
draft._targetBranches = branchNameCorrect([
...newCases.filter(Boolean).map(item => ({ id: item.case_id, name: '' })),
{ id: 'false', name: '' },
])
})
setInputs(newInputs)
handleInputsChange(sortCases(inputsRef.current, newCases))
updateNodeInternals(id)
}, [id, inputs, setInputs, updateNodeInternals])
}, [handleInputsChange, id, updateNodeInternals])
const handleAddCondition = useCallback<HandleAddCondition>((caseId, valueSelector, varItem) => {
const newInputs = produce(inputs, (draft) => {
const targetCase = draft.cases?.find(item => item.case_id === caseId)
if (targetCase) {
targetCase.conditions.push({
id: uuid4(),
varType: varItem.type,
variable_selector: valueSelector,
comparison_operator: getOperators(varItem.type, getIsVarFileAttribute(valueSelector) ? { key: valueSelector.slice(-1)[0] } : undefined)[0],
value: (varItem.type === VarType.boolean || varItem.type === VarType.arrayBoolean) ? false : '',
})
}
})
setInputs(newInputs)
}, [getIsVarFileAttribute, inputs, setInputs])
handleInputsChange(addCondition({
inputs: inputsRef.current,
caseId,
valueSelector,
variable: varItem,
isVarFileAttribute: !!getIsVarFileAttribute(valueSelector),
}))
}, [getIsVarFileAttribute, handleInputsChange])
const handleRemoveCondition = useCallback<HandleRemoveCondition>((caseId, conditionId) => {
const newInputs = produce(inputs, (draft) => {
const targetCase = draft.cases?.find(item => item.case_id === caseId)
if (targetCase)
targetCase.conditions = targetCase.conditions.filter(item => item.id !== conditionId)
})
setInputs(newInputs)
}, [inputs, setInputs])
handleInputsChange(removeCondition(inputsRef.current, caseId, conditionId))
}, [handleInputsChange])
const handleUpdateCondition = useCallback<HandleUpdateCondition>((caseId, conditionId, newCondition) => {
const newInputs = produce(inputs, (draft) => {
const targetCase = draft.cases?.find(item => item.case_id === caseId)
if (targetCase) {
const targetCondition = targetCase.conditions.find(item => item.id === conditionId)
if (targetCondition)
Object.assign(targetCondition, newCondition)
}
})
setInputs(newInputs)
}, [inputs, setInputs])
handleInputsChange(updateCondition(inputsRef.current, caseId, conditionId, newCondition))
}, [handleInputsChange])
const handleToggleConditionLogicalOperator = useCallback<HandleToggleConditionLogicalOperator>((caseId) => {
const newInputs = produce(inputs, (draft) => {
const targetCase = draft.cases?.find(item => item.case_id === caseId)
if (targetCase)
targetCase.logical_operator = targetCase.logical_operator === LogicalOperator.and ? LogicalOperator.or : LogicalOperator.and
})
setInputs(newInputs)
}, [inputs, setInputs])
handleInputsChange(toggleConditionLogicalOperator(inputsRef.current, caseId))
}, [handleInputsChange])
const handleAddSubVariableCondition = useCallback<HandleAddSubVariableCondition>((caseId: string, conditionId: string, key?: string) => {
const newInputs = produce(inputs, (draft) => {
const condition = draft.cases?.find(item => item.case_id === caseId)?.conditions.find(item => item.id === conditionId)
if (!condition)
return
if (!condition?.sub_variable_condition) {
condition.sub_variable_condition = {
case_id: uuid4(),
logical_operator: LogicalOperator.and,
conditions: [],
}
}
const subVarCondition = condition.sub_variable_condition
if (subVarCondition) {
if (!subVarCondition.conditions)
subVarCondition.conditions = []
subVarCondition.conditions.push({
id: uuid4(),
key: key || '',
varType: VarType.string,
comparison_operator: undefined,
value: '',
})
}
})
setInputs(newInputs)
}, [inputs, setInputs])
handleInputsChange(addSubVariableCondition(inputsRef.current, caseId, conditionId, key))
}, [handleInputsChange])
const handleRemoveSubVariableCondition = useCallback((caseId: string, conditionId: string, subConditionId: string) => {
const newInputs = produce(inputs, (draft) => {
const condition = draft.cases?.find(item => item.case_id === caseId)?.conditions.find(item => item.id === conditionId)
if (!condition)
return
if (!condition?.sub_variable_condition)
return
const subVarCondition = condition.sub_variable_condition
if (subVarCondition)
subVarCondition.conditions = subVarCondition.conditions.filter(item => item.id !== subConditionId)
})
setInputs(newInputs)
}, [inputs, setInputs])
handleInputsChange(removeSubVariableCondition(inputsRef.current, caseId, conditionId, subConditionId))
}, [handleInputsChange])
const handleUpdateSubVariableCondition = useCallback<HandleUpdateSubVariableCondition>((caseId, conditionId, subConditionId, newSubCondition) => {
const newInputs = produce(inputs, (draft) => {
const targetCase = draft.cases?.find(item => item.case_id === caseId)
if (targetCase) {
const targetCondition = targetCase.conditions.find(item => item.id === conditionId)
if (targetCondition && targetCondition.sub_variable_condition) {
const targetSubCondition = targetCondition.sub_variable_condition.conditions.find(item => item.id === subConditionId)
if (targetSubCondition)
Object.assign(targetSubCondition, newSubCondition)
}
}
})
setInputs(newInputs)
}, [inputs, setInputs])
handleInputsChange(updateSubVariableCondition(inputsRef.current, caseId, conditionId, subConditionId, newSubCondition))
}, [handleInputsChange])
const handleToggleSubVariableConditionLogicalOperator = useCallback<HandleToggleSubVariableConditionLogicalOperator>((caseId, conditionId) => {
const newInputs = produce(inputs, (draft) => {
const targetCase = draft.cases?.find(item => item.case_id === caseId)
if (targetCase) {
const targetCondition = targetCase.conditions.find(item => item.id === conditionId)
if (targetCondition && targetCondition.sub_variable_condition)
targetCondition.sub_variable_condition.logical_operator = targetCondition.sub_variable_condition.logical_operator === LogicalOperator.and ? LogicalOperator.or : LogicalOperator.and
}
})
setInputs(newInputs)
}, [inputs, setInputs])
handleInputsChange(toggleSubVariableConditionLogicalOperator(inputsRef.current, caseId, conditionId))
}, [handleInputsChange])
return {
readOnly,