mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-21 12:51:17 -05:00
Add a Git section to the context mentions
This commit is contained in:
130
webview-ui/src/utils/__tests__/context-mentions.test.ts
Normal file
130
webview-ui/src/utils/__tests__/context-mentions.test.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { insertMention, removeMention, getContextMenuOptions, shouldShowContextMenu, ContextMenuOptionType, ContextMenuQueryItem } from '../context-mentions'
|
||||
|
||||
describe('insertMention', () => {
|
||||
it('should insert mention at cursor position when no @ symbol exists', () => {
|
||||
const result = insertMention('Hello world', 5, 'test')
|
||||
expect(result.newValue).toBe('Hello@test world')
|
||||
expect(result.mentionIndex).toBe(5)
|
||||
})
|
||||
|
||||
it('should replace text after last @ symbol', () => {
|
||||
const result = insertMention('Hello @wor world', 8, 'test')
|
||||
expect(result.newValue).toBe('Hello @test world')
|
||||
expect(result.mentionIndex).toBe(6)
|
||||
})
|
||||
|
||||
it('should handle empty text', () => {
|
||||
const result = insertMention('', 0, 'test')
|
||||
expect(result.newValue).toBe('@test ')
|
||||
expect(result.mentionIndex).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('removeMention', () => {
|
||||
it('should remove mention when cursor is at end of mention', () => {
|
||||
// Test with the problems keyword that matches the regex
|
||||
const result = removeMention('Hello @problems ', 15)
|
||||
expect(result.newText).toBe('Hello ')
|
||||
expect(result.newPosition).toBe(6)
|
||||
})
|
||||
|
||||
it('should not remove text when not at end of mention', () => {
|
||||
const result = removeMention('Hello @test world', 8)
|
||||
expect(result.newText).toBe('Hello @test world')
|
||||
expect(result.newPosition).toBe(8)
|
||||
})
|
||||
|
||||
it('should handle text without mentions', () => {
|
||||
const result = removeMention('Hello world', 5)
|
||||
expect(result.newText).toBe('Hello world')
|
||||
expect(result.newPosition).toBe(5)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getContextMenuOptions', () => {
|
||||
const mockQueryItems: ContextMenuQueryItem[] = [
|
||||
{
|
||||
type: ContextMenuOptionType.File,
|
||||
value: 'src/test.ts',
|
||||
label: 'test.ts',
|
||||
description: 'Source file'
|
||||
},
|
||||
{
|
||||
type: ContextMenuOptionType.Git,
|
||||
value: 'abc1234',
|
||||
label: 'Initial commit',
|
||||
description: 'First commit',
|
||||
icon: '$(git-commit)'
|
||||
},
|
||||
{
|
||||
type: ContextMenuOptionType.Folder,
|
||||
value: 'src',
|
||||
label: 'src',
|
||||
description: 'Source folder'
|
||||
}
|
||||
]
|
||||
|
||||
it('should return all option types for empty query', () => {
|
||||
const result = getContextMenuOptions('', null, [])
|
||||
expect(result).toHaveLength(5)
|
||||
expect(result.map(item => item.type)).toEqual([
|
||||
ContextMenuOptionType.Problems,
|
||||
ContextMenuOptionType.URL,
|
||||
ContextMenuOptionType.Folder,
|
||||
ContextMenuOptionType.File,
|
||||
ContextMenuOptionType.Git
|
||||
])
|
||||
})
|
||||
|
||||
it('should filter by selected type when query is empty', () => {
|
||||
const result = getContextMenuOptions('', ContextMenuOptionType.File, mockQueryItems)
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].type).toBe(ContextMenuOptionType.File)
|
||||
expect(result[0].value).toBe('src/test.ts')
|
||||
})
|
||||
|
||||
it('should match git commands', () => {
|
||||
const result = getContextMenuOptions('git', null, mockQueryItems)
|
||||
expect(result[0].type).toBe(ContextMenuOptionType.Git)
|
||||
expect(result[0].label).toBe('Git Commits')
|
||||
})
|
||||
|
||||
it('should match git commit hashes', () => {
|
||||
const result = getContextMenuOptions('abc1234', null, mockQueryItems)
|
||||
expect(result[0].type).toBe(ContextMenuOptionType.Git)
|
||||
expect(result[0].value).toBe('abc1234')
|
||||
})
|
||||
|
||||
it('should return NoResults when no matches found', () => {
|
||||
const result = getContextMenuOptions('nonexistent', null, mockQueryItems)
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].type).toBe(ContextMenuOptionType.NoResults)
|
||||
})
|
||||
})
|
||||
|
||||
describe('shouldShowContextMenu', () => {
|
||||
it('should return true for @ symbol', () => {
|
||||
expect(shouldShowContextMenu('@', 1)).toBe(true)
|
||||
})
|
||||
|
||||
it('should return true for @ followed by text', () => {
|
||||
expect(shouldShowContextMenu('Hello @test', 10)).toBe(true)
|
||||
})
|
||||
|
||||
it('should return false when no @ symbol exists', () => {
|
||||
expect(shouldShowContextMenu('Hello world', 5)).toBe(false)
|
||||
})
|
||||
|
||||
it('should return false for @ followed by whitespace', () => {
|
||||
expect(shouldShowContextMenu('Hello @ world', 6)).toBe(false)
|
||||
})
|
||||
|
||||
it('should return false for @ in URL', () => {
|
||||
expect(shouldShowContextMenu('Hello @http://test.com', 17)).toBe(false)
|
||||
})
|
||||
|
||||
it('should return false for @problems', () => {
|
||||
// Position cursor at the end to test the full word
|
||||
expect(shouldShowContextMenu('@problems', 9)).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -51,12 +51,16 @@ export enum ContextMenuOptionType {
|
||||
Folder = "folder",
|
||||
Problems = "problems",
|
||||
URL = "url",
|
||||
Git = "git",
|
||||
NoResults = "noResults",
|
||||
}
|
||||
|
||||
export interface ContextMenuQueryItem {
|
||||
type: ContextMenuOptionType
|
||||
value?: string
|
||||
label?: string
|
||||
description?: string
|
||||
icon?: string
|
||||
}
|
||||
|
||||
export function getContextMenuOptions(
|
||||
@@ -64,6 +68,14 @@ export function getContextMenuOptions(
|
||||
selectedType: ContextMenuOptionType | null = null,
|
||||
queryItems: ContextMenuQueryItem[],
|
||||
): ContextMenuQueryItem[] {
|
||||
const workingChanges: ContextMenuQueryItem = {
|
||||
type: ContextMenuOptionType.Git,
|
||||
value: "git-changes",
|
||||
label: "Working changes",
|
||||
description: "Current uncommitted changes",
|
||||
icon: "$(git-commit)"
|
||||
}
|
||||
|
||||
if (query === "") {
|
||||
if (selectedType === ContextMenuOptionType.File) {
|
||||
const files = queryItems
|
||||
@@ -79,30 +91,88 @@ export function getContextMenuOptions(
|
||||
return folders.length > 0 ? folders : [{ type: ContextMenuOptionType.NoResults }]
|
||||
}
|
||||
|
||||
if (selectedType === ContextMenuOptionType.Git) {
|
||||
const commits = queryItems
|
||||
.filter((item) => item.type === ContextMenuOptionType.Git)
|
||||
return commits.length > 0 ? [workingChanges, ...commits] : [workingChanges]
|
||||
}
|
||||
|
||||
return [
|
||||
{ type: ContextMenuOptionType.URL },
|
||||
{ type: ContextMenuOptionType.Problems },
|
||||
{ type: ContextMenuOptionType.URL },
|
||||
{ type: ContextMenuOptionType.Folder },
|
||||
{ type: ContextMenuOptionType.File },
|
||||
{ type: ContextMenuOptionType.Git },
|
||||
]
|
||||
}
|
||||
|
||||
const lowerQuery = query.toLowerCase()
|
||||
const suggestions: ContextMenuQueryItem[] = []
|
||||
|
||||
// Check for top-level option matches
|
||||
if ("git".startsWith(lowerQuery)) {
|
||||
suggestions.push({
|
||||
type: ContextMenuOptionType.Git,
|
||||
label: "Git Commits",
|
||||
description: "Search repository history",
|
||||
icon: "$(git-commit)"
|
||||
})
|
||||
} else if ("git-changes".startsWith(lowerQuery)) {
|
||||
suggestions.push(workingChanges)
|
||||
}
|
||||
if ("problems".startsWith(lowerQuery)) {
|
||||
suggestions.push({ type: ContextMenuOptionType.Problems })
|
||||
}
|
||||
if (query.startsWith("http")) {
|
||||
return [{ type: ContextMenuOptionType.URL, value: query }]
|
||||
} else {
|
||||
const matchingItems = queryItems.filter((item) => item.value?.toLowerCase().includes(lowerQuery))
|
||||
suggestions.push({ type: ContextMenuOptionType.URL, value: query })
|
||||
}
|
||||
|
||||
if (matchingItems.length > 0) {
|
||||
return matchingItems.map((item) => ({
|
||||
type: item.type,
|
||||
value: item.value,
|
||||
}))
|
||||
// Add exact SHA matches to suggestions
|
||||
if (/^[a-f0-9]{7,40}$/i.test(lowerQuery)) {
|
||||
const exactMatches = queryItems.filter((item) =>
|
||||
item.type === ContextMenuOptionType.Git &&
|
||||
item.value?.toLowerCase() === lowerQuery
|
||||
)
|
||||
if (exactMatches.length > 0) {
|
||||
suggestions.push(...exactMatches)
|
||||
} else {
|
||||
return [{ type: ContextMenuOptionType.NoResults }]
|
||||
// If no exact match but valid SHA format, add as option
|
||||
suggestions.push({
|
||||
type: ContextMenuOptionType.Git,
|
||||
value: lowerQuery,
|
||||
label: `Commit ${lowerQuery}`,
|
||||
description: "Git commit hash",
|
||||
icon: "$(git-commit)"
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Get matching items, separating by type
|
||||
const matchingItems = queryItems.filter((item) =>
|
||||
item.value?.toLowerCase().includes(lowerQuery) ||
|
||||
item.label?.toLowerCase().includes(lowerQuery) ||
|
||||
item.description?.toLowerCase().includes(lowerQuery)
|
||||
)
|
||||
|
||||
const fileMatches = matchingItems.filter(item =>
|
||||
item.type === ContextMenuOptionType.File ||
|
||||
item.type === ContextMenuOptionType.Folder
|
||||
)
|
||||
const gitMatches = matchingItems.filter(item =>
|
||||
item.type === ContextMenuOptionType.Git
|
||||
)
|
||||
const otherMatches = matchingItems.filter(item =>
|
||||
item.type !== ContextMenuOptionType.File &&
|
||||
item.type !== ContextMenuOptionType.Folder &&
|
||||
item.type !== ContextMenuOptionType.Git
|
||||
)
|
||||
|
||||
// Combine suggestions with matching items in the desired order
|
||||
if (suggestions.length > 0 || matchingItems.length > 0) {
|
||||
return [...suggestions, ...fileMatches, ...gitMatches, ...otherMatches]
|
||||
}
|
||||
|
||||
return [{ type: ContextMenuOptionType.NoResults }]
|
||||
}
|
||||
|
||||
export function shouldShowContextMenu(text: string, position: number): boolean {
|
||||
|
||||
Reference in New Issue
Block a user