Warning, /firebird/packages/firebird-ng/display/src/lib/services/file-open-router.service.spec.ts is written in an unsupported language. File is not indexed.
0001 /**
0002 * The routing contract: the control carries facts, the loaders decide.
0003 *
0004 * These specs pin the part that is easy to get wrong later - that a `.root`
0005 * file goes to whichever loader recognizes what is INSIDE it, and that no
0006 * format knowledge leaked into the router itself.
0007 */
0008
0009 import { TestBed } from '@angular/core/testing';
0010 import { describe, expect, it, beforeEach, vi } from 'vitest';
0011 import type {
0012 DataSource,
0013 EventDataLoader,
0014 FileContentProbe,
0015 GeometryDataLoader,
0016 } from '@dexvis/firebird-core';
0017 import { EVENT_LOADERS, GEOMETRY_LOADERS } from '@dexvis/firebird-ng/api';
0018 import { FileOpenRouterService } from './file-open-router.service';
0019 import { RootFileService } from './root-file.service';
0020
0021 const GEOMETRY_PROBE: FileContentProbe = {
0022 name: 'detector.root',
0023 entries: [
0024 { name: 'Default', className: 'TGeoManager' },
0025 { name: 'StreamerInfo', className: 'TList' },
0026 ],
0027 };
0028
0029 const EVENTS_PROBE: FileContentProbe = {
0030 name: 'reco.edm4eic.root',
0031 entries: [
0032 { name: 'events', className: 'TTree' },
0033 { name: 'StreamerInfo', className: 'TList' },
0034 ],
0035 };
0036
0037 const HISTOGRAM_PROBE: FileContentProbe = {
0038 name: 'plots.root',
0039 entries: [{ name: 'hMass', className: 'TH1F' }],
0040 };
0041
0042 class FakeGeometryLoader implements GeometryDataLoader {
0043 readonly meta = { id: 'fake-geometry', label: 'Fake geometry', fileExtensions: ['.root'] };
0044 load = vi.fn(() => Promise.reject(new Error('not called in this spec')));
0045 canLoad(source: DataSource): boolean {
0046 return name(source).endsWith('.root');
0047 }
0048 canLoadContent(probe: FileContentProbe): boolean {
0049 return probe.entries.some(entry => entry.className === 'TGeoManager');
0050 }
0051 }
0052
0053 class FakeEventLoader implements EventDataLoader {
0054 readonly meta = { id: 'fake-events', label: 'Fake events', fileExtensions: ['.root'] };
0055 loadEvents = vi.fn(() => Promise.reject(new Error('not called in this spec')));
0056 canLoad(source: DataSource): boolean {
0057 return name(source).endsWith('.root');
0058 }
0059 canLoadContent(probe: FileContentProbe): boolean {
0060 return probe.entries.some(entry => entry.name === 'events' && entry.className === 'TTree');
0061 }
0062 }
0063
0064 /** A loader that claims a name nothing else does, and cannot be probed. */
0065 class FakeDexLoader implements EventDataLoader {
0066 readonly meta = { id: 'fake-dex', label: 'Fake DEX', fileExtensions: ['.firebird.json'] };
0067 loadEvents = vi.fn(() => Promise.reject(new Error('not called in this spec')));
0068 canLoad(source: DataSource): boolean {
0069 return name(source).endsWith('.firebird.json');
0070 }
0071 }
0072
0073 function name(source: DataSource): string {
0074 return typeof source === 'string' ? source : source.name;
0075 }
0076
0077 describe('FileOpenRouterService', () => {
0078 let probe: ReturnType<typeof vi.fn>;
0079
0080 function setup(probeResult: FileContentProbe): FileOpenRouterService {
0081 probe = vi.fn(async () => probeResult);
0082 TestBed.configureTestingModule({
0083 providers: [
0084 { provide: GEOMETRY_LOADERS, useValue: [new FakeGeometryLoader()], multi: false },
0085 { provide: EVENT_LOADERS, useValue: [new FakeDexLoader(), new FakeEventLoader()], multi: false },
0086 { provide: RootFileService, useValue: { probe } },
0087 ],
0088 });
0089 return TestBed.inject(FileOpenRouterService);
0090 }
0091
0092 beforeEach(() => TestBed.resetTestingModule());
0093
0094 it('sends a ROOT file holding a TGeoManager to the geometry loader', async () => {
0095 const router = setup(GEOMETRY_PROBE);
0096 const route = await router.route('detector.root');
0097 expect(route.kind).toBe('geometry');
0098 expect(probe).toHaveBeenCalledOnce();
0099 });
0100
0101 it('sends a ROOT file holding an events tree to the event loader', async () => {
0102 const router = setup(EVENTS_PROBE);
0103 const route = await router.route('reco.edm4eic.root');
0104 expect(route.kind).toBe('events');
0105 });
0106
0107 it('routes a dropped File the same way as a URL', async () => {
0108 const router = setup(EVENTS_PROBE);
0109 const file = new File([new Uint8Array(4)], 'dropped.root');
0110 const route = await router.route(file);
0111 expect(route.kind).toBe('events');
0112 expect(probe).toHaveBeenCalledWith(file);
0113 });
0114
0115 it('reports what an unrecognized ROOT file holds instead of guessing', async () => {
0116 const router = setup(HISTOGRAM_PROBE);
0117 const route = await router.route('plots.root');
0118 expect(route.kind).toBe('unknown');
0119 if (route.kind === 'unknown') {
0120 expect(route.message).toContain('hMass (TH1F)');
0121 // StreamerInfo is noise in a message meant for a human
0122 expect(route.message).not.toContain('StreamerInfo');
0123 }
0124 });
0125
0126 it('does not probe when the name is already unambiguous', async () => {
0127 const router = setup(EVENTS_PROBE);
0128 const route = await router.route('sample.firebird.json');
0129 expect(route.kind).toBe('events');
0130 if (route.kind === 'events') expect(route.loader.meta.id).toBe('fake-dex');
0131 expect(probe).not.toHaveBeenCalled();
0132 });
0133
0134 it('says so when nothing claims the source', async () => {
0135 const router = setup(EVENTS_PROBE);
0136 const route = await router.route('notes.txt');
0137 expect(route.kind).toBe('unknown');
0138 if (route.kind === 'unknown') expect(route.message).toContain('No loader claims');
0139 expect(probe).not.toHaveBeenCalled();
0140 });
0141 });