UNPKG

maplibre-gl

Version:

BSD licensed community fork of mapbox-gl, a WebGL interactive maps library

157 lines (123 loc) 6.32 kB
import {describe, test, expect, beforeEach, vi, afterEach, type Mock} from 'vitest'; import {browser} from './browser'; describe('browser', () => { describe('frame',() => { let originalRAF: typeof window.requestAnimationFrame; let originalCAF: typeof window.cancelAnimationFrame; let rafCallbacks: Array<{id: number; callback: FrameRequestCallback}> = []; let rafIdCounter = 0; /** Mimic scheduling RAFs for later */ function flushAllRAFs() { const pending = [...rafCallbacks]; rafCallbacks = []; pending.forEach(({callback}) => callback(performance.now())); } beforeEach(() => { originalRAF = window.requestAnimationFrame; originalCAF = window.cancelAnimationFrame; rafCallbacks = []; rafIdCounter = 0; vi.spyOn(window, 'requestAnimationFrame').mockImplementation(cb => { rafIdCounter++; const id = rafIdCounter; rafCallbacks.push({id, callback: cb}); return id; }); vi.spyOn(window, 'cancelAnimationFrame').mockImplementation(id => { rafCallbacks = rafCallbacks.filter(entry => entry.id !== id); }); }); afterEach(() => { window.requestAnimationFrame = originalRAF; window.cancelAnimationFrame = originalCAF; vi.restoreAllMocks(); }); test('calls requestAnimationFrame and invokes fn callback with timestamp', () => { const abortController = new AbortController(); const addListenerSpy = vi.spyOn(abortController.signal, 'addEventListener'); const removeListenerSpy = vi.spyOn(abortController.signal, 'removeEventListener'); const fn = vi.fn(); const reject = vi.fn(); browser.frame(abortController, fn, reject); expect(window.requestAnimationFrame).toHaveBeenCalledTimes(1); flushAllRAFs(); expect(fn).toHaveBeenCalledTimes(1); const callArg = fn.mock.calls[0][0]; expect(typeof callArg).toBe('number'); expect(window.cancelAnimationFrame).not.toHaveBeenCalled(); expect(reject).not.toHaveBeenCalled(); // cleanup leftover listeners expect(addListenerSpy).toHaveBeenCalledWith('abort', expect.any(Function), false); expect(removeListenerSpy).toHaveBeenCalledWith('abort', expect.any(Function), false); }); test('when AbortController is aborted before frame fires, calls cancelAnimationFrame and reject', () => { // We override the default mock so that the callback is NOT called immediately // giving us time to abort. (window.requestAnimationFrame as Mock).mockImplementation( () => { // Return ID but do not invoke cb return 42; } ); const abortController = new AbortController(); const addListenerSpy = vi.spyOn(abortController.signal, 'addEventListener'); const removeListenerSpy = vi.spyOn(abortController.signal, 'removeEventListener'); const fn = vi.fn(); const reject = vi.fn(); browser.frame(abortController, fn, reject); abortController.abort(); // Now we expect cancelAnimationFrame to be called with the ID 42 expect(window.cancelAnimationFrame).toHaveBeenCalledTimes(1); expect(window.cancelAnimationFrame).toHaveBeenCalledWith(42); // Expect reject to be called expect(reject).toHaveBeenCalledTimes(1); const errorArg = reject.mock.calls[0][0]; expect(errorArg).toBeInstanceOf(Error); expect(errorArg.message).toMatch(/abort/i); // fn should never have been called because we never triggered the RAF callback expect(fn).not.toHaveBeenCalled(); // cleanup leftover listeners expect(addListenerSpy).toHaveBeenCalledWith('abort', expect.any(Function), false); expect(removeListenerSpy).toHaveBeenCalledWith('abort', expect.any(Function), false); }); test('when AbortController is aborted after frame fires, fn is invoked anyway', () => { const abortController = new AbortController(); const addListenerSpy = vi.spyOn(abortController.signal, 'addEventListener'); const removeListenerSpy = vi.spyOn(abortController.signal, 'removeEventListener'); const fn = vi.fn(); const reject = vi.fn(); browser.frame(abortController, fn, reject); flushAllRAFs(); // The callback should have already been called expect(fn).toHaveBeenCalledTimes(1); // The callback runs immediately in our default mock // so if we abort now, it's too late to cancel the frame abortController.abort(); // Because callback already fired, there's no need to cancel expect(window.cancelAnimationFrame).not.toHaveBeenCalled(); // And reject shouldn't be called either expect(reject).not.toHaveBeenCalled(); // cleanup leftover listeners expect(addListenerSpy).toHaveBeenCalledWith('abort', expect.any(Function), false); expect(removeListenerSpy).toHaveBeenCalledWith('abort', expect.any(Function), false); }); }); describe('frameAsync',()=>{ test('expect RAF to be called and receive RAF id', async () => { const id = await browser.frameAsync(new AbortController()); expect(id).toBeTruthy(); }); test('throw error when abort is called', async () => { const abortController = new AbortController(); const promise = browser.frameAsync(abortController); abortController.abort(); await expect(promise).rejects.toThrow(); }); }); test('now', () => { expect(typeof browser.now()).toBe('number'); }); test('hardwareConcurrency', () => { expect(typeof browser.hardwareConcurrency).toBe('number'); }); });