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