fix(naming): prevent identical normalized block names (#1105)

This commit is contained in:
Waleed Latif
2025-08-22 13:20:45 -07:00
committed by GitHub
parent a268fb7c04
commit 60c4668682
3 changed files with 159 additions and 2 deletions

View File

@@ -603,4 +603,133 @@ describe('workflow store', () => {
expect(childBlock.data?.extent).toBe('parent')
})
})
describe('updateBlockName', () => {
beforeEach(() => {
useWorkflowStore.setState({
blocks: {},
edges: [],
loops: {},
parallels: {},
})
const { addBlock } = useWorkflowStore.getState()
addBlock('block1', 'agent', 'Column AD', { x: 0, y: 0 })
addBlock('block2', 'function', 'Employee Length', { x: 100, y: 0 })
addBlock('block3', 'trigger', 'Start', { x: 200, y: 0 })
})
it('should have test blocks set up correctly', () => {
const state = useWorkflowStore.getState()
expect(state.blocks.block1).toBeDefined()
expect(state.blocks.block1.name).toBe('Column AD')
expect(state.blocks.block2).toBeDefined()
expect(state.blocks.block2.name).toBe('Employee Length')
expect(state.blocks.block3).toBeDefined()
expect(state.blocks.block3.name).toBe('Start')
})
it('should successfully rename a block when no conflicts exist', () => {
const { updateBlockName } = useWorkflowStore.getState()
const result = updateBlockName('block1', 'Data Processor')
expect(result).toBe(true)
const state = useWorkflowStore.getState()
expect(state.blocks.block1.name).toBe('Data Processor')
})
it('should allow renaming a block to a different case/spacing of its current name', () => {
const { updateBlockName } = useWorkflowStore.getState()
const result = updateBlockName('block1', 'column ad')
expect(result).toBe(true)
const state = useWorkflowStore.getState()
expect(state.blocks.block1.name).toBe('column ad')
})
it('should prevent renaming when another block has the same normalized name', () => {
const { updateBlockName } = useWorkflowStore.getState()
const result = updateBlockName('block2', 'Column AD')
expect(result).toBe(false)
const state = useWorkflowStore.getState()
expect(state.blocks.block2.name).toBe('Employee Length')
})
it('should prevent renaming when another block has a name that normalizes to the same value', () => {
const { updateBlockName } = useWorkflowStore.getState()
const result = updateBlockName('block2', 'columnad')
expect(result).toBe(false)
const state = useWorkflowStore.getState()
expect(state.blocks.block2.name).toBe('Employee Length')
})
it('should prevent renaming when another block has a similar name with different spacing', () => {
const { updateBlockName } = useWorkflowStore.getState()
const result = updateBlockName('block3', 'employee length')
expect(result).toBe(false)
const state = useWorkflowStore.getState()
expect(state.blocks.block3.name).toBe('Start')
})
it('should handle edge cases with empty or whitespace-only names', () => {
const { updateBlockName } = useWorkflowStore.getState()
const result1 = updateBlockName('block1', '')
expect(result1).toBe(true)
const result2 = updateBlockName('block2', ' ')
expect(result2).toBe(true)
const state = useWorkflowStore.getState()
expect(state.blocks.block1.name).toBe('')
expect(state.blocks.block2.name).toBe(' ')
})
it('should return false when trying to rename a non-existent block', () => {
const { updateBlockName } = useWorkflowStore.getState()
const result = updateBlockName('nonexistent', 'New Name')
expect(result).toBe(false)
})
it('should handle complex normalization cases correctly', () => {
const { updateBlockName } = useWorkflowStore.getState()
const conflictingNames = [
'column ad',
'COLUMN AD',
'Column AD',
'columnad',
'ColumnAD',
'COLUMNAD',
]
for (const name of conflictingNames) {
const result = updateBlockName('block2', name)
expect(result).toBe(false)
}
const result = updateBlockName('block2', 'Unique Name')
expect(result).toBe(true)
const state = useWorkflowStore.getState()
expect(state.blocks.block2.name).toBe('Unique Name')
})
})
})

View File

@@ -601,7 +601,33 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
updateBlockName: (id: string, name: string) => {
const oldBlock = get().blocks[id]
if (!oldBlock) return
if (!oldBlock) return false
// Helper function to normalize block names (same as resolver)
const normalizeBlockName = (blockName: string): string => {
return blockName.toLowerCase().replace(/\s+/g, '')
}
// Check for normalized name collisions
const normalizedNewName = normalizeBlockName(name)
const currentBlocks = get().blocks
// Find any other block with the same normalized name
const conflictingBlock = Object.entries(currentBlocks).find(([blockId, block]) => {
return (
blockId !== id && // Different block
block.name && // Has a name
normalizeBlockName(block.name) === normalizedNewName // Same normalized name
)
})
if (conflictingBlock) {
// Don't allow the rename - another block already uses this normalized name
logger.error(
`Cannot rename block to "${name}" - another block "${conflictingBlock[1].name}" already uses the normalized name "${normalizedNewName}"`
)
return false
}
// Create a new state with the updated block name
const newState = {
@@ -696,6 +722,8 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
pushHistory(set, get, newState, `${name} block name updated`)
get().updateLastSaved()
// Note: Socket.IO handles real-time sync automatically
return true
},
toggleBlockWide: (id: string) => {

View File

@@ -183,7 +183,7 @@ export interface WorkflowActions {
toggleBlockEnabled: (id: string) => void
duplicateBlock: (id: string) => void
toggleBlockHandles: (id: string) => void
updateBlockName: (id: string, name: string) => void
updateBlockName: (id: string, name: string) => boolean
toggleBlockWide: (id: string) => void
setBlockWide: (id: string, isWide: boolean) => void
setBlockAdvancedMode: (id: string, advancedMode: boolean) => void