import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; describe('Service Worker Registration Tests', () => { let originalNavigator; let mockServiceWorkerContainer; let mockRegistration; beforeEach(() => { // Save original navigator originalNavigator = global.navigator; // Create mock service worker registration mockRegistration = { installing: null, waiting: null, active: { state: 'activated', postMessage: vi.fn(), }, scope: '/', update: vi.fn(), unregister: vi.fn(), addEventListener: vi.fn(), removeEventListener: vi.fn(), }; // Create mock service worker container mockServiceWorkerContainer = { register: vi.fn().mockResolvedValue(mockRegistration), getRegistration: vi.fn().mockResolvedValue(mockRegistration), getRegistrations: vi.fn().mockResolvedValue([mockRegistration]), ready: Promise.resolve(mockRegistration), controller: null, addEventListener: vi.fn(), removeEventListener: vi.fn(), }; // Mock navigator.serviceWorker Object.defineProperty(global.navigator, 'serviceWorker', { value: mockServiceWorkerContainer, configurable: true, writable: true, }); }); afterEach(() => { global.navigator = originalNavigator; }); describe('Service Worker Support Detection', () => { it('should detect service worker support in browser', () => { expect('serviceWorker' in navigator).toBe(true); }); it('should not have service worker in browsers that do not support it', () => { // Remove serviceWorker const navigatorWithoutSW = { ...global.navigator }; delete navigatorWithoutSW.serviceWorker; global.navigator = navigatorWithoutSW; expect('serviceWorker' in navigator).toBe(false); }); }); describe('Service Worker Registration', () => { it('should register service worker with correct scope', async () => { if ('serviceWorker' in navigator) { await navigator.serviceWorker.register('/sw.js', { scope: '/' }); expect(mockServiceWorkerContainer.register).toHaveBeenCalledWith( '/sw.js', expect.objectContaining({ scope: '/' }) ); } }); it('should return registration object on successful registration', async () => { if ('serviceWorker' in navigator) { const registration = await navigator.serviceWorker.register('/sw.js'); expect(registration).toBeDefined(); expect(registration.scope).toBe('/'); expect(registration.active).toBeDefined(); } }); it('should handle registration failure gracefully', async () => { // Mock registration failure mockServiceWorkerContainer.register = vi.fn().mockRejectedValue( new Error('Registration failed') ); if ('serviceWorker' in navigator) { await expect( navigator.serviceWorker.register('/sw.js') ).rejects.toThrow('Registration failed'); } }); it('should get existing service worker registration', async () => { if ('serviceWorker' in navigator) { const registration = await navigator.serviceWorker.getRegistration('/'); expect(registration).toBeDefined(); expect(mockServiceWorkerContainer.getRegistration).toHaveBeenCalledWith('/'); } }); it('should get all service worker registrations', async () => { if ('serviceWorker' in navigator) { const registrations = await navigator.serviceWorker.getRegistrations(); expect(Array.isArray(registrations)).toBe(true); expect(registrations.length).toBeGreaterThan(0); } }); }); describe('Service Worker Lifecycle', () => { it('should have active service worker after registration', async () => { if ('serviceWorker' in navigator) { const registration = await navigator.serviceWorker.register('/sw.js'); expect(registration.active).toBeDefined(); expect(registration.active.state).toBe('activated'); } }); it('should update service worker', async () => { if ('serviceWorker' in navigator) { const registration = await navigator.serviceWorker.register('/sw.js'); await registration.update(); expect(mockRegistration.update).toHaveBeenCalled(); } }); it('should unregister service worker', async () => { if ('serviceWorker' in navigator) { const registration = await navigator.serviceWorker.register('/sw.js'); await registration.unregister(); expect(mockRegistration.unregister).toHaveBeenCalled(); } }); it('should wait for service worker to be ready', async () => { if ('serviceWorker' in navigator) { const registration = await navigator.serviceWorker.ready; expect(registration).toBeDefined(); expect(registration.active).toBeDefined(); } }); }); describe('Service Worker Communication', () => { it('should post message to service worker', async () => { if ('serviceWorker' in navigator) { const registration = await navigator.serviceWorker.register('/sw.js'); const message = { type: 'SKIP_WAITING' }; registration.active.postMessage(message); expect(mockRegistration.active.postMessage).toHaveBeenCalledWith(message); } }); it('should listen for service worker messages', () => { if ('serviceWorker' in navigator) { const messageHandler = vi.fn(); navigator.serviceWorker.addEventListener('message', messageHandler); expect(mockServiceWorkerContainer.addEventListener).toHaveBeenCalledWith( 'message', messageHandler ); } }); it('should remove message event listener', () => { if ('serviceWorker' in navigator) { const messageHandler = vi.fn(); navigator.serviceWorker.addEventListener('message', messageHandler); navigator.serviceWorker.removeEventListener('message', messageHandler); expect(mockServiceWorkerContainer.removeEventListener).toHaveBeenCalledWith( 'message', messageHandler ); } }); }); describe('Workbox Integration', () => { it('should have workbox available in service worker scope', () => { // Mock workbox global object const mockWorkbox = { core: { clientsClaim: vi.fn(), skipWaiting: vi.fn(), }, precaching: { precacheAndRoute: vi.fn(), cleanupOutdatedCaches: vi.fn(), }, routing: { registerRoute: vi.fn(), }, strategies: { CacheFirst: vi.fn(), NetworkFirst: vi.fn(), StaleWhileRevalidate: vi.fn(), }, }; global.workbox = mockWorkbox; expect(global.workbox).toBeDefined(); expect(global.workbox.core).toBeDefined(); expect(global.workbox.precaching).toBeDefined(); expect(global.workbox.routing).toBeDefined(); expect(global.workbox.strategies).toBeDefined(); // Cleanup delete global.workbox; }); it('should call precacheAndRoute in service worker', () => { const mockWorkbox = { precaching: { precacheAndRoute: vi.fn(), }, }; global.workbox = mockWorkbox; // Simulate precaching const manifest = [ { url: '/index.html', revision: 'abc123' }, { url: '/assets/main.js', revision: 'def456' }, ]; global.workbox.precaching.precacheAndRoute(manifest); expect(mockWorkbox.precaching.precacheAndRoute).toHaveBeenCalledWith(manifest); // Cleanup delete global.workbox; }); it('should cleanup outdated caches', () => { const mockWorkbox = { precaching: { cleanupOutdatedCaches: vi.fn(), }, }; global.workbox = mockWorkbox; global.workbox.precaching.cleanupOutdatedCaches(); expect(mockWorkbox.precaching.cleanupOutdatedCaches).toHaveBeenCalled(); // Cleanup delete global.workbox; }); }); describe('Cache Storage API', () => { let mockCache; let mockCacheStorage; beforeEach(() => { mockCache = { match: vi.fn(), matchAll: vi.fn(), add: vi.fn(), addAll: vi.fn(), put: vi.fn(), delete: vi.fn(), keys: vi.fn(), }; mockCacheStorage = { open: vi.fn().mockResolvedValue(mockCache), has: vi.fn().mockResolvedValue(true), delete: vi.fn().mockResolvedValue(true), keys: vi.fn().mockResolvedValue(['v1-static', 'v1-images']), match: vi.fn(), }; global.caches = mockCacheStorage; }); it('should open cache storage', async () => { const cache = await caches.open('v1-static'); expect(cache).toBeDefined(); expect(mockCacheStorage.open).toHaveBeenCalledWith('v1-static'); }); it('should check if cache exists', async () => { const exists = await caches.has('v1-static'); expect(exists).toBe(true); expect(mockCacheStorage.has).toHaveBeenCalledWith('v1-static'); }); it('should delete cache', async () => { const deleted = await caches.delete('old-cache'); expect(deleted).toBe(true); expect(mockCacheStorage.delete).toHaveBeenCalledWith('old-cache'); }); it('should list all caches', async () => { const cacheNames = await caches.keys(); expect(Array.isArray(cacheNames)).toBe(true); expect(cacheNames).toContain('v1-static'); expect(cacheNames).toContain('v1-images'); }); it('should add file to cache', async () => { const cache = await caches.open('v1-static'); await cache.add('/index.html'); expect(mockCache.add).toHaveBeenCalledWith('/index.html'); }); it('should add multiple files to cache', async () => { const cache = await caches.open('v1-static'); const urls = ['/index.html', '/main.js', '/style.css']; await cache.addAll(urls); expect(mockCache.addAll).toHaveBeenCalledWith(urls); }); it('should match cached request', async () => { mockCache.match = vi.fn().mockResolvedValue( new Response('Cached content', { status: 200 }) ); const cache = await caches.open('v1-static'); const response = await cache.match('/index.html'); expect(response).toBeDefined(); expect(response.status).toBe(200); expect(mockCache.match).toHaveBeenCalledWith('/index.html'); }); }); });