@@ -226,71 +226,169 @@ describe('CrisStatisticsPageComponent', () => {
226226 expect ( component . selectedReportId ) . toBeUndefined ( ) ;
227227 } ) ;
228228
229- it ( 'should set selectedReportId to the first report id if no reportType query param is present ' , ( ) => {
229+ it ( 'should emit null on selectedFullReport$ before loading the new report ' , ( ) => {
230230 const category = { id : 'category1' } ;
231- const reports = [ { id : 'report1' , reportType : 'type1' } , { id : 'report2' , reportType : 'type2' } ] ;
231+ const reports = [ { id : 'report1' , reportType : 'type1' } ] ;
232232 spyOn ( component , 'getReports$' ) . and . returnValue ( of ( reports as UsageReport [ ] ) ) ;
233233 spyOn ( component , 'getReportId' ) . and . returnValue ( of ( null ) ) ;
234234 spyOn ( component , 'getCategoryId' ) . and . returnValue ( of ( null ) ) ;
235235 spyOn ( component , 'setStatisticsState' ) ;
236236
237+ const emitted : ( UsageReport | null ) [ ] = [ ] ;
238+ ( component as any ) . selectedFullReport$ . subscribe ( ( value ) => emitted . push ( value ) ) ;
239+
237240 component . getUserReports ( category ) ;
238241
239- expect ( component . setStatisticsState ) . toHaveBeenCalledWith ( 'report1' , 'category1' ) ;
240- expect ( component . selectedReportId ) . toBe ( 'report1' ) ;
242+ // first emitted value is the initial null from the BehaviorSubject,
243+ // second is the reset to null done at the start of getUserReports
244+ expect ( emitted [ 0 ] ) . toBeNull ( ) ;
245+ expect ( emitted [ 1 ] ) . toBeNull ( ) ;
241246 } ) ;
242247
243- it ( 'should set selectedReportId to the report id matching the reportType query param' , ( ) => {
244- const category = { id : 'category1' } ;
245- const reports = [ { id : 'report1' , reportType : 'type1' } , { id : 'report2' , reportType : 'type2' } ] ;
246- spyOn ( component , 'getReports$' ) . and . returnValue ( of ( reports as UsageReport [ ] ) ) ;
247- spyOn ( component , 'getReportId' ) . and . returnValue ( of ( null ) ) ;
248- spyOn ( component , 'getCategoryId' ) . and . returnValue ( of ( null ) ) ;
249- spyOn ( component , 'setStatisticsState' ) ;
248+ describe ( 'loadFullReport' , ( ) => {
250249
251- component . getUserReports ( category , 'type2' ) ;
250+ beforeEach ( ( ) => {
251+ ( component as any ) . fullReportCache . clear ( ) ;
252+ ( component as any ) . inFlightRequests . clear ( ) ;
253+ } ) ;
254+
255+ it ( 'should return of(null) and reset currentReportId when reportId is falsy' , ( done : DoneFn ) => {
256+ ( component as any ) . currentReportId = 'something' ;
257+
258+ component . loadFullReport ( null ) . subscribe ( ( result ) => {
259+ expect ( result ) . toBeNull ( ) ;
260+ expect ( ( component as any ) . currentReportId ) . toBeNull ( ) ;
261+ done ( ) ;
262+ } ) ;
263+ } ) ;
264+
265+ it ( 'should return the cached report without calling the service when present in cache' , ( done : DoneFn ) => {
266+ const reportId = '1911e8a4-6939-490c-b58b-a5d70f8d91fb_TotalVisits' ;
267+ const cachedReport = { id : reportId } as UsageReport ;
268+ ( component as any ) . fullReportCache . set ( reportId , cachedReport ) ;
269+ spyOn ( usageReportServiceStub , 'findById' ) . and . callThrough ( ) ;
270+
271+ component . loadFullReport ( reportId ) . subscribe ( ( result ) => {
272+ expect ( result ) . toBe ( cachedReport ) ;
273+ expect ( usageReportServiceStub . findById ) . not . toHaveBeenCalled ( ) ;
274+ done ( ) ;
275+ } ) ;
276+ } ) ;
277+
278+ it ( 'should call usageReportService.findById when the report is not cached' , ( done : DoneFn ) => {
279+ const reportId = '1911e8a4-6939-490c-b58b-a5d70f8d91fb_TotalVisits' ;
280+ spyOn ( usageReportServiceStub , 'findById' ) . and . callThrough ( ) ;
281+
282+ component . loadFullReport ( reportId ) . subscribe ( ( result ) => {
283+ expect ( usageReportServiceStub . findById ) . toHaveBeenCalledWith ( reportId , false , true ) ;
284+ expect ( result . id ) . toBe ( reportId ) ;
285+ expect ( ( component as any ) . currentReportId ) . toBe ( reportId ) ;
286+ done ( ) ;
287+ } ) ;
288+ } ) ;
289+
290+ it ( 'should reuse the same observable for concurrent requests of the same reportId' , ( ) => {
291+ const reportId = '1911e8a4-6939-490c-b58b-a5d70f8d91fb_TotalVisits' ;
292+ spyOn ( usageReportServiceStub , 'findById' ) . and . callThrough ( ) ;
293+
294+ const first$ = component . loadFullReport ( reportId ) ;
295+ const second$ = component . loadFullReport ( reportId ) ;
296+
297+ expect ( first$ ) . toBe ( second$ ) ;
298+ expect ( usageReportServiceStub . findById ) . toHaveBeenCalledTimes ( 1 ) ;
299+ } ) ;
252300
253- expect ( component . setStatisticsState ) . toHaveBeenCalledWith ( 'report2' , 'category1' ) ;
254- expect ( component . selectedReportId ) . toBe ( 'report2' ) ;
255301 } ) ;
256302
257- it ( 'should set selectedReportId to the first report id if reportType query param does not match any report' , ( ) => {
258- const category = { id : 'category1' } ;
259- const reports = [ { id : 'report1' , reportType : 'type1' } , { id : 'report2' , reportType : 'type2' } ] ;
260- spyOn ( component , 'getReports$' ) . and . returnValue ( of ( reports as UsageReport [ ] ) ) ;
261- spyOn ( component , 'getReportId' ) . and . returnValue ( of ( null ) ) ;
262- spyOn ( component , 'getCategoryId' ) . and . returnValue ( of ( null ) ) ;
263- spyOn ( component , 'setStatisticsState' ) ;
303+ describe ( 'setReportId' , ( ) => {
264304
265- component . getUserReports ( category , 'non_existing_type' ) ;
305+ beforeEach ( ( ) => {
306+ ( component as any ) . fullReportCache . clear ( ) ;
307+ } ) ;
308+
309+ it ( 'should cache the report and update selectedFullReport$ when reportId matches currentReportId' , ( ) => {
310+ const reportId = 'report1' ;
311+ const report = { id : reportId } as UsageReport ;
312+ ( component as any ) . currentReportId = reportId ;
313+
314+ ( component as any ) . setReportId ( report , reportId ) ;
315+
316+ expect ( ( component as any ) . fullReportCache . get ( reportId ) ) . toBe ( report ) ;
317+ expect ( ( component as any ) . selectedFullReport$ . value ) . toBe ( report ) ;
318+ } ) ;
319+
320+ it ( 'should not update selectedFullReport$ when reportId does not match currentReportId (stale response)' , ( ) => {
321+ const report = { id : 'oldReport' } as UsageReport ;
322+ ( component as any ) . currentReportId = 'newerReport' ;
323+ ( component as any ) . selectedFullReport$ . next ( null ) ;
324+
325+ ( component as any ) . setReportId ( report , 'oldReport' ) ;
326+
327+ expect ( ( component as any ) . selectedFullReport$ . value ) . toBeNull ( ) ;
328+ // it is still cached for later reuse even if stale
329+ expect ( ( component as any ) . fullReportCache . get ( 'oldReport' ) ) . toBe ( report ) ;
330+ } ) ;
331+
332+ it ( 'should not cache a null/falsy report' , ( ) => {
333+ ( component as any ) . currentReportId = 'report1' ;
334+
335+ ( component as any ) . setReportId ( null , 'report1' ) ;
336+
337+ expect ( ( component as any ) . fullReportCache . has ( 'report1' ) ) . toBe ( false ) ;
338+ expect ( ( component as any ) . selectedFullReport$ . value ) . toBeNull ( ) ;
339+ } ) ;
266340
267- expect ( component . setStatisticsState ) . toHaveBeenCalledWith ( 'report1' , 'category1' ) ;
268- expect ( component . selectedReportId ) . toBe ( 'report1' ) ;
269341 } ) ;
270342
271- it ( 'should set selectedReportId and categoryId from state if they exist' , ( ) => {
272- const category = { id : 'category1' } ;
273- const reports = [ { id : 'report1' , reportType : 'type1' } , { id : 'report2' , reportType : 'type2' } ] ;
274- spyOn ( component , 'getReports$' ) . and . returnValue ( of ( reports as UsageReport [ ] ) ) ;
275- spyOn ( component , 'getReportId' ) . and . returnValue ( of ( 'report1' ) ) ;
276- spyOn ( component , 'getCategoryId' ) . and . returnValue ( of ( 'category1' ) ) ;
277- spyOn ( component , 'setStatisticsState' ) ;
343+ describe ( 'changeReport' , ( ) => {
278344
279- component . getUserReports ( category ) ;
345+ it ( 'should update statistics state, selectedReportId and selectedFullReport$' , ( done : DoneFn ) => {
346+ const reportId = '1911e8a4-6939-490c-b58b-a5d70f8d91fb_TotalVisits' ;
347+ spyOn ( component , 'getCategoryId' ) . and . returnValue ( of ( 'category1' ) ) ;
348+ spyOn ( component , 'setStatisticsState' ) ;
349+
350+ component . changeReport ( reportId ) ;
351+
352+ ( component as any ) . selectedFullReport$ . subscribe ( ( report : UsageReport ) => {
353+ if ( report ) {
354+ expect ( component . setStatisticsState ) . toHaveBeenCalledWith ( reportId , 'category1' ) ;
355+ expect ( component . selectedReportId ) . toBe ( reportId ) ;
356+ expect ( report . id ) . toBe ( reportId ) ;
357+ done ( ) ;
358+ }
359+ } ) ;
360+ } ) ;
361+
362+ it ( 'should emit null on selectedFullReport$ before loading the new report' , ( ) => {
363+ spyOn ( component , 'getCategoryId' ) . and . returnValue ( of ( 'category1' ) ) ;
364+ spyOn ( component , 'setStatisticsState' ) ;
365+
366+ const emitted : ( UsageReport | null ) [ ] = [ ] ;
367+ ( component as any ) . selectedFullReport$ . subscribe ( ( value ) => emitted . push ( value ) ) ;
368+
369+ component . changeReport ( '1911e8a4-6939-490c-b58b-a5d70f8d91fb_TotalVisits' ) ;
370+
371+ expect ( emitted [ 0 ] ) . toBeNull ( ) ;
372+ expect ( emitted [ 1 ] ) . toBeNull ( ) ;
373+ } ) ;
280374
281- expect ( component . setStatisticsState ) . toHaveBeenCalledWith ( 'report1' , 'category1' ) ;
282375 } ) ;
283376
284- it ( 'should handle null category gracefully' , ( ) => {
285- spyOn ( component , 'getReports$' ) . and . returnValue ( of ( [ ] ) ) ;
286- spyOn ( component , 'getReportId' ) . and . returnValue ( of ( null ) ) ;
287- spyOn ( component , 'getCategoryId' ) . and . returnValue ( of ( null ) ) ;
288- spyOn ( component , 'setStatisticsState' ) ;
377+ describe ( 'clearFullReportCache' , ( ) => {
289378
290- component . getUserReports ( null ) ;
379+ it ( 'should clear caches, reset currentReportId and emit null on selectedFullReport$' , ( ) => {
380+ ( component as any ) . fullReportCache . set ( 'r1' , { id : 'r1' } as UsageReport ) ;
381+ ( component as any ) . inFlightRequests . set ( 'r1' , of ( { id : 'r1' } as UsageReport ) ) ;
382+ ( component as any ) . currentReportId = 'r1' ;
383+
384+ ( component as any ) . clearFullReportCache ( ) ;
385+
386+ expect ( ( component as any ) . fullReportCache . size ) . toBe ( 0 ) ;
387+ expect ( ( component as any ) . inFlightRequests . size ) . toBe ( 0 ) ;
388+ expect ( ( component as any ) . currentReportId ) . toBeNull ( ) ;
389+ expect ( ( component as any ) . selectedFullReport$ . value ) . toBeNull ( ) ;
390+ } ) ;
291391
292- expect ( component . setStatisticsState ) . not . toHaveBeenCalled ( ) ;
293- expect ( component . selectedReportId ) . toBeUndefined ( ) ;
294392 } ) ;
295393
296394} ) ;
0 commit comments