UNPKG

maplibre-gl

Version:

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

171 lines (144 loc) 7.87 kB
import {describe, beforeEach, it, afterEach, expect, vi, type MockInstance} from 'vitest'; import {type FakeServer, fakeServer} from 'nise'; import {rtlMainThreadPluginFactory} from './rtl_text_plugin_main_thread'; import {sleep} from '../util/test/util'; import {browser} from '../util/browser'; import {Dispatcher} from '../util/dispatcher'; import {type PluginState} from './rtl_text_plugin_status'; import {MessageType} from '../util/actor_messages'; const rtlMainThreadPlugin = rtlMainThreadPluginFactory(); describe('RTLMainThreadPlugin', () => { let server: FakeServer; let broadcastSpy: MockInstance; const url = 'http://example.com/plugin'; const failedToLoadMessage = `RTL Text Plugin failed to import scripts from ${url}`; const SyncRTLPluginStateMessageName = MessageType.syncRTLPluginState; beforeEach(() => { server = fakeServer.create(); global.fetch = null; // Reset the singleton instance before each test rtlMainThreadPlugin.clearRTLTextPlugin(); broadcastSpy = vi.spyOn(Dispatcher.prototype, 'broadcast').mockImplementation(() => { return Promise.resolve({} as any); }); }); function broadcastMockSuccess(message: MessageType, payload: PluginState): Promise<PluginState[]> { if (message === SyncRTLPluginStateMessageName) { if (payload.pluginStatus === 'loading') { const resultState: PluginState = { pluginStatus: 'loaded', pluginURL: payload.pluginURL }; return Promise.resolve([resultState]); } } } function broadcastMockSuccessDefer(message: MessageType, payload: PluginState): Promise<PluginState[]> { if (message === SyncRTLPluginStateMessageName) { if (payload.pluginStatus === 'deferred') { const resultState: PluginState = { pluginStatus: 'deferred', pluginURL: payload.pluginURL }; return Promise.resolve([resultState]); } } } function broadcastMockFailure(message: MessageType, payload: PluginState): Promise<PluginState[]> { if (message === SyncRTLPluginStateMessageName) { if (payload.pluginStatus === 'loading') { return Promise.reject(failedToLoadMessage); } } else { return Promise.resolve([]); } } afterEach(() => { server.restore(); broadcastSpy.mockRestore(); }); it('should get the RTL text plugin status', () => { const status = rtlMainThreadPlugin.getRTLTextPluginStatus(); expect(status).toBe('unavailable'); }); it('should set the RTL text plugin and download it', async () => { broadcastSpy = vi.spyOn(Dispatcher.prototype, 'broadcast').mockImplementation(broadcastMockSuccess as any); await rtlMainThreadPlugin.setRTLTextPlugin(url); expect(rtlMainThreadPlugin.url).toEqual(url); expect(rtlMainThreadPlugin.status).toBe('loaded'); }); it('should set the RTL text plugin but deffer downloading', async () => { await rtlMainThreadPlugin.setRTLTextPlugin(url, true); expect(rtlMainThreadPlugin.status).toBe('deferred'); expect(broadcastSpy).toHaveBeenCalledWith(SyncRTLPluginStateMessageName, {pluginStatus: 'deferred', pluginURL: url}); }); it('should throw if the plugin is already set', async () => { await rtlMainThreadPlugin.setRTLTextPlugin(url, true); await expect(rtlMainThreadPlugin.setRTLTextPlugin(url)).rejects.toThrow('setRTLTextPlugin cannot be called multiple times.'); }); it('should throw if the plugin url is not set', async () => { const spy = vi.spyOn(browser, 'resolveURL').mockImplementation(() => { return ''; }); await expect(rtlMainThreadPlugin.setRTLTextPlugin(null)).rejects.toThrow('requested url null is invalid'); spy.mockRestore(); }); it('should be in error state if download fails', async () => { broadcastSpy = vi.spyOn(Dispatcher.prototype, 'broadcast').mockImplementation(broadcastMockFailure as any); const resultPromise = rtlMainThreadPlugin.setRTLTextPlugin(url); await expect(resultPromise).rejects.toBe(failedToLoadMessage); expect(rtlMainThreadPlugin.url).toEqual(url); expect(rtlMainThreadPlugin.status).toBe('error'); }); it('should lazy load the plugin if deferred', async () => { // use success spy to make sure test case does not throw exception const deferredSpy = vi.spyOn(Dispatcher.prototype, 'broadcast').mockImplementation(broadcastMockSuccessDefer as any); await rtlMainThreadPlugin.setRTLTextPlugin(url, true); expect(deferredSpy).toHaveBeenCalledTimes(1); expect(deferredSpy).toHaveBeenCalledWith(SyncRTLPluginStateMessageName, {pluginStatus: 'deferred', pluginURL: url}); expect(rtlMainThreadPlugin.status).toBe('deferred'); deferredSpy.mockRestore(); // this is really a fire and forget broadcastSpy = vi.spyOn(Dispatcher.prototype, 'broadcast').mockImplementation(broadcastMockSuccess as any); rtlMainThreadPlugin.lazyLoad(); await sleep(1); // 'loading' expect(broadcastSpy).toHaveBeenCalledWith(SyncRTLPluginStateMessageName, {pluginStatus: 'loading', pluginURL: url}); expect(broadcastSpy).toHaveBeenCalledTimes(1); // second call to lazyLoad should not change anything rtlMainThreadPlugin.lazyLoad(); expect(broadcastSpy).toHaveBeenCalledTimes(1); expect(rtlMainThreadPlugin.status).toBe('loaded'); // 3rd call to lazyLoad should not change anything rtlMainThreadPlugin.lazyLoad(); expect(rtlMainThreadPlugin.status).toBe('loaded'); expect(broadcastSpy).toHaveBeenCalledTimes(1); }); it('should set status to requested if RTL plugin was not set', async () => { rtlMainThreadPlugin.lazyLoad(); expect(rtlMainThreadPlugin.status).toBe('requested'); }); it('should immediately download if RTL plugin was already requested, ignoring deferred:true', async () => { broadcastSpy = vi.spyOn(Dispatcher.prototype, 'broadcast').mockImplementation(broadcastMockSuccess as any); rtlMainThreadPlugin.lazyLoad(); expect(rtlMainThreadPlugin.status).toBe('requested'); await sleep(1); // notice even when deferred is true, it should download because already requested await rtlMainThreadPlugin.setRTLTextPlugin(url, true); expect(rtlMainThreadPlugin.status).toBe('loaded'); expect(broadcastSpy).toHaveBeenCalledWith(SyncRTLPluginStateMessageName, {pluginStatus: 'loading', pluginURL: url}); }); it('should allow multiple calls to lazyLoad', async () => { rtlMainThreadPlugin.lazyLoad(); expect(rtlMainThreadPlugin.status).toBe('requested'); rtlMainThreadPlugin.lazyLoad(); expect(rtlMainThreadPlugin.status).toBe('requested'); }); it('should be in error state if lazyLoad fails', async () => { broadcastSpy = vi.spyOn(Dispatcher.prototype, 'broadcast').mockImplementation(broadcastMockSuccessDefer); const resultPromise = rtlMainThreadPlugin.setRTLTextPlugin(url, true); await expect(resultPromise).resolves.toBeUndefined(); expect(rtlMainThreadPlugin.status).toBe('deferred'); // the next one should fail broadcastSpy = vi.spyOn(Dispatcher.prototype, 'broadcast').mockImplementation(broadcastMockFailure as any); await expect(rtlMainThreadPlugin._requestImport()).rejects.toBe(failedToLoadMessage); expect(rtlMainThreadPlugin.url).toEqual(url); expect(rtlMainThreadPlugin.status).toBe('error'); }); });