Warning, /firebird/packages/firebird-ng/display/src/lib/services/geometry.service.spec.ts is written in an unsupported language. File is not indexed.
0001 /**
0002 * GeometryService and its worker: the worker comes from the application's
0003 * factory (withWorkers), a failed worker rejects what waits on it and is
0004 * replaced on the next load, and an aborted load cancels the worker request.
0005 * The worker is a fake that records requests and answers on demand.
0006 */
0007
0008 import { TestBed } from '@angular/core/testing';
0009 import { provideHttpClient } from '@angular/common/http';
0010 import { ServerConfigService } from '@dexvis/app-features';
0011 import { withWorkers } from '@dexvis/firebird-ng/api';
0012 import type { GeometryLoadRequest, WorkerResponse } from '@dexvis/firebird-ng/workers/geometry';
0013 import { GeometryService } from './geometry.service';
0014
0015 class FakeWorker {
0016 static instances: FakeWorker[] = [];
0017 readonly requests: Array<{ type: string; requestId: string }> = [];
0018 onmessage: ((event: { data: WorkerResponse }) => void) | null = null;
0019 onerror: ((event: { message: string }) => void) | null = null;
0020 terminated = false;
0021
0022 constructor() {
0023 FakeWorker.instances.push(this);
0024 }
0025
0026 postMessage(request: GeometryLoadRequest): void {
0027 this.requests.push(request);
0028 }
0029
0030 terminate(): void {
0031 this.terminated = true;
0032 }
0033 }
0034
0035 /** Lets the rule resolution before each worker request run. */
0036 async function settle(): Promise<void> {
0037 for (let i = 0; i < 5; i++) await new Promise(resolve => setTimeout(resolve, 0));
0038 }
0039
0040 describe('GeometryService worker', () => {
0041 const worker = (index = 0) => FakeWorker.instances[index];
0042
0043 function create(registerWorkers = true): GeometryService {
0044 const startFake = () => new FakeWorker() as unknown as Worker;
0045 TestBed.configureTestingModule({
0046 providers: [
0047 provideHttpClient(),
0048 ...(registerWorkers ? withWorkers({ geometry: startFake, rootFile: startFake }).providers : []),
0049 ],
0050 });
0051 TestBed.inject(ServerConfigService).setUnitTestConfig({});
0052 return TestBed.inject(GeometryService);
0053 }
0054
0055 beforeEach(() => {
0056 FakeWorker.instances = [];
0057 vi.stubGlobal('Worker', FakeWorker);
0058 vi.spyOn(console, 'log').mockImplementation(() => undefined);
0059 vi.spyOn(console, 'error').mockImplementation(() => undefined);
0060 vi.spyOn(console, 'time').mockImplementation(() => undefined);
0061 vi.spyOn(console, 'timeEnd').mockImplementation(() => undefined);
0062 TestBed.resetTestingModule();
0063 });
0064
0065 afterEach(() => {
0066 vi.unstubAllGlobals();
0067 vi.restoreAllMocks();
0068 });
0069
0070 it('names the missing feature when the application registered no workers', async () => {
0071 await expect(create(false).loadGeometry('https://h/detector.root')).rejects.toThrow('add withWorkers');
0072 });
0073
0074 it('rejects the waiting load when the worker fails, and starts a fresh worker for the next', async () => {
0075 const service = create();
0076 const load = service.loadGeometry('https://h/detector.root');
0077 await settle();
0078 expect(worker().requests).toEqual([expect.objectContaining({ type: 'load' })]);
0079
0080 worker().onerror?.({ message: 'worker-ABC.js: 404' });
0081 await expect(load).rejects.toThrow('Geometry worker error: worker-ABC.js: 404');
0082 expect(worker().terminated).toBe(true);
0083
0084 void service.loadGeometry('https://h/detector.root').catch(() => undefined);
0085 await settle();
0086 expect(FakeWorker.instances.length).toBe(2);
0087 expect(worker(1).requests).toEqual([expect.objectContaining({ type: 'load' })]);
0088 });
0089
0090 it('cancels the worker request when the signal aborts, and rejects with the reason', async () => {
0091 const service = create();
0092 const abort = new AbortController();
0093 const load = service.loadGeometry('https://h/detector.root', { signal: abort.signal });
0094 await settle();
0095 const [request] = worker().requests;
0096
0097 abort.abort(new DOMException('a newer load', 'AbortError'));
0098 expect(worker().requests[1]).toEqual({ type: 'cancel', requestId: request.requestId });
0099 worker().onmessage?.({ data: { type: 'cancelled', requestId: request.requestId } });
0100 await expect(load).rejects.toThrow('a newer load');
0101 });
0102 });