// url-validator.js attaches to window.URLValidator
import '@js/security/url-validator.js';
const { URLValidator } = window;
describe('URLValidator', () => {
describe('isUnsafeScheme', () => {
it('detects javascript: scheme', () => {
expect(URLValidator.isUnsafeScheme('javascript:alert(1)')).toBe(true);
});
it('detects data: scheme', () => {
expect(URLValidator.isUnsafeScheme('data:text/html,
hi
')).toBe(true);
});
it('is case-insensitive', () => {
expect(URLValidator.isUnsafeScheme('JAVASCRIPT:void(0)')).toBe(true);
});
it('returns false for safe schemes', () => {
expect(URLValidator.isUnsafeScheme('https://example.com')).toBe(false);
});
it('returns false for empty/null input', () => {
expect(URLValidator.isUnsafeScheme('')).toBe(false);
expect(URLValidator.isUnsafeScheme(null)).toBe(false);
});
});
describe('isSafeUrl', () => {
it('accepts https URLs', () => {
expect(URLValidator.isSafeUrl('https://example.com')).toBe(true);
});
it('accepts http URLs', () => {
expect(URLValidator.isSafeUrl('http://example.com')).toBe(true);
});
it('rejects javascript: URLs', () => {
expect(URLValidator.isSafeUrl('javascript:alert(1)')).toBe(false);
});
it('rejects non-string input', () => {
expect(URLValidator.isSafeUrl(null)).toBe(false);
expect(URLValidator.isSafeUrl(123)).toBe(false);
expect(URLValidator.isSafeUrl(undefined)).toBe(false);
});
it('handles fragment-only URLs based on option', () => {
expect(URLValidator.isSafeUrl('#section', { allowFragments: true })).toBe(true);
expect(URLValidator.isSafeUrl('#section', { allowFragments: false })).toBe(false);
});
it('rejects mailto by default', () => {
expect(URLValidator.isSafeUrl('mailto:user@example.com')).toBe(false);
});
it('allows mailto when option is set', () => {
expect(URLValidator.isSafeUrl('mailto:user@example.com', { allowMailto: true })).toBe(true);
});
it('validates trusted domains', () => {
const opts = { trustedDomains: ['example.com'] };
expect(URLValidator.isSafeUrl('https://example.com/page', opts)).toBe(true);
expect(URLValidator.isSafeUrl('https://sub.example.com/page', opts)).toBe(true);
expect(URLValidator.isSafeUrl('https://evil.com/page', opts)).toBe(false);
});
});
describe('sanitizeUrl', () => {
it('returns null for unsafe schemes', () => {
expect(URLValidator.sanitizeUrl('javascript:alert(1)')).toBeNull();
});
it('adds default https scheme when missing', () => {
expect(URLValidator.sanitizeUrl('example.com')).toBe('https://example.com');
});
it('preserves existing scheme', () => {
expect(URLValidator.sanitizeUrl('http://example.com')).toBe('http://example.com');
});
it('returns null for empty input', () => {
expect(URLValidator.sanitizeUrl('')).toBeNull();
expect(URLValidator.sanitizeUrl(null)).toBeNull();
});
});
describe('safeAssign', () => {
it('allows internal paths starting with /', () => {
const el = {};
expect(URLValidator.safeAssign(el, 'href', '/settings')).toBe(true);
expect(el.href).toBe('/settings');
});
it('allows fragment URLs', () => {
const el = {};
expect(URLValidator.safeAssign(el, 'href', '#top')).toBe(true);
expect(el.href).toBe('#top');
});
it('allows blob URLs for downloads', () => {
const el = {};
const blobUrl = 'blob:http://localhost/abc-123';
expect(URLValidator.safeAssign(el, 'href', blobUrl)).toBe(true);
expect(el.href).toBe(blobUrl);
});
it('blocks unsafe external URLs', () => {
const el = {};
expect(URLValidator.safeAssign(el, 'href', 'javascript:alert(1)')).toBe(false);
expect(el.href).toBeUndefined();
});
it('allows safe external URLs', () => {
const el = {};
expect(URLValidator.safeAssign(el, 'href', 'https://example.com')).toBe(true);
expect(el.href).toBe('https://example.com');
});
});
});