/** * Tests for KeywordDetector */ import { describe, it, expect } from 'vitest'; import { detectKeywords, hasKeyword, getAllKeywords, getPrimaryKeyword, getKeywordType, hasKeywordType, sanitizeText, removeCodeBlocks, KEYWORD_PATTERNS, KEYWORD_PRIORITY } from '../src/core/hooks/keyword-detector.js'; import type { KeywordType, DetectedKeyword } from '../src/core/hooks/keyword-detector.js'; describe('removeCodeBlocks', () => { it('should remove fenced code blocks with ```', () => { const text = 'Hello ```code here``` world'; expect(removeCodeBlocks(text)).toBe('Hello world'); }); it('should remove fenced code blocks with ~~~', () => { const text = 'Hello ~~~code here~~~ world'; expect(removeCodeBlocks(text)).toBe('Hello world'); }); it('should remove inline code with backticks', () => { const text = 'Use the `forEach` method'; expect(removeCodeBlocks(text)).toBe('Use the method'); }); it('should handle multiline code blocks', () => { const text = 'Start\n```\nline1\nline2\n```\nEnd'; expect(removeCodeBlocks(text)).toBe('Start\n\nEnd'); }); it('should handle multiple code blocks', () => { const text = 'Use `a` and `b` and ```c``` too'; expect(removeCodeBlocks(text)).toBe('Use and and too'); }); }); describe('sanitizeText', () => { it('should remove XML tag blocks', () => { const text = 'Hello content world'; expect(sanitizeText(text)).toBe('Hello world'); }); it('should remove self-closing XML tags', () => { const text = 'Hello
world'; expect(sanitizeText(text)).toBe('Hello world'); }); it('should remove URLs', () => { const text = 'Visit https://example.com for more'; expect(sanitizeText(text)).toBe('Visit for more'); }); it('should remove file paths with ./', () => { const text = 'Check ./src/file.ts for details'; expect(sanitizeText(text)).toBe('Check for details'); }); it('should remove file paths with /', () => { const text = 'Edit /home/user/file.ts'; expect(sanitizeText(text)).toBe('Edit '); }); it('should remove code blocks', () => { const text = 'See ```code``` below'; expect(sanitizeText(text)).toBe('See below'); }); it('should handle complex text', () => { const text = 'Use api_key from https://example.com and check ./config.ts'; const sanitized = sanitizeText(text); expect(sanitized).not.toContain(''); expect(sanitized).not.toContain('https://'); expect(sanitized).not.toContain('./config.ts'); }); }); describe('detectKeywords', () => { describe('basic detection', () => { it('should detect "autopilot" keyword', () => { const keywords = detectKeywords('use autopilot mode'); expect(keywords.some(k => k.type === 'autopilot')).toBe(true); }); it('should detect "ultrawork" keyword', () => { const keywords = detectKeywords('run ultrawork now'); expect(keywords.some(k => k.type === 'ultrawork')).toBe(true); }); it('should detect "ultrawork" alias "ulw"', () => { const keywords = detectKeywords('use ulw for this'); expect(keywords.some(k => k.type === 'ultrawork')).toBe(true); }); it('should detect "plan this" keyword', () => { const keywords = detectKeywords('please plan this feature'); expect(keywords.some(k => k.type === 'plan')).toBe(true); }); it('should detect "tdd" keyword', () => { const keywords = detectKeywords('use tdd approach'); expect(keywords.some(k => k.type === 'tdd')).toBe(true); }); it('should detect "ultrathink" keyword', () => { const keywords = detectKeywords('ultrathink about this'); expect(keywords.some(k => k.type === 'ultrathink')).toBe(true); }); it('should detect "deepsearch" keyword', () => { const keywords = detectKeywords('deepsearch for the answer'); expect(keywords.some(k => k.type === 'deepsearch')).toBe(true); }); }); describe('cancel keyword', () => { it('should detect "cancelomc" keyword', () => { const keywords = detectKeywords('cancelomc'); expect(keywords.some(k => k.type === 'cancel')).toBe(true); }); it('should detect "stopomc" keyword', () => { const keywords = detectKeywords('stopomc'); expect(keywords.some(k => k.type === 'cancel')).toBe(true); }); }); describe('delegation keywords', () => { it('should detect "ask codex" keyword', () => { const keywords = detectKeywords('ask codex to help'); expect(keywords.some(k => k.type === 'codex')).toBe(true); }); it('should detect "use gemini" keyword', () => { const keywords = detectKeywords('use gemini for this'); expect(keywords.some(k => k.type === 'gemini')).toBe(true); }); it('should detect "delegate to gpt" keyword', () => { const keywords = detectKeywords('delegate to gpt'); expect(keywords.some(k => k.type === 'codex')).toBe(true); }); }); describe('case insensitivity', () => { it('should be case-insensitive', () => { const keywords = detectKeywords('AUTOPILOT and ULTRAWORK'); expect(keywords.some(k => k.type === 'autopilot')).toBe(true); expect(keywords.some(k => k.type === 'ultrawork')).toBe(true); }); }); describe('team feature flag', () => { it('should skip team keywords when teamEnabled is false', () => { const keywords = detectKeywords('use team mode', { teamEnabled: false }); expect(keywords.some(k => k.type === 'team')).toBe(false); }); it('should detect team keywords when teamEnabled is true', () => { const keywords = detectKeywords('use team mode', { teamEnabled: true }); expect(keywords.some(k => k.type === 'team')).toBe(true); }); }); describe('code block filtering', () => { it('should not detect keywords in code blocks', () => { const text = 'Do this:\n```\nautopilot\n```\nnot in code'; const keywords = detectKeywords(text); expect(keywords.some(k => k.type === 'autopilot')).toBe(false); }); it('should not detect keywords in inline code', () => { const text = 'Use the `autopilot` function'; const keywords = detectKeywords(text); expect(keywords.some(k => k.type === 'autopilot')).toBe(false); }); }); describe('returned metadata', () => { it('should include keyword position', () => { const keywords = detectKeywords('please use autopilot now'); const autopilot = keywords.find(k => k.type === 'autopilot'); expect(autopilot?.position).toBe(11); // 'autopilot' starts at position 11 }); it('should include matched keyword string', () => { const keywords = detectKeywords('use AUTOPILOT'); const autopilot = keywords.find(k => k.type === 'autopilot'); expect(autopilot?.keyword).toBe('AUTOPILOT'); }); }); }); describe('hasKeyword', () => { it('should return true when keyword is present', () => { expect(hasKeyword('use autopilot')).toBe(true); }); it('should return false when no keyword is present', () => { expect(hasKeyword('hello world')).toBe(false); }); }); describe('getAllKeywords', () => { describe('conflict resolution', () => { it('should return only "cancel" when cancel is present', () => { const types = getAllKeywords('cancelomc and autopilot'); expect(types).toEqual(['cancel']); }); it('should remove "autopilot" when "team" is present', () => { const types = getAllKeywords('use autopilot and team mode', { teamEnabled: true }); expect(types).not.toContain('autopilot'); expect(types).toContain('team'); }); }); it('should return empty array when no keywords', () => { expect(getAllKeywords('hello world')).toEqual([]); }); it('should return keywords in priority order', () => { const types = getAllKeywords('use plan this and tdd'); // 'plan' has priority 9, 'tdd' has priority 10 expect(types).toEqual(['plan', 'tdd']); }); }); describe('getPrimaryKeyword', () => { it('should return null when no keywords', () => { expect(getPrimaryKeyword('hello world')).toBeNull(); }); it('should return highest priority keyword', () => { const keyword = getPrimaryKeyword('use plan this and tdd'); expect(keyword?.type).toBe('plan'); }); it('should return cancel as primary when present', () => { const keyword = getPrimaryKeyword('use autopilot and cancelomc'); expect(keyword?.type).toBe('cancel'); }); it('should include original keyword metadata', () => { const keyword = getPrimaryKeyword('USE AUTOPILOT'); expect(keyword?.keyword).toBe('AUTOPILOT'); expect(keyword?.position).toBeDefined(); }); }); describe('getKeywordType', () => { it('should return type for valid keyword', () => { expect(getKeywordType('autopilot')).toBe('autopilot'); }); it('should return null for invalid keyword', () => { expect(getKeywordType('notakeyword')).toBeNull(); }); }); describe('hasKeywordType', () => { it('should return true for present keyword type', () => { expect(hasKeywordType('use autopilot', 'autopilot')).toBe(true); }); it('should return false for absent keyword type', () => { expect(hasKeywordType('hello world', 'autopilot')).toBe(false); }); it('should sanitize text before checking', () => { expect(hasKeywordType('use `autopilot` in code', 'autopilot')).toBe(false); }); }); describe('KEYWORD_PRIORITY', () => { it('should have cancel as highest priority', () => { expect(KEYWORD_PRIORITY[0]).toBe('cancel'); }); it('should have gemini as lowest priority', () => { expect(KEYWORD_PRIORITY[KEYWORD_PRIORITY.length - 1]).toBe('gemini'); }); }); describe('KEYWORD_PATTERNS', () => { it('should have patterns for all keyword types', () => { const types: KeywordType[] = [ 'cancel', 'ralph', 'autopilot', 'ultrapilot', 'team', 'ultrawork', 'swarm', 'pipeline', 'ralplan', 'plan', 'tdd', 'ultrathink', 'deepsearch', 'analyze', 'codex', 'gemini' ]; types.forEach(type => { expect(KEYWORD_PATTERNS[type]).toBeDefined(); expect(KEYWORD_PATTERNS[type] instanceof RegExp).toBe(true); }); }); });