/**
* Tests for security/xss-protection.js
*
* Tests HTML escaping, sanitization, and safe DOM manipulation
* functions that protect against XSS attacks.
*/
import '@js/security/xss-protection.js';
const { escapeHtml, escapeHtmlAttribute, sanitizeUserInput,
safeSetInnerHTML, safeSetTextContent } = window;
describe('escapeHtml', () => {
it('escapes ampersands', () => {
expect(escapeHtml('a & b')).toBe('a & b');
});
it('escapes angle brackets', () => {
expect(escapeHtml('')).toBe(
'<script>alert(1)</script>'
);
});
it('escapes double quotes', () => {
expect(escapeHtml('"hello"')).toBe('"hello"');
});
it('escapes single quotes', () => {
expect(escapeHtml("it's")).toBe('it's');
});
it('escapes forward slashes', () => {
expect(escapeHtml('a/b')).toBe('a/b');
});
it('returns empty string for null', () => {
expect(escapeHtml(null)).toBe('');
});
it('returns empty string for undefined', () => {
expect(escapeHtml(undefined)).toBe('');
});
it('converts non-strings to string first', () => {
expect(escapeHtml(123)).toBe('123');
expect(escapeHtml(true)).toBe('true');
});
it('handles strings with no special characters', () => {
expect(escapeHtml('hello world')).toBe('hello world');
});
it('handles empty string', () => {
expect(escapeHtml('')).toBe('');
});
it('escapes all dangerous characters together', () => {
expect(escapeHtml('
')).toBe(
'<img onerror="alert('xss')" />'
);
});
});
describe('escapeHtmlAttribute', () => {
it('escapes double quotes for attribute context', () => {
expect(escapeHtmlAttribute('value"with"quotes')).toContain('"');
});
it('escapes angle brackets', () => {
expect(escapeHtmlAttribute('')).toContain('<');
});
it('converts non-strings', () => {
expect(escapeHtmlAttribute(42)).toBe('42');
});
});
describe('sanitizeUserInput', () => {
it('returns empty string for null/undefined', () => {
expect(sanitizeUserInput(null)).toBe('');
expect(sanitizeUserInput(undefined)).toBe('');
});
it('trims whitespace by default', () => {
const result = sanitizeUserInput(' hello ');
expect(result).not.toMatch(/^\s/);
expect(result).not.toMatch(/\s$/);
});
it('does not trim when option is false', () => {
const result = sanitizeUserInput(' hello ', { trimWhitespace: false });
// The result is escaped, so check it contains the text
expect(result).toContain('hello');
});
it('enforces maxLength', () => {
const long = 'a'.repeat(200);
const result = sanitizeUserInput(long, { maxLength: 10 });
// After escaping, the core content is truncated to 10 chars
expect(result.length).toBeLessThanOrEqual(10);
});
it('normalizes line breaks', () => {
const input = 'line1\r\nline2\rline3';
const result = sanitizeUserInput(input, { allowLineBreaks: true });
expect(result).not.toContain('\r');
});
it('escapes HTML by default (allowHtml=false)', () => {
const result = sanitizeUserInput('');
expect(result).toContain('<');
expect(result).not.toContain('', '
',
'">