Skip to main content
Glama
search-tool.test.ts11.1 kB
import { describe, test, expect, beforeEach, afterEach } from 'vitest'; import { writeFileSync, existsSync, rmSync, mkdirSync } from 'fs'; import { performSearch, formatSearchResults, } from '../../src/core/search-tool.js'; describe('Search Tool', () => { const testDir = 'tests/temp-search'; beforeEach(() => { // Clean up and create test directory if (existsSync(testDir)) { rmSync(testDir, { recursive: true, force: true }); } mkdirSync(testDir, { recursive: true }); // Create test files writeFileSync( `${testDir}/test1.js`, `function testFunction() { const variable = 'test'; return variable; } export function exportedFunction() { console.log('exported'); return 'result'; }` ); writeFileSync( `${testDir}/test2.ts`, `interface TestInterface { id: number; name: string; } export class TestClass { getData(): TestInterface { return { id: 1, name: 'test' }; } }` ); writeFileSync( `${testDir}/context-test.js`, `import legacy_sdk from 'old-package'; const legacy_sdk_local = 'local variable'; console.log(legacy_sdk_local); legacy_sdk.initialize();` ); }); afterEach(() => { if (existsSync(testDir)) { rmSync(testDir, { recursive: true, force: true }); } }); describe('performSearch', () => { const testCases = [ { name: 'should find basic function patterns', options: { searchPattern: 'function.*\\(', filePattern: `${testDir}/**/*.js`, }, expected: { resultCount: 1, filePath: `${testDir}/test1.js`, matchCount: 2, lineNumbers: [1, 6], groupedLines: ['line: 1', 'line: 6'], }, }, { name: 'should find patterns with file filtering', options: { searchPattern: 'export.*', filePattern: `${testDir}/**/*.{js,ts}`, }, expected: { resultCount: 2, hasTest1: true, hasTest2: true, }, }, { name: 'should work with context filtering', options: { searchPattern: 'legacy_sdk', contextPattern: 'import', filePattern: `${testDir}/context-test.js`, }, expected: { resultCount: 1, matchCount: 2, hasImport: true, }, }, { name: 'should return empty results when no matches found', options: { searchPattern: 'nonexistent.*pattern', filePattern: `${testDir}/**/*.js`, }, expected: { resultCount: 0, }, }, { name: 'should handle regex special characters', options: { searchPattern: 'TestInterface\\s*\\{', filePattern: `${testDir}/**/*.ts`, }, expected: { resultCount: 1, matchCount: 2, }, }, { name: 'should find patterns across multiple lines', options: { searchPattern: 'const.*=', filePattern: `${testDir}/**/*.js`, }, expected: { resultCount: 2, hasVariable: true, }, }, { name: 'should capture groups from regex patterns', options: { searchPattern: 'function (\\w+)\\(', filePattern: `${testDir}/**/*.js`, }, expected: { resultCount: 1, matchCount: 2, hasCaptureGroups: true, expectedCaptureGroups: ['testFunction', 'exportedFunction'], }, }, { name: 'should capture multiple groups from regex patterns', options: { searchPattern: '(export )?function (\\w+)\\(', filePattern: `${testDir}/**/*.js`, }, expected: { resultCount: 1, matchCount: 2, hasCaptureGroups: true, hasMultipleCaptureGroups: true, }, }, { name: 'should store matched text in search results', options: { searchPattern: 'function (\\w+)\\(', filePattern: `${testDir}/**/*.js`, }, expected: { resultCount: 1, matchCount: 2, hasMatchedText: true, expectedMatchedTexts: [ 'function testFunction(', 'function exportedFunction(', ], }, }, ]; testCases.forEach(({ name, options, expected }) => { test(name, async () => { const results = await performSearch(options); expect(results).toHaveLength(expected.resultCount); if (expected.filePath) { expect(results[0].filePath).toBe(expected.filePath); } if (expected.matchCount) { expect(results[0].matches).toHaveLength(expected.matchCount); } if (expected.lineNumbers) { expect(results[0].lineNumbers).toEqual(expected.lineNumbers); } if (expected.groupedLines) { expect(results[0].groupedLines).toEqual(expected.groupedLines); } if (expected.hasTest1) { expect(results.some(r => r.filePath.endsWith('test1.js'))).toBe(true); } if (expected.hasTest2) { expect(results.some(r => r.filePath.endsWith('test2.ts'))).toBe(true); } if (expected.hasImport) { expect( results[0].matches.some(m => m.content.includes('import')) ).toBe(true); } if (expected.hasVariable) { expect( results.some(r => r.matches.some(m => m.content.includes('variable')) ) ).toBe(true); } if (expected.hasCaptureGroups) { expect( results[0].matches.some( m => m.captureGroups && m.captureGroups.length > 0 ) ).toBe(true); } if (expected.expectedCaptureGroups) { const allCaptureGroups = results[0].matches .filter(m => m.captureGroups) .flatMap(m => m.captureGroups!); expected.expectedCaptureGroups.forEach(group => { expect(allCaptureGroups).toContain(group); }); } if (expected.hasMultipleCaptureGroups) { const hasMultipleGroups = results[0].matches.some( m => m.captureGroups && m.captureGroups.length > 1 ); expect(hasMultipleGroups).toBe(true); } if (expected.hasMatchedText) { expect( results[0].matches.every(m => m.matchedText !== undefined) ).toBe(true); } if (expected.expectedMatchedTexts) { const allMatchedTexts = results[0].matches .map(m => m.matchedText) .filter(text => text !== undefined); expected.expectedMatchedTexts.forEach(text => { expect(allMatchedTexts).toContain(text); }); } }); }); }); describe('formatSearchResults', () => { const testCases = [ { name: 'should format single result correctly', setupFn: async () => { return await performSearch({ searchPattern: 'function', filePattern: `${testDir}/test1.js`, }); }, expected: { contains: [ 'Search results:', `${testDir}/test1.js`, '(line: 1, line: 6)', ], }, }, { name: 'should format empty results correctly', setupFn: () => [], expected: { equals: 'No matches found for the given pattern', }, }, { name: 'should format multiple results correctly', setupFn: async () => { return await performSearch({ searchPattern: 'export', filePattern: `${testDir}/**/*.{js,ts}`, }); }, expected: { contains: ['Search results:'], minLines: 2, }, }, { name: 'should handle consecutive line numbers correctly', setupFn: async () => { writeFileSync( `${testDir}/consecutive.js`, `const a = 1; const b = 2; const c = 3; const d = 4;` ); return await performSearch({ searchPattern: 'const.*=', filePattern: `${testDir}/consecutive.js`, }); }, expected: { contains: ['lines: 1-4'], }, }, ]; testCases.forEach(({ name, setupFn, expected }) => { test(name, async () => { const results = await setupFn(); const formatted = formatSearchResults(results); if (expected.equals) { expect(formatted).toBe(expected.equals); } if (expected.contains) { expected.contains.forEach(text => { expect(formatted).toContain(text); }); } if (expected.minLines) { expect(formatted.split('\n').length).toBeGreaterThan( expected.minLines ); } }); }); }); describe('edge cases', () => { const testCases = [ { name: 'should handle files with no content', setupFn: () => { writeFileSync(`${testDir}/empty.js`, ''); }, options: { searchPattern: 'anything', filePattern: `${testDir}/empty.js`, }, expected: { resultCount: 0, }, }, { name: 'should handle invalid regex gracefully', setupFn: null, options: { searchPattern: '[invalid regex', filePattern: `${testDir}/**/*.js`, }, expected: { shouldThrow: true, }, }, { name: 'should handle non-existent file patterns', setupFn: null, options: { searchPattern: 'function', filePattern: 'non-existent/**/*.js', }, expected: { resultCount: 0, }, }, { name: 'should handle large files efficiently', setupFn: () => { const largeContent = 'function test() {}\n'.repeat(1000); writeFileSync(`${testDir}/large.js`, largeContent); }, options: { searchPattern: 'function test', filePattern: `${testDir}/large.js`, }, expected: { resultCount: 1, matchCount: 1000, }, }, ]; testCases.forEach(({ name, setupFn, options, expected }) => { test(name, async () => { if (setupFn) { setupFn(); } if (expected.shouldThrow) { await expect(performSearch(options)).rejects.toThrow(); } else { const results = await performSearch(options); expect(results).toHaveLength(expected.resultCount); if (expected.matchCount && results.length > 0) { expect(results[0].matches).toHaveLength(expected.matchCount); } } }); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/myuon/refactor-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server