@@ -150,6 +150,66 @@ describe("replay data loading", () => {
150150 expect ( selectDisplayTrailsByHex ( st ) . abc123 ) . toHaveLength ( 2 ) ;
151151 } ) ;
152152
153+ it ( "expands compact replay aircraft into the display aircraft shape" , async ( ) => {
154+ const block = {
155+ version : 2 ,
156+ start : 120_000 ,
157+ end : 180_000 ,
158+ step_ms : 5_000 ,
159+ frames : [
160+ {
161+ ts : 130_000 ,
162+ aircraft : [
163+ {
164+ hex : "abc123" ,
165+ type : "adsb_icao" ,
166+ flight : "UAL123" ,
167+ r : "N12345" ,
168+ t : "B738" ,
169+ lat : 34 ,
170+ lon : - 118 ,
171+ alt_baro : 12000 ,
172+ gs : 420 ,
173+ track : 90 ,
174+ seen_pos : 0.5 ,
175+ rssi : - 12 ,
176+ } ,
177+ {
178+ hex : "ground1" ,
179+ type : "adsb_icao" ,
180+ alt_baro : "ground" ,
181+ } ,
182+ ] ,
183+ } ,
184+ ] ,
185+ } ;
186+ globalThis . fetch = vi . fn ( async ( url : string ) =>
187+ url . includes ( "manifest" ) ? responseJson ( manifest ( ) ) : responseJson ( block ) ,
188+ ) as never ;
189+
190+ await refreshReplayManifest ( ) ;
191+ useIdentStore . getState ( ) . enterReplay ( 130_000 ) ;
192+ await ensureReplayRange ( 120_000 , 180_000 ) ;
193+
194+ const aircraft = selectDisplayAircraftMap ( useIdentStore . getState ( ) ) ;
195+ expect ( aircraft . get ( "abc123" ) ) . toMatchObject ( {
196+ hex : "abc123" ,
197+ idKind : "icao" ,
198+ source : "adsb_icao" ,
199+ flight : "UAL123" ,
200+ reg : "N12345" ,
201+ typeDesignator : "B738" ,
202+ altBaroFt : 12000 ,
203+ gsKt : 420 ,
204+ trackDeg : 90 ,
205+ seenPosSec : 0.5 ,
206+ rssiDbfs : - 12 ,
207+ } ) ;
208+ expect ( aircraft . get ( "ground1" ) ) . toMatchObject ( {
209+ onGround : true ,
210+ } ) ;
211+ } ) ;
212+
153213 it ( "does not request blocks that are already loaded" , async ( ) => {
154214 const block = replayBlock ( ) ;
155215 globalThis . fetch = vi . fn ( async ( url : string ) =>
@@ -574,16 +634,10 @@ describe("replay data loading", () => {
574634 await refreshReplayManifest ( ) ;
575635 await ensureReplayRange ( 120_000 , 180_000 , { background : true } ) ;
576636
577- expect ( globalThis . fetch ) . toHaveBeenCalledTimes ( 4 ) ;
578- expect ( globalThis . fetch ) . toHaveBeenCalledWith (
637+ expect ( globalThis . fetch ) . toHaveBeenCalledTimes ( 3 ) ;
638+ expect ( globalThis . fetch ) . not . toHaveBeenCalledWith (
579639 "/ident/api/replay/block-failure" ,
580- expect . objectContaining ( {
581- method : "POST" ,
582- body : JSON . stringify ( {
583- url : "/api/replay/blocks/120000-180000.json.zst" ,
584- reason : "decode_failed" ,
585- } ) ,
586- } ) ,
640+ expect . anything ( ) ,
587641 ) ;
588642 expect ( warn ) . not . toHaveBeenCalledWith (
589643 "[ident replay] background block load failed" ,
@@ -600,6 +654,37 @@ describe("replay data loading", () => {
600654 ) ;
601655 } ) ;
602656
657+ it ( "surfaces replay blocks whose frames normalize away" , async ( ) => {
658+ vi . spyOn ( console , "warn" ) . mockImplementation ( ( ) => { } ) ;
659+ globalThis . fetch = vi . fn ( async ( url : string ) =>
660+ url . includes ( "manifest" )
661+ ? responseJson ( manifest ( ) )
662+ : responseJson ( {
663+ version : 2 ,
664+ start : 120_000 ,
665+ end : 180_000 ,
666+ step_ms : 5_000 ,
667+ frames : [ { aircraft : [ { flight : "BROKEN" } ] } ] ,
668+ } ) ,
669+ ) as never ;
670+
671+ await refreshReplayManifest ( ) ;
672+ await ensureReplayRange ( 120_000 , 180_000 , { background : true } ) ;
673+
674+ expect ( globalThis . fetch ) . not . toHaveBeenCalledWith (
675+ "/ident/api/replay/block-failure" ,
676+ expect . anything ( ) ,
677+ ) ;
678+ expect ( useIdentStore . getState ( ) . replay . error ) . toContain (
679+ "Invalid replay block" ,
680+ ) ;
681+ expect (
682+ useIdentStore . getState ( ) . replay . cache [
683+ "/api/replay/blocks/120000-180000.json.zst"
684+ ] ,
685+ ) . toBeUndefined ( ) ;
686+ } ) ;
687+
603688 it ( "retries malformed replay blocks and clears the matching error after recovery" , async ( ) => {
604689 let malformed = true ;
605690 globalThis . fetch = vi . fn ( async ( url : string ) => {
0 commit comments