maplibre-gl
Version:
BSD licensed community fork of mapbox-gl, a WebGL interactive maps library
219 lines (162 loc) • 8.91 kB
text/typescript
import {Actor, ActorTarget} from './actor';
import {WorkerGlobalScopeInterface, workerFactory} from './web_worker';
import {setGlobalWorker} from '../../test/unit/lib/web_worker_mock';
import {sleep} from './test/util';
import {ABORT_ERROR, createAbortError} from './abort_error';
import {MessageType} from './actor_messages';
class MockWorker {
self: any;
actor: Actor;
constructor(self) {
this.self = self;
this.actor = new Actor(self);
}
}
describe('Actor', () => {
let originalWorker;
beforeAll(() => {
originalWorker = global.Worker;
setGlobalWorker(MockWorker);
});
afterAll(() => {
global.Worker = originalWorker;
});
test('forwards responses to correct handler', async () => {
const worker = workerFactory() as any as WorkerGlobalScopeInterface & ActorTarget;
worker.worker.actor.registerMessageHandler(MessageType.getClusterExpansionZoom, async (_mapId, params) => {
await sleep(0);
return params.clusterId;
});
const m1 = new Actor(worker, '1');
const m2 = new Actor(worker, '2');
const p1 = m1.sendAsync({type: MessageType.getClusterExpansionZoom, data: {type: 'geojson', source: '', clusterId: 1729}});
const p2 = m2.sendAsync({type: MessageType.getClusterExpansionZoom, data: {type: 'geojson', source: '', clusterId: 4104}});
await Promise.all([p1, p2]);
await expect(p1).resolves.toBe(1729);
await expect(p2).resolves.toBe(4104);
});
test('cancel a request does not reject or resolve a promise', async () => {
const worker = workerFactory() as any as WorkerGlobalScopeInterface & ActorTarget;
worker.worker.actor.registerMessageHandler(MessageType.getClusterExpansionZoom, async (_mapId, params) => {
await sleep(200);
return params.clusterId;
});
const m1 = new Actor(worker, '1');
let received = false;
const abortController = new AbortController();
const p1 = m1.sendAsync({type: MessageType.getClusterExpansionZoom, data: {type: 'geojson', source: '', clusterId: 1729}}, abortController)
.then(() => { received = true; })
.catch(() => { received = true; });
abortController.abort();
const p2 = new Promise((resolve) => (setTimeout(resolve, 500)));
await Promise.any([p1, p2]);
expect(received).toBeFalsy();
});
test('aborting a request will successfully abort it', async () => {
const worker = workerFactory() as any as WorkerGlobalScopeInterface & ActorTarget;
let gotAbortSignal = false;
worker.worker.actor.registerMessageHandler(MessageType.getClusterExpansionZoom, (_mapId, _params, handlerAbortController) => {
return new Promise((resolve, reject) => {
handlerAbortController.signal.addEventListener('abort', () => {
gotAbortSignal = true;
reject(createAbortError());
});
setTimeout(resolve, 200);
});
});
const m1 = new Actor(worker, '1');
let received = false;
const abortController = new AbortController();
m1.sendAsync({type: MessageType.getClusterExpansionZoom, data: {type: 'geojson', source: '', clusterId: 1729}}, abortController)
.then(() => { received = true; })
.catch(() => { received = true; });
abortController.abort();
await sleep(500);
expect(received).toBeFalsy();
expect(gotAbortSignal).toBeTruthy();
});
test('cancel a request that must be queued will not call the method at all', async () => {
const worker = workerFactory() as any as WorkerGlobalScopeInterface & ActorTarget;
const actor = new Actor(worker, '1');
const spy = jest.fn().mockReturnValue(Promise.resolve({}));
worker.worker.actor.registerMessageHandler(MessageType.getClusterExpansionZoom, spy);
let received = false;
const abortController = new AbortController();
const p1 = actor.sendAsync({type: MessageType.getClusterExpansionZoom, data: {type: 'geojson', source: '', clusterId: 1729}, mustQueue: true}, abortController)
.then(() => { received = true; })
.catch(() => { received = true; });
abortController.abort();
const p2 = new Promise((resolve) => (setTimeout(resolve, 500)));
await Promise.any([p1, p2]);
expect(received).toBeFalsy();
expect(spy).not.toHaveBeenCalled();
});
test('#remove unbinds event listener', done => {
const actor = new Actor({
addEventListener(type, callback, useCapture) {
this._addEventListenerArgs = [type, callback, useCapture];
},
removeEventListener(type, callback, useCapture) {
expect([type, callback, useCapture]).toEqual(this._addEventListenerArgs);
done();
}
} as ActorTarget, null);
actor.remove();
});
test('send a message that is rejected', async () => {
const worker = workerFactory() as any as WorkerGlobalScopeInterface & ActorTarget;
const actor = new Actor(worker, '1');
worker.worker.actor.registerMessageHandler(MessageType.abortTile, () => Promise.reject(createAbortError()));
await expect(async () => actor.sendAsync({type: MessageType.abortTile, data: {} as any})).rejects.toThrow(ABORT_ERROR);
});
test('send a message that must be queued, it should still arrive', async () => {
const worker = workerFactory() as any as WorkerGlobalScopeInterface & ActorTarget;
const actor = new Actor(worker, '1');
worker.worker.actor.registerMessageHandler(MessageType.getClusterExpansionZoom, () => Promise.resolve(42));
const response = await actor.sendAsync({type: MessageType.getClusterExpansionZoom, data: {} as any, mustQueue: true});
expect(response).toBe(42);
});
test('send a message is not registered should throw', async () => {
const worker = workerFactory() as any as WorkerGlobalScopeInterface & ActorTarget;
const actor = new Actor(worker, '1');
worker.worker.actor.registerMessageHandler(MessageType.getClusterExpansionZoom, () => Promise.resolve(42));
await expect(async () => actor.sendAsync({type: MessageType.abortTile, data: {} as any})).rejects.toThrow(/Could not find a registered handler for.*/);
});
test('should not process a message with the wrong map id', async () => {
const worker = workerFactory() as any as WorkerGlobalScopeInterface & ActorTarget;
const actor = new Actor(worker, '1');
worker.worker.actor.mapId = '2';
const spy = jest.fn().mockReturnValue(Promise.resolve({}));
worker.worker.actor.registerMessageHandler(MessageType.getClusterExpansionZoom, spy);
actor.sendAsync({type: MessageType.getClusterExpansionZoom, data: {} as any, targetMapId: '1'});
await sleep(100);
expect(spy).not.toHaveBeenCalled();
});
test('should not process a message with the wrong origin', async () => {
const worker = workerFactory() as any as WorkerGlobalScopeInterface & ActorTarget;
const actor = new Actor(worker, '1');
const spy = jest.fn().mockReturnValue(Promise.resolve({}));
worker.worker.actor.registerMessageHandler(MessageType.getClusterExpansionZoom, spy);
actor.target.postMessage({type: 'getClusterExpansionZoom', data: {} as any, origin: 'https://example.com'});
await sleep(100);
expect(spy).not.toHaveBeenCalled();
});
test('should process a message when origin is "file://"', async () => {
const worker = workerFactory() as any as WorkerGlobalScopeInterface & ActorTarget;
const actor = new Actor(worker, '1');
const spy = jest.fn().mockReturnValue(Promise.resolve({}));
worker.worker.actor.registerMessageHandler(MessageType.getClusterExpansionZoom, spy);
actor.target.postMessage({type: MessageType.getClusterExpansionZoom, data: {} as any, origin: 'file://'});
await sleep(0);
expect(spy).toHaveBeenCalled();
});
test('should process a message when origin is "resource://android"', async () => {
const worker = workerFactory() as any as WorkerGlobalScopeInterface & ActorTarget;
const actor = new Actor(worker, '1');
const spy = jest.fn().mockReturnValue(Promise.resolve({}));
worker.worker.actor.registerMessageHandler(MessageType.getClusterExpansionZoom, spy);
actor.target.postMessage({type: MessageType.getClusterExpansionZoom, data: {} as any, origin: 'resource://android'});
await sleep(0);
expect(spy).toHaveBeenCalled();
});
});