mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
fix(naming): prevent identical normalized block names (#1105)
This commit is contained in:
@@ -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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user