1// SPDX-License-Identifier: GPL-2.0-only
2// Copyright (c) 2026-present FERS Contributors (see AUTHORS.md).
4import { describe, expect, test } from 'bun:test';
6 formatNumberFieldValue,
7 resolveNumberFieldBlur,
9} from './InspectorControls';
11 createDechirpReference,
14 DECHIRP_REFERENCE_SOURCE_OPTIONS,
15 getAvailableDechirpReferenceSourceOptions,
16 getCompatibleWaveforms,
19 getPulsedRadarFieldLabels,
21 resolveWaveformSelectValue,
22 shouldClearWaveformForRadarType,
23} from './PlatformComponentInspector';
25 ensureCubicPositionWaypoints,
26 ensureCubicRotationWaypoints,
27} from './PlatformInspector';
29 createWaveformForType,
30 getVisibleWaveformFieldLabels,
31 WAVEFORM_TYPE_OPTIONS,
32} from './WaveformInspector';
34describe('InspectorControls blur resolution', () => {
35 test('reverts required numeric fields when blurred empty', () => {
36 expect(resolveNumberFieldBlur('', 42, 'revert')).toEqual({
38 error: 'Value required.',
43 test('commits null for unsettable numeric fields when blurred empty', () => {
44 expect(resolveNumberFieldBlur('', 42, 'null')).toEqual({
51 test('reverts timing noise-entry drafts instead of producing empty commits', () => {
52 expect(resolveNumberFieldBlur('', 0.5, 'revert')).toEqual({
54 error: 'Value required.',
59 test('rejects invalid numeric drafts on blur', () => {
60 expect(resolveNumberFieldBlur('-', 7, 'revert')).toEqual({
62 error: 'Invalid number.',
67 test('parses valid numeric drafts on blur', () => {
68 expect(resolveNumberFieldBlur('1e3', 7, 'revert')).toEqual({
75 test('reverts required text fields when blurred empty', () => {
76 expect(resolveTextFieldBlur(' ', 'Timing A', false)).toEqual({
78 error: 'Value required.',
79 nextDraft: 'Timing A',
83 test('commits non-empty text drafts', () => {
85 resolveTextFieldBlur('Updated Timing', 'Timing A', false)
88 nextDraft: 'Updated Timing',
89 value: 'Updated Timing',
93 test('formats null numeric values as an empty draft', () => {
94 expect(formatNumberFieldValue(null)).toBe('');
98describe('Waveform inspector authoring options', () => {
99 test('offers pulse file, CW, and FMCW waveform types', () => {
100 expect(WAVEFORM_TYPE_OPTIONS).toEqual([
101 { value: 'pulsed_from_file', label: 'Pulse File' },
102 { value: 'cw', label: 'CW' },
103 { value: 'fmcw_linear_chirp', label: 'FMCW Linear Chirp' },
104 { value: 'fmcw_triangle', label: 'FMCW Triangle' },
108 test('creates FMCW defaults while preserving common waveform fields', () => {
109 const fmcwWaveform = createWaveformForType(
113 name: 'Search chirp',
116 carrier_frequency: 9.6e9,
118 start_frequency_offset: 125,
123 expect(fmcwWaveform).toMatchObject({
126 name: 'Search chirp',
127 waveformType: 'fmcw_linear_chirp',
130 carrier_frequency: 9.6e9,
131 chirp_bandwidth: 4e3,
132 chirp_duration: 1e-3,
134 start_frequency_offset: 125,
139 test('reports FMCW chirp fields only for FMCW waveforms', () => {
140 expect(getVisibleWaveformFieldLabels('fmcw_linear_chirp')).toEqual([
142 'Chirp Bandwidth (Hz)',
143 'Chirp Duration (s)',
145 'Start Frequency Offset (Hz)',
148 expect(getVisibleWaveformFieldLabels('fmcw_triangle')).toEqual([
149 'Chirp Bandwidth (Hz)',
150 'Chirp Duration (s)',
151 'Start Frequency Offset (Hz)',
154 expect(getVisibleWaveformFieldLabels('cw')).toEqual([]);
158describe('Platform inspector authoring helpers', () => {
159 test('adds a second position waypoint before selecting cubic interpolation', () => {
170 const next = ensureCubicPositionWaypoints(waypoints);
172 expect(next).toHaveLength(2);
173 expect(next[0]).toEqual(waypoints[0]);
174 expect(next[1]).toMatchObject({
180 expect(next[1].id).not.toBe('position-1');
183 test('adds a second rotation waypoint before selecting cubic interpolation', () => {
193 const next = ensureCubicRotationWaypoints(waypoints);
195 expect(next).toHaveLength(2);
196 expect(next[0]).toEqual(waypoints[0]);
197 expect(next[1]).toMatchObject({
202 expect(next[1].id).not.toBe('rotation-1');
206describe('Platform component inspector waveform compatibility', () => {
208 { id: '1', name: 'Pulse', waveformType: 'pulsed_from_file' },
209 { id: '2', name: 'Tone', waveformType: 'cw' },
210 { id: '3', name: 'Chirp', waveformType: 'fmcw_linear_chirp' },
211 { id: '4', name: 'Triangle', waveformType: 'fmcw_triangle' },
214 test('offers FMCW radar mode and hides pulsed-only fields for FMCW/CW', () => {
215 expect(RADAR_MODE_OPTIONS).toEqual([
216 { value: 'pulsed', label: 'Pulsed' },
217 { value: 'cw', label: 'CW' },
218 { value: 'fmcw', label: 'FMCW' },
221 expect(getPulsedRadarFieldLabels('pulsed')).toEqual([
226 expect(getPulsedRadarFieldLabels('cw')).toEqual([]);
227 expect(getPulsedRadarFieldLabels('fmcw')).toEqual([]);
230 test('filters waveform dropdown choices by radar mode', () => {
231 expect(getCompatibleWaveforms(waveforms, 'pulsed')).toEqual([
234 expect(getCompatibleWaveforms(waveforms, 'cw')).toEqual([waveforms[1]]);
235 expect(getCompatibleWaveforms(waveforms, 'fmcw')).toEqual([
241 test('clears and blocks incompatible waveform selection values', () => {
242 expect(shouldClearWaveformForRadarType('1', waveforms, 'fmcw')).toBe(
245 expect(shouldClearWaveformForRadarType('3', waveforms, 'fmcw')).toBe(
248 expect(shouldClearWaveformForRadarType('4', waveforms, 'fmcw')).toBe(
251 expect(resolveWaveformSelectValue('1', waveforms, 'fmcw')).toBe('');
252 expect(resolveWaveformSelectValue('3', waveforms, 'fmcw')).toBe('3');
253 expect(resolveWaveformSelectValue('4', waveforms, 'fmcw')).toBe('4');
256 test('offers dechirp modes and source options for receiver FMCW mode', () => {
257 expect(DECHIRP_MODE_OPTIONS).toEqual([
258 { value: 'none', label: 'None' },
259 { value: 'physical', label: 'Physical' },
260 { value: 'ideal', label: 'Ideal' },
262 expect(DECHIRP_REFERENCE_SOURCE_OPTIONS).toEqual([
263 { value: 'attached', label: 'Attached' },
264 { value: 'transmitter', label: 'Transmitter' },
265 { value: 'custom', label: 'Custom Waveform' },
267 expect(getAvailableDechirpReferenceSourceOptions('monostatic')).toEqual(
268 [...DECHIRP_REFERENCE_SOURCE_OPTIONS]
270 expect(getAvailableDechirpReferenceSourceOptions('receiver')).toEqual([
271 { value: 'transmitter', label: 'Transmitter' },
272 { value: 'custom', label: 'Custom Waveform' },
275 getAvailableDechirpReferenceSourceOptions('receiver', 'attached')
276 ).toEqual([...DECHIRP_REFERENCE_SOURCE_OPTIONS]);
279 test('builds backend-valid dechirp config shapes', () => {
280 expect(createFmcwModeConfig('none')).toEqual({});
281 expect(createFmcwModeConfig('physical')).toEqual({
282 dechirp_mode: 'physical',
283 dechirp_reference: { source: 'attached' },
286 createFmcwModeConfig('ideal', {
287 dechirp_mode: 'physical',
289 source: 'transmitter',
290 transmitter_name: 'TX A',
294 dechirp_mode: 'ideal',
296 source: 'transmitter',
297 transmitter_name: 'TX A',
301 createFmcwModeConfig('ideal', {
302 dechirp_mode: 'physical',
303 dechirp_reference: { source: 'attached' },
305 if_filter_bandwidth: 4e5,
306 if_filter_transition_width: 1e5,
309 dechirp_mode: 'ideal',
310 dechirp_reference: { source: 'attached' },
312 if_filter_bandwidth: 4e5,
313 if_filter_transition_width: 1e5,
316 createDechirpReference('transmitter', {
318 waveform_name: 'Bad carryover',
319 transmitter_name: 'TX B',
322 source: 'transmitter',
323 transmitter_name: 'TX B',
326 createDechirpReference('custom', {
327 source: 'transmitter',
328 transmitter_name: 'Bad carryover',
329 waveform_name: 'LO Waveform',
333 waveform_name: 'LO Waveform',
337 test('lists only FMCW waveforms and FMCW emitters for dechirp references', () => {
338 const fullWaveforms = [
341 type: 'Waveform' as const,
343 waveformType: 'pulsed_from_file' as const,
345 carrier_frequency: 1,
346 filename: 'pulse.h5',
350 type: 'Waveform' as const,
352 waveformType: 'fmcw_linear_chirp' as const,
353 direction: 'up' as const,
355 carrier_frequency: 1,
359 start_frequency_offset: 0,
366 type: 'Platform' as const,
369 interpolation: 'static' as const,
381 type: 'fixed' as const,
390 type: 'transmitter' as const,
392 radarType: 'fmcw' as const,
401 type: 'transmitter' as const,
403 radarType: 'pulsed' as const,
414 expect(getFmcwWaveformNames(fullWaveforms)).toEqual(['FMCW LO']);
415 expect(getFmcwEmitterNames(platforms, fullWaveforms)).toEqual([