Warning, /firebird/packages/firebird-example-extension/src/cherenkov-ring.spec.ts is written in an unsupported language. File is not indexed.
0001 /**
0002 * The example extension as a template: the piece decodes DEX columns and
0003 * rejects malformed ones, and the painter reads its ring color knob through
0004 * `this.config`, restyles live in `onConfigChanged()`, and shows rings by
0005 * event time.
0006 */
0007 import { describe, expect, it } from 'vitest';
0008 import { Group, Line, LineBasicMaterial } from 'three';
0009 import {
0010 PainterConfigView,
0011 RESERVED_PAINTER_KNOB_KEYS,
0012 assertPainterKnobKeys,
0013 defaultPainterConfigView,
0014 } from '@dexvis/firebird-core';
0015 import { Signal, signal } from '@angular/core';
0016 import { CherenkovRingPiece, CherenkovRingPieceFactory } from './cherenkov-ring.piece';
0017 import { CherenkovRingPainter } from './cherenkov-ring.painter';
0018
0019 function ringsDex(overrides: Record<string, unknown> = {}) {
0020 return {
0021 name: 'ExampleRings',
0022 type: 'example.CherenkovRing',
0023 version: '1.0',
0024 count: 2,
0025 columns: {
0026 center: [0, 0, 1200, 100, 0, 1200],
0027 radius: [600, 450],
0028 nPhotons: [14, 9],
0029 time: [5, 8],
0030 track: [0, -1],
0031 },
0032 refs: { track: 'ExampleTracks' },
0033 ...overrides,
0034 };
0035 }
0036
0037 /** A config view whose ring color the test changes, like a knob edit in the painter panel. */
0038 function editableConfig(ringColor: string) {
0039 const color = signal(ringColor);
0040 const view: PainterConfigView = {
0041 value: <T>(key: string) => (key === 'ringColor' ? color() : undefined) as T,
0042 signal: <T>() => color as unknown as Signal<T>,
0043 };
0044 return { view, color };
0045 }
0046
0047 function ringColors(node: Group): string[] {
0048 const colors: string[] = [];
0049 node.traverse(object => {
0050 if (object instanceof Line) colors.push((object.material as LineBasicMaterial).color.getHexString());
0051 });
0052 return colors;
0053 }
0054
0055 function paintRings(config?: PainterConfigView) {
0056 const piece = new CherenkovRingPieceFactory().fromDexObject(ringsDex()) as CherenkovRingPiece;
0057 const node = new Group();
0058 const painter = new CherenkovRingPainter(node, piece, config);
0059 return { piece, node, painter };
0060 }
0061
0062 describe('CherenkovRingPiece', () => {
0063 it('decodes the columns and the ring -> track reference', () => {
0064 const piece = new CherenkovRingPieceFactory().fromDexObject(ringsDex()) as CherenkovRingPiece;
0065 expect(piece.count).toBe(2);
0066 expect(Array.from(piece.radius)).toEqual([600, 450]);
0067 expect(piece.timeRange).toEqual([5, 8]);
0068 expect(piece.entityRefs(0)).toEqual([{ column: 'track', targetPiece: 'ExampleTracks', targetIndex: 0 }]);
0069 expect(piece.entityRefs(1)).toEqual([]);
0070 expect(piece.toDexObject().columns.radius).toEqual([600, 450]);
0071 });
0072
0073 it('rejects a column whose length disagrees with count', () => {
0074 expect(() => CherenkovRingPiece.fromDexObject(ringsDex({ count: 3 }))).toThrow(/radius|center/);
0075 });
0076 });
0077
0078 describe('CherenkovRingPainter', () => {
0079 it('declares its ring color as a knob, with no reserved knob names', () => {
0080 expect(CherenkovRingPainter.meta.configs?.map(knob => knob.key)).toEqual(['ringColor']);
0081 expect(RESERVED_PAINTER_KNOB_KEYS).not.toContain('ringColor');
0082 expect(() => assertPainterKnobKeys(CherenkovRingPainter)).not.toThrow();
0083 });
0084
0085 it('paints in the knob default without a config system (workers, scripts)', () => {
0086 const { node } = paintRings();
0087 expect(defaultPainterConfigView(CherenkovRingPainter.meta).value('ringColor')).toBe('#00e5ff');
0088 expect(ringColors(node)).toEqual(['00e5ff', '00e5ff']);
0089 });
0090
0091 it('builds rings in the configured color and restyles them live on knob changes', () => {
0092 const { view, color } = editableConfig('#ff0000');
0093 const { node, painter } = paintRings(view);
0094 expect(ringColors(node)).toEqual(['ff0000', 'ff0000']);
0095
0096 color.set('#00ff00');
0097 painter.onConfigChanged();
0098 expect(ringColors(node)).toEqual(['00ff00', '00ff00']);
0099 });
0100
0101 it('keeps a highlighted ring white through a restyle, and returns it to the knob color', () => {
0102 const { view, color } = editableConfig('#ff0000');
0103 const { node, painter } = paintRings(view);
0104 painter.highlightEntity(1);
0105 color.set('#0000ff');
0106 painter.onConfigChanged();
0107 expect(ringColors(node)).toEqual(['0000ff', 'ffffff']);
0108
0109 painter.unhighlightEntity(1);
0110 expect(ringColors(node)).toEqual(['0000ff', '0000ff']);
0111 });
0112
0113 it('shows a ring once the event time passed its production time', () => {
0114 const { node, painter } = paintRings();
0115 const rings: Line[] = [];
0116 node.traverse(object => { if (object instanceof Line) rings.push(object); });
0117
0118 painter.paint(6);
0119 expect(rings.map(ring => ring.visible)).toEqual([true, false]);
0120 painter.paint(null);
0121 expect(rings.map(ring => ring.visible)).toEqual([true, true]);
0122 });
0123
0124 it('maps each ring object back to its entity index for selection', () => {
0125 const { painter } = paintRings();
0126 expect(painter.objectForEntity(1)?.userData['entityIndex']).toBe(1);
0127 expect(painter.objectForEntity(1)?.userData['pieceName']).toBe('ExampleRings');
0128 });
0129 });