From dd0787d6c49e5ab23428bd726df2a2f572a143d5 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 17 Jun 2025 21:29:01 -0700 Subject: [PATCH] fixed extra closing tag on tag dropdown autocomplete --- apps/sim/components/ui/tag-dropdown.test.tsx | 114 +++++++++++++++++++ apps/sim/components/ui/tag-dropdown.tsx | 18 ++- 2 files changed, 131 insertions(+), 1 deletion(-) diff --git a/apps/sim/components/ui/tag-dropdown.test.tsx b/apps/sim/components/ui/tag-dropdown.test.tsx index 6cb6541c055..f21fc8cd549 100644 --- a/apps/sim/components/ui/tag-dropdown.test.tsx +++ b/apps/sim/components/ui/tag-dropdown.test.tsx @@ -485,3 +485,117 @@ describe('TagDropdown Tag Ordering', () => { expect(tagIndexMap.get('nonexistent')).toBeUndefined() }) }) + +describe('TagDropdown Tag Selection Logic', () => { + test('should handle existing closing bracket correctly when editing tags', () => { + const testCases = [ + { + description: 'should remove existing closing bracket from incomplete tag', + inputValue: 'Hello ', + cursorPosition: 21, // cursor after the dot + tag: 'start.response.input', + expectedResult: 'Hello ', + }, + { + description: 'should remove existing closing bracket when replacing tag content', + inputValue: 'Hello ', + cursorPosition: 22, // cursor after 'response.' + tag: 'start.response.data', + expectedResult: 'Hello ', + }, + { + description: 'should preserve content after closing bracket', + inputValue: 'Hello world', + cursorPosition: 21, + tag: 'start.response.input', + expectedResult: 'Hello world', + }, + { + description: + 'should not affect closing bracket if text between contains invalid characters', + inputValue: 'Hello and ', + cursorPosition: 22, + tag: 'start.response.data', + expectedResult: 'Hello and ', + }, + { + description: 'should handle case with no existing closing bracket', + inputValue: 'Hello ', + }, + ] + + testCases.forEach(({ description, inputValue, cursorPosition, tag, expectedResult }) => { + // Simulate the handleTagSelect logic + const textBeforeCursor = inputValue.slice(0, cursorPosition) + const textAfterCursor = inputValue.slice(cursorPosition) + const lastOpenBracket = textBeforeCursor.lastIndexOf('<') + + // Apply the new logic for handling existing closing brackets + const nextCloseBracket = textAfterCursor.indexOf('>') + let remainingTextAfterCursor = textAfterCursor + + if (nextCloseBracket !== -1) { + const textBetween = textAfterCursor.slice(0, nextCloseBracket) + if (/^[a-zA-Z0-9._]*$/.test(textBetween)) { + remainingTextAfterCursor = textAfterCursor.slice(nextCloseBracket + 1) + } + } + + const newValue = `${textBeforeCursor.slice(0, lastOpenBracket)}<${tag}>${remainingTextAfterCursor}` + + expect(newValue).toBe(expectedResult) + }) + }) + + test('should validate tag-like character regex correctly', () => { + const regex = /^[a-zA-Z0-9._]*$/ + + // Valid tag-like text + expect(regex.test('')).toBe(true) // empty string + expect(regex.test('input')).toBe(true) + expect(regex.test('response.data')).toBe(true) + expect(regex.test('user_name')).toBe(true) + expect(regex.test('item123')).toBe(true) + expect(regex.test('response.data.item_1')).toBe(true) + + // Invalid tag-like text (should not remove closing bracket) + expect(regex.test('input> and more')).toBe(false) + expect(regex.test('response data')).toBe(false) // space + expect(regex.test('user-name')).toBe(false) // hyphen + expect(regex.test('data[')).toBe(false) // bracket + expect(regex.test('response.data!')).toBe(false) // exclamation + }) + + test('should find correct position of last open bracket', () => { + const testCases = [ + { input: 'Hello and { + const lastOpenBracket = input.lastIndexOf('<') + expect(lastOpenBracket).toBe(expected) + }) + }) + + test('should find correct position of next closing bracket', () => { + const testCases = [ + { input: 'input>', expected: 5 }, + { input: 'response.data> more text', expected: 13 }, + { input: 'no closing bracket', expected: -1 }, + { input: '>', expected: 0 }, + { input: 'multiple > > > >last', expected: 9 }, + ] + + testCases.forEach(({ input, expected }) => { + const nextCloseBracket = input.indexOf('>') + expect(nextCloseBracket).toBe(expected) + }) + }) +}) diff --git a/apps/sim/components/ui/tag-dropdown.tsx b/apps/sim/components/ui/tag-dropdown.tsx index c8210247256..78c9bf0a146 100644 --- a/apps/sim/components/ui/tag-dropdown.tsx +++ b/apps/sim/components/ui/tag-dropdown.tsx @@ -475,7 +475,23 @@ export const TagDropdown: React.FC = ({ } } - const newValue = `${textBeforeCursor.slice(0, lastOpenBracket)}<${processedTag}>${textAfterCursor}` + // Check if there's a closing bracket in textAfterCursor that belongs to the current tag + // Find the first '>' in textAfterCursor (if any) + const nextCloseBracket = textAfterCursor.indexOf('>') + let remainingTextAfterCursor = textAfterCursor + + // If there's a '>' right after the cursor or with only whitespace/tag content in between, + // it's likely part of the existing tag being edited, so we should skip it + if (nextCloseBracket !== -1) { + const textBetween = textAfterCursor.slice(0, nextCloseBracket) + // If the text between cursor and '>' contains only tag-like characters (letters, dots, numbers) + // then it's likely part of the current tag being edited + if (/^[a-zA-Z0-9._]*$/.test(textBetween)) { + remainingTextAfterCursor = textAfterCursor.slice(nextCloseBracket + 1) + } + } + + const newValue = `${textBeforeCursor.slice(0, lastOpenBracket)}<${processedTag}>${remainingTextAfterCursor}` onSelect(newValue) onClose?.()