@sc4rfurryx/proteusjs
Version:
The Modern Web Development Framework for Accessible, Responsive, and High-Performance Applications. Intelligent container queries, fluid typography, WCAG compliance, and performance optimization.
329 lines (246 loc) • 11.3 kB
text/typescript
/**
* BrowserPolyfills Test Suite
* Tests for cross-browser compatibility polyfills
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { BrowserPolyfills } from '../BrowserPolyfills';
describe('BrowserPolyfills', () => {
let polyfills: BrowserPolyfills;
let originalResizeObserver: any;
let originalIntersectionObserver: any;
let originalPerformance: any;
let originalCSS: any;
beforeEach(() => {
// Store original implementations
originalResizeObserver = (global as any).ResizeObserver;
originalIntersectionObserver = (global as any).IntersectionObserver;
originalPerformance = global.performance;
originalCSS = (global as any).CSS;
polyfills = BrowserPolyfills.getInstance();
});
afterEach(() => {
// Restore original implementations
(global as any).ResizeObserver = originalResizeObserver;
(global as any).IntersectionObserver = originalIntersectionObserver;
global.performance = originalPerformance;
(global as any).CSS = originalCSS;
});
describe('Singleton Pattern', () => {
it('should return the same instance', () => {
const instance1 = BrowserPolyfills.getInstance();
const instance2 = BrowserPolyfills.getInstance();
expect(instance1).toBe(instance2);
});
});
describe('Browser Support Detection', () => {
it('should detect supported features', () => {
const support = polyfills.checkBrowserSupport();
expect(support).toHaveProperty('supported');
expect(support).toHaveProperty('missing');
expect(support).toHaveProperty('warnings');
expect(Array.isArray(support.supported)).toBe(true);
expect(Array.isArray(support.missing)).toBe(true);
expect(Array.isArray(support.warnings)).toBe(true);
});
it('should detect missing ResizeObserver', () => {
delete (global as any).ResizeObserver;
const support = polyfills.checkBrowserSupport();
expect(support.missing).toContain('ResizeObserver');
});
it('should detect missing IntersectionObserver', () => {
delete (global as any).IntersectionObserver;
const support = polyfills.checkBrowserSupport();
expect(support.missing).toContain('IntersectionObserver');
});
it('should detect browser-specific warnings', () => {
// Mock IE user agent
Object.defineProperty(navigator, 'userAgent', {
value: 'Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko',
configurable: true
});
const support = polyfills.checkBrowserSupport();
expect(support.warnings.some(w => w.includes('Internet Explorer'))).toBe(true);
});
});
describe('ResizeObserver Polyfill', () => {
beforeEach(() => {
delete (global as any).ResizeObserver;
});
it('should load ResizeObserver polyfill when missing', async () => {
await polyfills.loadPolyfills({ resizeObserver: true });
expect(typeof (global as any).ResizeObserver).toBe('function');
expect(polyfills.getLoadedPolyfills()).toContain('ResizeObserver');
});
it('should create functional ResizeObserver polyfill', async () => {
await polyfills.loadPolyfills({ resizeObserver: true });
const callback = vi.fn();
const observer = new (global as any).ResizeObserver(callback);
expect(observer).toBeDefined();
expect(typeof observer.observe).toBe('function');
expect(typeof observer.unobserve).toBe('function');
expect(typeof observer.disconnect).toBe('function');
});
it('should not load polyfill when ResizeObserver exists', async () => {
(global as any).ResizeObserver = vi.fn();
await polyfills.loadPolyfills({ resizeObserver: true });
expect(polyfills.getLoadedPolyfills()).not.toContain('ResizeObserver');
});
});
describe('IntersectionObserver Polyfill', () => {
beforeEach(() => {
delete (global as any).IntersectionObserver;
});
it('should load IntersectionObserver polyfill when missing', async () => {
await polyfills.loadPolyfills({ intersectionObserver: true });
expect(typeof (global as any).IntersectionObserver).toBe('function');
expect(polyfills.getLoadedPolyfills()).toContain('IntersectionObserver');
});
it('should create functional IntersectionObserver polyfill', async () => {
await polyfills.loadPolyfills({ intersectionObserver: true });
const callback = vi.fn();
const observer = new (global as any).IntersectionObserver(callback);
expect(observer).toBeDefined();
expect(typeof observer.observe).toBe('function');
expect(typeof observer.unobserve).toBe('function');
expect(typeof observer.disconnect).toBe('function');
});
});
describe('Performance API Polyfill', () => {
it('should load performance polyfill when missing', async () => {
delete (global as any).performance;
await polyfills.loadPolyfills({ performance: true });
expect(typeof performance).toBe('object');
expect(typeof performance.now).toBe('function');
expect(polyfills.getLoadedPolyfills()).toContain('Performance');
});
it('should provide working performance.now', async () => {
delete (global as any).performance;
await polyfills.loadPolyfills({ performance: true });
const time1 = performance.now();
await new Promise(resolve => setTimeout(resolve, 10));
const time2 = performance.now();
expect(time2).toBeGreaterThan(time1);
});
});
describe('RequestAnimationFrame Polyfill', () => {
it('should load RAF polyfill when missing', async () => {
delete (global as any).requestAnimationFrame;
delete (global as any).cancelAnimationFrame;
await polyfills.loadPolyfills({ requestAnimationFrame: true });
expect(typeof requestAnimationFrame).toBe('function');
expect(typeof cancelAnimationFrame).toBe('function');
expect(polyfills.getLoadedPolyfills()).toContain('RequestAnimationFrame');
});
it('should provide working requestAnimationFrame', async () => {
delete (global as any).requestAnimationFrame;
await polyfills.loadPolyfills({ requestAnimationFrame: true });
const callback = vi.fn();
const id = requestAnimationFrame(callback);
expect(typeof id).toBe('number');
// Wait for callback
await new Promise(resolve => setTimeout(resolve, 20));
expect(callback).toHaveBeenCalled();
});
});
describe('CSS.supports Polyfill', () => {
it('should load CSS.supports polyfill when missing', async () => {
delete (global as any).CSS;
await polyfills.loadPolyfills({ cssSupports: true });
expect(typeof CSS).toBe('object');
expect(typeof CSS.supports).toBe('function');
expect(polyfills.getLoadedPolyfills()).toContain('CSS.supports');
});
it('should provide working CSS.supports', async () => {
delete (global as any).CSS;
await polyfills.loadPolyfills({ cssSupports: true });
// Test basic property support
const supportsColor = CSS.supports('color', 'red');
const supportsInvalid = CSS.supports('invalid-property', 'invalid-value');
expect(typeof supportsColor).toBe('boolean');
expect(typeof supportsInvalid).toBe('boolean');
});
});
describe('Element.classList Polyfill', () => {
it('should load classList polyfill when missing', async () => {
// Mock missing classList
const originalDescriptor = Object.getOwnPropertyDescriptor(Element.prototype, 'classList');
delete (Element.prototype as any).classList;
await polyfills.loadPolyfills({ classList: true });
const element = document.createElement('div');
expect(element.classList).toBeDefined();
expect(typeof element.classList.add).toBe('function');
expect(polyfills.getLoadedPolyfills()).toContain('Element.classList');
// Restore original
if (originalDescriptor) {
Object.defineProperty(Element.prototype, 'classList', originalDescriptor);
}
});
});
describe('Element.closest Polyfill', () => {
it('should load closest polyfill when missing', async () => {
const originalClosest = Element.prototype.closest;
delete (Element.prototype as any).closest;
await polyfills.loadPolyfills({ closest: true });
const element = document.createElement('div');
expect(typeof element.closest).toBe('function');
expect(polyfills.getLoadedPolyfills()).toContain('Element.closest');
// Restore original
Element.prototype.closest = originalClosest;
});
});
describe('Auto Initialization', () => {
it('should auto-initialize based on browser support', async () => {
// Remove some features to trigger polyfills
delete (global as any).ResizeObserver;
delete (global as any).IntersectionObserver;
await BrowserPolyfills.autoInit();
expect(typeof (global as any).ResizeObserver).toBe('function');
expect(typeof (global as any).IntersectionObserver).toBe('function');
});
it('should not load unnecessary polyfills', async () => {
// Ensure features exist
(global as any).ResizeObserver = vi.fn();
(global as any).IntersectionObserver = vi.fn();
const polyfillsBefore = polyfills.getLoadedPolyfills().length;
await BrowserPolyfills.autoInit();
const polyfillsAfter = polyfills.getLoadedPolyfills().length;
// Should not have loaded additional polyfills
expect(polyfillsAfter).toBe(polyfillsBefore);
});
});
describe('Configuration', () => {
it('should respect polyfill configuration', async () => {
delete (global as any).ResizeObserver;
delete (global as any).IntersectionObserver;
await polyfills.loadPolyfills({
resizeObserver: true,
intersectionObserver: false
});
expect(typeof (global as any).ResizeObserver).toBe('function');
expect(typeof (global as any).IntersectionObserver).toBe('undefined');
});
it('should use default configuration when none provided', async () => {
delete (global as any).ResizeObserver;
await polyfills.loadPolyfills();
expect(typeof (global as any).ResizeObserver).toBe('function');
});
});
describe('Error Handling', () => {
it('should handle polyfill loading errors gracefully', async () => {
// This test ensures no errors are thrown during polyfill loading
expect(async () => {
await polyfills.loadPolyfills();
}).not.toThrow();
});
it('should handle missing DOM gracefully', async () => {
// Mock missing document
const originalDocument = global.document;
delete (global as any).document;
expect(async () => {
await polyfills.loadPolyfills();
}).not.toThrow();
// Restore document
global.document = originalDocument;
});
});
});