Back to home page

EIC code displayed by LXR

 
 

    


Warning, /firebird/packages/firebird-ng/api/src/lib/firebird-features.spec.ts is written in an unsupported language. File is not indexed.

0001 /**
0002  * Firebird's contributions to the shared startup machinery: the URL
0003  * shorthand grammar (`dex`, `geometry`, `event`), the server config
0004  * defaults, and event piece factory registration in provideFirebird().
0005  * The mechanism itself (config.* params, ?cmd=, server-before-URL ordering)
0006  * is covered by the @dexvis/app-features suite.
0007  */
0008 import { TestBed } from '@angular/core/testing';
0009 import { ApplicationInitStatus, Injectable, provideZonelessChangeDetection } from '@angular/core';
0010 import { provideHttpClient } from '@angular/common/http';
0011 import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
0012 import { CommandBusService, ServerConfigService, UrlStartupService, withCommandHandler } from '@dexvis/app-features';
0013 import { getEventPieceFactory } from '@dexvis/firebird-core/model';
0014 import type { EventPiece, EventPieceFactory } from '@dexvis/firebird-core';
0015 import { EventPiecePainter, type PainterMeta } from '@dexvis/firebird-core';
0016 import {
0017   firebirdFeatures,
0018   firebirdPack,
0019   withCameraPreset,
0020   withEventPiece,
0021   withFirebirdUrlShorthands,
0022   withPainter,
0023 } from './firebird-features';
0024 import { CAMERA_PRESETS, PAINTERS } from './tokens';
0025 import {
0026   CameraPresetCommandHandler,
0027   OpenDexCommandHandler,
0028   OpenGeometryCommandHandler,
0029   provideFirebird,
0030   ShowEventCommandHandler,
0031 } from '@dexvis/firebird-ng';
0032 import { defaultFirebirdConfig, ServerConfig } from './services/server-config';
0033 
0034 @Injectable()
0035 class SpecPieceFactory implements EventPieceFactory {
0036   readonly type = 'spec.FirebirdFeaturesPiece';
0037   fromDexObject(): EventPiece { throw new Error('not used'); }
0038 }
0039 
0040 describe('Firebird URL shorthands', () => {
0041   let service: UrlStartupService;
0042   let commandBus: CommandBusService;
0043 
0044   beforeEach(() => {
0045     TestBed.configureTestingModule({
0046       providers: [
0047         provideZonelessChangeDetection(),
0048         provideHttpClient(),
0049         ...withFirebirdUrlShorthands().providers,
0050         // The built-in handlers build the shorthand commands (fromUrlArg).
0051         ...withCommandHandler(OpenGeometryCommandHandler).providers,
0052         ...withCommandHandler(OpenDexCommandHandler).providers,
0053         ...withCommandHandler(ShowEventCommandHandler).providers,
0054         ...withCommandHandler(CameraPresetCommandHandler).providers,
0055       ],
0056     });
0057     service = TestBed.inject(UrlStartupService);
0058     commandBus = TestBed.inject(CommandBusService);
0059     TestBed.inject(ServerConfigService).setUnitTestConfig({});
0060   });
0061 
0062   it('turns dex/geometry/event shorthands into queued commands', () => {
0063     service.parse(new URLSearchParams('?dex=asset://data/sample.firebird.zip&geometry=exp://detector.root&event=2'));
0064     expect(commandBus.peekStartupCommands()).toEqual([
0065       { type: 'open-geometry', url: 'exp://detector.root', source: 'url' },
0066       { type: 'open-dex', url: 'asset://data/sample.firebird.zip', source: 'url' },
0067       { type: 'show-event', index: 2, source: 'url' },
0068     ]);
0069   });
0070 
0071   it('skips empty shorthands and queues them before ?cmd=', () => {
0072     service.parse(new URLSearchParams('?cmd=camera-preset:endcap&dex=&event=1'));
0073     expect(commandBus.peekStartupCommands()).toEqual([
0074       { type: 'show-event', index: 1, source: 'url' },
0075       { type: 'camera-preset', name: 'endcap', source: 'url' },
0076     ]);
0077   });
0078 });
0079 
0080 describe('provideFirebird', () => {
0081   it('boots with Firebird server config defaults, registers piece factories, and queues server commands', async () => {
0082     TestBed.configureTestingModule({
0083       providers: [
0084         provideZonelessChangeDetection(),
0085         provideHttpClient(),
0086         provideHttpClientTesting(),
0087         provideFirebird(withEventPiece(SpecPieceFactory)),
0088       ],
0089     });
0090     const serverConfig = TestBed.inject<ServerConfigService<ServerConfig>>(ServerConfigService);
0091     // Before the load answers, the config is Firebird's defaults.
0092     expect(serverConfig.configSignal()).toEqual(defaultFirebirdConfig);
0093     expect(getEventPieceFactory('spec.FirebirdFeaturesPiece')).toBeInstanceOf(SpecPieceFactory);
0094 
0095     const httpMock = TestBed.inject(HttpTestingController);
0096     httpMock.expectOne('assets/config.jsonc').flush(JSON.stringify({
0097       servedByPyrobird: true,
0098       startupCommands: ['show-event:4'],
0099     }));
0100     await TestBed.inject(ApplicationInitStatus).donePromise;
0101 
0102     expect(serverConfig.config.servedByPyrobird).toBe(true);
0103     expect(serverConfig.config.apiBaseUrl).toBe(''); // default kept
0104     expect(TestBed.inject(CommandBusService).peekStartupCommands().map(c => [c.type, c.source]))
0105       .toEqual([['show-event', 'server']]);
0106     httpMock.verify();
0107   });
0108 });
0109 
0110 /** The values a feature contributes to one multi-provider token, in order. */
0111 function contributions(feature: { providers: unknown[] }, token: unknown): unknown[] {
0112   return (feature.providers as Array<{ provide?: unknown; useValue?: unknown }>)
0113     .filter(provider => provider.provide === token)
0114     .map(provider => provider.useValue);
0115 }
0116 
0117 describe('firebirdFeatures', () => {
0118   const endcap = withCameraPreset({ name: 'endcap', position: [0, 0, 9000], target: [0, 0, 0] });
0119   const barrel = withCameraPreset({ name: 'barrel', position: [9000, 0, 0], target: [0, 0, 0] });
0120 
0121   it('skips falsy entries, so a pack can include features conditionally', () => {
0122     const debug = false;
0123     const pack = firebirdFeatures(endcap, debug && barrel, null, undefined, barrel);
0124     expect(contributions(pack, CAMERA_PRESETS).map(preset => (preset as { name: string }).name)).toEqual(['endcap', 'barrel']);
0125   });
0126 
0127   it('installs a feature id once: a later contribution replaces the earlier one at its position', () => {
0128     const closerEndcap = withCameraPreset({ name: 'endcap', position: [0, 0, 5000], target: [0, 0, 0] });
0129     const pack = firebirdFeatures(endcap, barrel, closerEndcap);
0130     expect(contributions(pack, CAMERA_PRESETS)).toEqual([
0131       { name: 'endcap', position: [0, 0, 5000], target: [0, 0, 0] },
0132       { name: 'barrel', position: [9000, 0, 0], target: [0, 0, 0] },
0133     ]);
0134   });
0135 
0136   it('installs a pack passed twice once', () => {
0137     const pack = firebirdPack('spec-pack', endcap);
0138     expect(contributions(firebirdFeatures(pack, barrel, pack), CAMERA_PRESETS).length).toBe(2);
0139   });
0140 });
0141 
0142 describe('withPainter', () => {
0143   class MetaPainter extends EventPiecePainter {
0144     static meta: PainterMeta = { id: 'spec-meta', forPieceTypes: ['spec.Hits', 'spec.Clusters'] };
0145     paint(): void { /* nothing to draw */ }
0146   }
0147 
0148   class PlainPainter extends EventPiecePainter {
0149     paint(): void { /* nothing to draw */ }
0150   }
0151 
0152   it('registers the painter for every type its static meta declares', () => {
0153     expect(contributions(withPainter(MetaPainter), PAINTERS)).toEqual([
0154       { forPieceType: 'spec.Hits', painterClass: MetaPainter },
0155       { forPieceType: 'spec.Clusters', painterClass: MetaPainter },
0156     ]);
0157   });
0158 
0159   it('registers for the forPieceType option instead of the meta types', () => {
0160     expect(contributions(withPainter(MetaPainter, { forPieceType: 'spec.Tracks' }), PAINTERS))
0161       .toEqual([{ forPieceType: 'spec.Tracks', painterClass: MetaPainter }]);
0162     expect(contributions(withPainter(PlainPainter, { forPieceType: 'spec.Tracks' }), PAINTERS))
0163       .toEqual([{ forPieceType: 'spec.Tracks', painterClass: PlainPainter }]);
0164   });
0165 
0166   it('throws for a painter with neither the option nor meta types', () => {
0167     expect(() => withPainter(PlainPainter))
0168       .toThrow('withPainter(PlainPainter): pass { forPieceType } or declare static meta.forPieceTypes');
0169   });
0170 });