/** @jest-environment jsdom */ // Load dependencies used by highlight utils require('../sanitizer.js'); require('../highlight.js'); describe('highlightUtils', () => { const { buildTokens, highlight, escape: esc, formatSnippet } = window.highlightUtils; test('buildTokens normalizes punctuation, trims non-alphanumerics, and dedupes', () => { const tokens = buildTokens(' John, Smith; "Smith" (J.) '); // Expect order preserved except deduping expect(tokens).toEqual(['John', 'Smith', 'J']); const empty = buildTokens(' , ; : '); expect(empty).toEqual([]); }); test('escape encodes special characters safely', () => { const out = esc('
& "quotes" and \'apostrophes\''); expect(out).toContain('<div>'); expect(out).toContain('&'); expect(out).toContain('"'); expect(out).toContain('''); expect(esc('Tom & Jerry')).toBe('Tom & Jerry'); }); test('highlight wraps tokens in and does not break HTML by escaping first', () => { const tokens = buildTokens('john smith'); const result = highlight('Hello John Smith & Sons', tokens); // Should escape original tags and then apply strong expect(result).toContain('<b>'); expect(result).toMatch(/John<\/strong>/i); expect(result).toMatch(/Smith<\/strong>/i); // Ampersand must be escaped expect(result).toContain('& Sons'); }); test('highlight handles overlapping tokens by sequential replacement', () => { const tokens = buildTokens('ann anna'); const out = highlight('Anna and Ann went', tokens); // Both tokens should appear highlighted; order of replacement should not remove prior highlights const strongCount = (out.match(//g) || []).length; expect(strongCount).toBeGreaterThanOrEqual(2); expect(out).toMatch(/Anna<\/strong> and Ann<\/strong> went/i); }); test('formatSnippet uses server-provided strong tags if present', () => { const tokens = buildTokens('alpha'); const serverSnippet = 'Value: Alpha beta'; const html = formatSnippet(serverSnippet, tokens); // Should preserve strong from server expect(html).toContain('Alpha'); // Should be sanitized and not double-escaped expect(html).toContain('Value: '); }); test('formatSnippet applies client-side bold when server snippet is plain text', () => { const tokens = buildTokens('delta'); const plain = 'Gamma delta epsilon'; const html = formatSnippet(plain, tokens); expect(html).toMatch(/Gamma delta<\/strong> epsilon/i); }); test('highlight is case-insensitive and preserves original text casing', () => { const tokens = buildTokens('joHN smiTH'); const out = highlight('John Smith', tokens); // Must wrap both tokens and preserve the original casing from the source text expect(out).toBe('John Smith'); }); test('formatSnippet highlights with mixed-case query tokens but keeps snippet casing', () => { const tokens = buildTokens('doE'); const html = formatSnippet('Hello Doe', tokens); // Exact casing from snippet should be preserved inside expect(html).toContain('Hello Doe'); }); });