/** @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');
});
});