UNPKG

a11yanalyze

Version:

A command-line tool for developers and QA engineers to test web pages and websites for WCAG 2.2 AA accessibility compliance

530 lines (411 loc) 18.1 kB
/** * URL Validator unit tests * Tests comprehensive URL validation, error handling, and auto-correction features */ import { UrlValidator, UrlErrorFormatter, UrlValidationOptions } from './url-validator'; describe('UrlValidator', () => { let validator: UrlValidator; beforeEach(() => { validator = new UrlValidator(); }); describe('Basic URL Validation', () => { it('should validate simple HTTP URLs', async () => { const result = await validator.validate('http://example.com'); expect(result.isValid).toBe(true); expect(result.normalizedUrl).toBe('http://example.com'); }); it('should validate simple HTTPS URLs', async () => { const result = await validator.validate('https://example.com'); expect(result.isValid).toBe(true); expect(result.normalizedUrl).toBe('https://example.com'); }); it('should auto-add HTTPS protocol', async () => { const result = await validator.validate('example.com'); expect(result.isValid).toBe(true); expect(result.normalizedUrl).toBe('https://example.com'); }); it('should auto-add HTTP for localhost', async () => { const result = await validator.validate('localhost:3000'); expect(result.isValid).toBe(true); expect(result.normalizedUrl).toBe('http://localhost:3000'); }); it('should validate complex URLs', async () => { const result = await validator.validate('https://sub.example.com:8080/path/to/page?param=value#section'); expect(result.isValid).toBe(true); expect(result.normalizedUrl).toBe('https://sub.example.com:8080/path/to/page?param=value#section'); }); }); describe('Input Normalization', () => { it('should fix common typos', async () => { const typos = [ 'htp://example.com', 'htps://example.com', 'http//example.com', 'https//example.com', ]; for (const typo of typos) { const result = await validator.validate(typo); expect(result.isValid).toBe(true); } }); it('should handle whitespace', async () => { const result = await validator.validate(' https://example.com '); expect(result.isValid).toBe(true); expect(result.normalizedUrl).toBe('https://example.com'); }); it('should add www suggestions', async () => { const result = await validator.validate('example.com'); expect(result.isValid).toBe(true); // Should normalize to https://example.com without www expect(result.normalizedUrl).toBe('https://example.com'); }); }); describe('Protocol Validation', () => { it('should reject unsupported protocols with strict validator', async () => { const strictValidator = UrlValidator.strict(); const result = await strictValidator.validate('http://example.com'); expect(result.isValid).toBe(false); expect(result.error?.type).toBe('protocol'); expect(result.error?.message).toContain('not allowed'); }); it('should accept HTTP and HTTPS by default', async () => { const httpResult = await validator.validate('http://example.com'); const httpsResult = await validator.validate('https://example.com'); expect(httpResult.isValid).toBe(true); expect(httpsResult.isValid).toBe(true); }); it('should accept additional protocols with lenient validator', async () => { const lenientValidator = UrlValidator.lenient(); const result = await lenientValidator.validate('ftp://example.com'); expect(result.isValid).toBe(true); }); }); describe('Domain Validation', () => { it('should validate localhost', async () => { const result = await validator.validate('http://localhost'); expect(result.isValid).toBe(true); expect(result.warnings).toContain('Using localhost - this URL will only work on your local machine'); }); it('should reject localhost with strict validator', async () => { const strictValidator = UrlValidator.strict(); const result = await strictValidator.validate('http://localhost'); expect(result.isValid).toBe(false); expect(result.error?.type).toBe('domain'); }); it('should validate IP addresses', async () => { const result = await validator.validate('http://192.168.1.1'); expect(result.isValid).toBe(true); expect(result.warnings).toContain('Using IP address - consider using a domain name for better accessibility'); }); it('should validate IPv6 addresses', async () => { const result = await validator.validate('http://[::1]'); expect(result.isValid).toBe(true); }); it('should reject invalid IP addresses', async () => { const result = await validator.validate('http://999.999.999.999'); expect(result.isValid).toBe(false); }); it('should reject domains without TLD', async () => { const result = await validator.validate('http://example'); expect(result.isValid).toBe(false); expect(result.error?.type).toBe('domain'); expect(result.error?.message).toContain('missing TLD'); }); it('should warn about suspicious domains', async () => { const result = await validator.validate('http://bit.ly/test'); expect(result.isValid).toBe(true); expect(result.warnings).toContain('may be a URL shortener'); }); }); describe('Port Validation', () => { it('should allow standard ports', async () => { const httpResult = await validator.validate('http://example.com:80'); const httpsResult = await validator.validate('https://example.com:443'); expect(httpResult.isValid).toBe(true); expect(httpsResult.isValid).toBe(true); }); it('should allow non-standard ports by default', async () => { const result = await validator.validate('http://example.com:8080'); expect(result.isValid).toBe(true); }); it('should reject non-standard ports with strict validator', async () => { const strictValidator = UrlValidator.strict(); const result = await strictValidator.validate('http://example.com:8080'); expect(result.isValid).toBe(false); expect(result.error?.type).toBe('domain'); expect(result.error?.message).toContain('Non-standard port'); }); }); describe('Security Validation', () => { it('should warn about HTTP for non-localhost', async () => { const result = await validator.validate('http://example.com'); expect(result.isValid).toBe(true); expect(result.warnings).toContain('Consider using HTTPS'); }); it('should reject path traversal patterns', async () => { const result = await validator.validate('https://example.com/../../../etc/passwd'); expect(result.isValid).toBe(false); expect(result.error?.type).toBe('security'); expect(result.error?.message).toContain('path traversal'); }); }); describe('Accessibility Validation', () => { it('should warn about tracking parameters', async () => { const trackingUrls = [ 'https://example.com?utm_source=test', 'https://example.com?fbclid=123', 'https://example.com?gclid=456', ]; for (const url of trackingUrls) { const result = await validator.validate(url); expect(result.isValid).toBe(true); expect(result.warnings).toContain('tracking parameters'); } }); it('should warn about text fragments', async () => { const result = await validator.validate('https://example.com#:~:text=hello'); expect(result.isValid).toBe(true); expect(result.warnings).toContain('text fragments'); }); it('should warn about very long URLs', async () => { const longPath = 'a'.repeat(1000); const result = await validator.validate(`https://example.com/${longPath}`); expect(result.isValid).toBe(true); expect(result.warnings).toContain('Very long URLs'); }); }); describe('Format Validation', () => { it('should reject empty strings', async () => { const result = await validator.validate(''); expect(result.isValid).toBe(false); expect(result.error?.type).toBe('format'); }); it('should reject very long URLs', async () => { const longUrl = 'https://example.com/' + 'a'.repeat(3000); const result = await validator.validate(longUrl); expect(result.isValid).toBe(false); expect(result.error?.type).toBe('format'); expect(result.error?.message).toContain('too long'); }); it('should reject URLs without protocol', async () => { const validator = new UrlValidator({ autoNormalize: false }); const result = await validator.validate('example.com'); expect(result.isValid).toBe(false); expect(result.error?.type).toBe('format'); expect(result.error?.message).toContain('valid protocol'); }); }); describe('Auto-correction Suggestions', () => { it('should suggest protocol additions', async () => { const validator = new UrlValidator({ autoNormalize: false }); const result = await validator.validate('example.com'); expect(result.isValid).toBe(false); expect(result.suggestions).toContain('https://example.com'); expect(result.suggestions).toContain('http://example.com'); }); it('should suggest typo corrections', async () => { const result = await validator.validate('htp://example.com'); // This should auto-correct and be valid expect(result.isValid).toBe(true); }); it('should suggest www additions', async () => { const validator = new UrlValidator({ autoNormalize: false }); const result = await validator.validate('example.com'); expect(result.suggestions).toContain('https://www.example.com'); }); }); describe('Multiple URL Validation', () => { it('should validate multiple URLs', async () => { const urls = [ 'https://example.com', 'http://localhost:3000', 'invalid-url', ]; const results = await validator.validateMultiple(urls); expect(results).toHaveLength(3); expect(results[0]?.isValid).toBe(true); expect(results[1]?.isValid).toBe(true); expect(results[2]?.isValid).toBe(false); }); it('should handle empty array', async () => { const results = await validator.validateMultiple([]); expect(results).toHaveLength(0); }); it('should handle validation errors gracefully', async () => { const results = await validator.validateMultiple(['', 'https://example.com']); expect(results).toHaveLength(2); expect(results[0]?.isValid).toBe(false); expect(results[1]?.isValid).toBe(true); }); }); describe('Quick Validation', () => { it('should quickly validate valid URLs', () => { expect(validator.isValidUrl('https://example.com')).toBe(true); expect(validator.isValidUrl('http://localhost')).toBe(true); }); it('should quickly reject invalid URLs', () => { expect(validator.isValidUrl('invalid')).toBe(false); expect(validator.isValidUrl('')).toBe(false); expect(validator.isValidUrl('ftp://example.com')).toBe(false); // Not in default allowed protocols }); }); describe('Preset Configurations', () => { it('should create strict validator', async () => { const strictValidator = UrlValidator.strict(); // Should reject HTTP const httpResult = await strictValidator.validate('http://example.com'); expect(httpResult.isValid).toBe(false); // Should reject localhost const localhostResult = await strictValidator.validate('https://localhost'); expect(localhostResult.isValid).toBe(false); }); it('should create lenient validator', async () => { const lenientValidator = UrlValidator.lenient(); // Should accept FTP const ftpResult = await lenientValidator.validate('ftp://example.com'); expect(ftpResult.isValid).toBe(true); }); it('should create development validator', async () => { const devValidator = UrlValidator.development(); // Should accept localhost const localhostResult = await devValidator.validate('http://localhost:3000'); expect(localhostResult.isValid).toBe(true); }); }); describe('Error Handling', () => { it('should handle malformed input gracefully', async () => { const result = await validator.validate('not a url at all'); expect(result.isValid).toBe(false); expect(result.error).toBeDefined(); expect(result.suggestions).toBeDefined(); }); it('should handle special characters', async () => { const result = await validator.validate('https://example.com/path with spaces'); // Should handle or reject gracefully expect(result).toBeDefined(); expect(typeof result.isValid).toBe('boolean'); }); it('should handle null and undefined', async () => { const nullResult = await validator.validate(null as any); const undefinedResult = await validator.validate(undefined as any); expect(nullResult.isValid).toBe(false); expect(undefinedResult.isValid).toBe(false); }); }); describe('Custom Configuration', () => { it('should respect custom allowed protocols', async () => { const customValidator = new UrlValidator({ allowedProtocols: ['https', 'ftp'], }); const httpsResult = await customValidator.validate('https://example.com'); const ftpResult = await customValidator.validate('ftp://example.com'); const httpResult = await customValidator.validate('http://example.com'); expect(httpsResult.isValid).toBe(true); expect(ftpResult.isValid).toBe(true); expect(httpResult.isValid).toBe(false); }); it('should respect custom max length', async () => { const customValidator = new UrlValidator({ maxLength: 50, }); const shortResult = await customValidator.validate('https://example.com'); const longResult = await customValidator.validate('https://example.com/very/long/path/that/exceeds/fifty/characters'); expect(shortResult.isValid).toBe(true); expect(longResult.isValid).toBe(false); }); it('should disable auto-normalization when configured', async () => { const customValidator = new UrlValidator({ autoNormalize: false, }); const result = await customValidator.validate('example.com'); expect(result.isValid).toBe(false); expect(result.suggestions).toContain('https://example.com'); }); }); }); describe('UrlErrorFormatter', () => { describe('Error Formatting', () => { it('should format validation errors', () => { const result = { isValid: false, error: { type: 'format' as const, message: 'Invalid URL format', details: 'Missing protocol', input: 'example.com', fixes: ['Add https://', 'Add http://'], }, suggestions: ['https://example.com', 'http://example.com'], }; const formatted = UrlErrorFormatter.formatError(result); expect(formatted).toContain('❌ Invalid URL format'); expect(formatted).toContain('Details: Missing protocol'); expect(formatted).toContain('💡 Suggestions:'); expect(formatted).toContain('Add https://'); expect(formatted).toContain('🔧 Try these URLs:'); expect(formatted).toContain('https://example.com'); }); it('should return empty string for valid results', () => { const result = { isValid: true, normalizedUrl: 'https://example.com', }; const formatted = UrlErrorFormatter.formatError(result); expect(formatted).toBe(''); }); }); describe('Warning Formatting', () => { it('should format warnings', () => { const result = { isValid: true, normalizedUrl: 'http://example.com', warnings: ['Consider using HTTPS', 'Domain might be suspicious'], }; const formatted = UrlErrorFormatter.formatWarnings(result); expect(formatted).toContain('⚠️ Warnings:'); expect(formatted).toContain('Consider using HTTPS'); expect(formatted).toContain('Domain might be suspicious'); }); it('should return empty string when no warnings', () => { const result = { isValid: true, normalizedUrl: 'https://example.com', }; const formatted = UrlErrorFormatter.formatWarnings(result); expect(formatted).toBe(''); }); }); describe('Success Formatting', () => { it('should format successful validation', () => { const result = { isValid: true, normalizedUrl: 'https://example.com', }; const formatted = UrlErrorFormatter.formatSuccess(result); expect(formatted).toContain('✅ URL is valid: https://example.com'); }); it('should include warnings in success message', () => { const result = { isValid: true, normalizedUrl: 'http://example.com', warnings: ['Consider using HTTPS'], }; const formatted = UrlErrorFormatter.formatSuccess(result); expect(formatted).toContain('✅ URL is valid'); expect(formatted).toContain('⚠️ Warnings:'); expect(formatted).toContain('Consider using HTTPS'); }); it('should return empty string for invalid results', () => { const result = { isValid: false, error: { type: 'format' as const, message: 'Invalid', input: 'test', }, }; const formatted = UrlErrorFormatter.formatSuccess(result); expect(formatted).toBe(''); }); }); });