Skip to content

Commit 537e17d

Browse files
authored
fix(autocomplete): fixed extra closing tag on tag dropdown autocomplete (#514)
1 parent dd50fb3 commit 537e17d

File tree

2 files changed

+131
-1
lines changed

2 files changed

+131
-1
lines changed

apps/sim/components/ui/tag-dropdown.test.tsx

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,3 +485,117 @@ describe('TagDropdown Tag Ordering', () => {
485485
expect(tagIndexMap.get('nonexistent')).toBeUndefined()
486486
})
487487
})
488+
489+
describe('TagDropdown Tag Selection Logic', () => {
490+
test('should handle existing closing bracket correctly when editing tags', () => {
491+
const testCases = [
492+
{
493+
description: 'should remove existing closing bracket from incomplete tag',
494+
inputValue: 'Hello <start.response.>',
495+
cursorPosition: 21, // cursor after the dot
496+
tag: 'start.response.input',
497+
expectedResult: 'Hello <start.response.input>',
498+
},
499+
{
500+
description: 'should remove existing closing bracket when replacing tag content',
501+
inputValue: 'Hello <start.response.input>',
502+
cursorPosition: 22, // cursor after 'response.'
503+
tag: 'start.response.data',
504+
expectedResult: 'Hello <start.response.data>',
505+
},
506+
{
507+
description: 'should preserve content after closing bracket',
508+
inputValue: 'Hello <start.response.> world',
509+
cursorPosition: 21,
510+
tag: 'start.response.input',
511+
expectedResult: 'Hello <start.response.input> world',
512+
},
513+
{
514+
description:
515+
'should not affect closing bracket if text between contains invalid characters',
516+
inputValue: 'Hello <start.response.input> and <other>',
517+
cursorPosition: 22,
518+
tag: 'start.response.data',
519+
expectedResult: 'Hello <start.response.data> and <other>',
520+
},
521+
{
522+
description: 'should handle case with no existing closing bracket',
523+
inputValue: 'Hello <start.response',
524+
cursorPosition: 21,
525+
tag: 'start.response.input',
526+
expectedResult: 'Hello <start.response.input>',
527+
},
528+
]
529+
530+
testCases.forEach(({ description, inputValue, cursorPosition, tag, expectedResult }) => {
531+
// Simulate the handleTagSelect logic
532+
const textBeforeCursor = inputValue.slice(0, cursorPosition)
533+
const textAfterCursor = inputValue.slice(cursorPosition)
534+
const lastOpenBracket = textBeforeCursor.lastIndexOf('<')
535+
536+
// Apply the new logic for handling existing closing brackets
537+
const nextCloseBracket = textAfterCursor.indexOf('>')
538+
let remainingTextAfterCursor = textAfterCursor
539+
540+
if (nextCloseBracket !== -1) {
541+
const textBetween = textAfterCursor.slice(0, nextCloseBracket)
542+
if (/^[a-zA-Z0-9._]*$/.test(textBetween)) {
543+
remainingTextAfterCursor = textAfterCursor.slice(nextCloseBracket + 1)
544+
}
545+
}
546+
547+
const newValue = `${textBeforeCursor.slice(0, lastOpenBracket)}<${tag}>${remainingTextAfterCursor}`
548+
549+
expect(newValue).toBe(expectedResult)
550+
})
551+
})
552+
553+
test('should validate tag-like character regex correctly', () => {
554+
const regex = /^[a-zA-Z0-9._]*$/
555+
556+
// Valid tag-like text
557+
expect(regex.test('')).toBe(true) // empty string
558+
expect(regex.test('input')).toBe(true)
559+
expect(regex.test('response.data')).toBe(true)
560+
expect(regex.test('user_name')).toBe(true)
561+
expect(regex.test('item123')).toBe(true)
562+
expect(regex.test('response.data.item_1')).toBe(true)
563+
564+
// Invalid tag-like text (should not remove closing bracket)
565+
expect(regex.test('input> and more')).toBe(false)
566+
expect(regex.test('response data')).toBe(false) // space
567+
expect(regex.test('user-name')).toBe(false) // hyphen
568+
expect(regex.test('data[')).toBe(false) // bracket
569+
expect(regex.test('response.data!')).toBe(false) // exclamation
570+
})
571+
572+
test('should find correct position of last open bracket', () => {
573+
const testCases = [
574+
{ input: 'Hello <start.response', expected: 6 },
575+
{ input: 'Hello <var> and <start.response', expected: 16 },
576+
{ input: 'No brackets here', expected: -1 },
577+
{ input: '<start.response', expected: 0 },
578+
{ input: 'Multiple < < < <last', expected: 15 },
579+
]
580+
581+
testCases.forEach(({ input, expected }) => {
582+
const lastOpenBracket = input.lastIndexOf('<')
583+
expect(lastOpenBracket).toBe(expected)
584+
})
585+
})
586+
587+
test('should find correct position of next closing bracket', () => {
588+
const testCases = [
589+
{ input: 'input>', expected: 5 },
590+
{ input: 'response.data> more text', expected: 13 },
591+
{ input: 'no closing bracket', expected: -1 },
592+
{ input: '>', expected: 0 },
593+
{ input: 'multiple > > > >last', expected: 9 },
594+
]
595+
596+
testCases.forEach(({ input, expected }) => {
597+
const nextCloseBracket = input.indexOf('>')
598+
expect(nextCloseBracket).toBe(expected)
599+
})
600+
})
601+
})

apps/sim/components/ui/tag-dropdown.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,23 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
475475
}
476476
}
477477

478-
const newValue = `${textBeforeCursor.slice(0, lastOpenBracket)}<${processedTag}>${textAfterCursor}`
478+
// Check if there's a closing bracket in textAfterCursor that belongs to the current tag
479+
// Find the first '>' in textAfterCursor (if any)
480+
const nextCloseBracket = textAfterCursor.indexOf('>')
481+
let remainingTextAfterCursor = textAfterCursor
482+
483+
// If there's a '>' right after the cursor or with only whitespace/tag content in between,
484+
// it's likely part of the existing tag being edited, so we should skip it
485+
if (nextCloseBracket !== -1) {
486+
const textBetween = textAfterCursor.slice(0, nextCloseBracket)
487+
// If the text between cursor and '>' contains only tag-like characters (letters, dots, numbers)
488+
// then it's likely part of the current tag being edited
489+
if (/^[a-zA-Z0-9._]*$/.test(textBetween)) {
490+
remainingTextAfterCursor = textAfterCursor.slice(nextCloseBracket + 1)
491+
}
492+
}
493+
494+
const newValue = `${textBeforeCursor.slice(0, lastOpenBracket)}<${processedTag}>${remainingTextAfterCursor}`
479495

480496
onSelect(newValue)
481497
onClose?.()

0 commit comments

Comments
 (0)