diff --git a/src/lib/utils/highlightText.test.ts b/src/lib/utils/highlightText.test.ts new file mode 100644 index 0000000..50eaf93 --- /dev/null +++ b/src/lib/utils/highlightText.test.ts @@ -0,0 +1,149 @@ +import { describe, it, expect } from 'vitest'; +import { highlightText } from './highlightText'; + +describe('highlightText', () => { + describe('basic behavior', () => { + it('returns original text when no search term', () => { + expect(highlightText('Hello world', '')).toBe('Hello world'); + }); + + it('returns original text when search term is too short (< 2 chars)', () => { + expect(highlightText('Hello world', 'H')).toBe('Hello world'); + }); + + it('returns empty string for empty input', () => { + expect(highlightText('', 'search')).toBe(''); + }); + + it('returns escaped empty string for empty input with empty query', () => { + expect(highlightText('', '')).toBe(''); + }); + }); + + describe('highlighting matches', () => { + it('highlights single match with mark tag', () => { + const result = highlightText('Hello world', 'world'); + expect(result).toBe('Hello world'); + }); + + it('highlights multiple matches', () => { + const result = highlightText('test one test two test', 'test'); + expect(result).toBe( + 'test one test two test' + ); + }); + + it('highlights match at beginning', () => { + const result = highlightText('start of text', 'start'); + expect(result).toBe('start of text'); + }); + + it('highlights match at end', () => { + const result = highlightText('text at end', 'end'); + expect(result).toBe('text at end'); + }); + }); + + describe('case sensitivity', () => { + it('matches case-insensitively', () => { + const result = highlightText('Hello World', 'hello'); + expect(result).toBe('Hello World'); + }); + + it('preserves original case in highlighted text', () => { + const result = highlightText('HELLO hello Hello', 'hello'); + expect(result).toBe( + 'HELLO hello Hello' + ); + }); + + it('matches uppercase query against lowercase text', () => { + const result = highlightText('lowercase text', 'LOWER'); + expect(result).toBe('lowercase text'); + }); + }); + + describe('special characters', () => { + it('handles special regex characters in search term', () => { + const result = highlightText('test (parentheses) here', '(parentheses)'); + expect(result).toBe( + 'test (parentheses) here' + ); + }); + + it('handles dots in search term', () => { + const result = highlightText('file.txt and file.js', 'file.'); + expect(result).toBe( + 'file.txt and file.js' + ); + }); + + it('handles asterisks in search term', () => { + const result = highlightText('a * b * c', '* b'); + expect(result).toBe('a * b * c'); + }); + + it('handles brackets in search term', () => { + const result = highlightText('array[0] = value', '[0]'); + expect(result).toBe('array[0] = value'); + }); + + it('handles backslashes in search term', () => { + const result = highlightText('path\\to\\file', '\\to'); + expect(result).toBe('path\\to\\file'); + }); + }); + + describe('HTML escaping (XSS prevention)', () => { + it('escapes HTML tags in original text', () => { + const result = highlightText('', 'script'); + expect(result).toContain('<'); + expect(result).toContain('>'); + expect(result).not.toContain('