@@ -33,7 +33,7 @@ describe("AnalyticsDashboardController", () => {
3333 expect ( controller . state . connection ) . toBe ( "11111111-1111-1111-1111-111111111111" ) ;
3434 } ) ;
3535
36- it ( "does not retain reports from the previous connection when the next one fails " , async ( ) => {
36+ it ( "does not request or retain reports for an unconfigured connection" , async ( ) => {
3737 const api = dashboardApi ( ) ;
3838 api . connections . mockResolvedValue ( ok ( {
3939 enabled : true ,
@@ -43,25 +43,20 @@ describe("AnalyticsDashboardController", () => {
4343 { key : "22222222-2222-2222-2222-222222222222" , displayName : "Unconfigured" , provider : "Vercel" , capabilities : fullCapabilities , isDefault : false , isConfigured : false , baseUrl : undefined , warnings : [ "No server-side access token is configured for this connection." ] } ,
4444 ] ,
4545 } ) ) ;
46- const nextEvents = deferred < Awaited < ReturnType < DashboardApi [ "events" ] > > > ( ) ;
47- api . events
48- . mockResolvedValueOnce ( ok ( { rows : [ { eventName : "Demo event" , visitors : 10 , count : 12 } ] } ) )
49- . mockReturnValueOnce ( nextEvents . promise ) ;
46+ api . events . mockResolvedValueOnce ( ok ( { rows : [ { eventName : "Demo event" , visitors : 10 , count : 12 } ] } ) ) ;
5047 const controller = new AnalyticsDashboardController ( vi . fn ( ) , api , environment ( ) ) ;
5148
5249 controller . connect ( ) ;
5350 await vi . waitFor ( ( ) => expect ( controller . state . events . status ) . toBe ( "success" ) ) ;
5451 controller . setConnection ( "22222222-2222-2222-2222-222222222222" ) ;
5552
56- expect ( controller . state . events ) . toEqual ( { status : "loading" } ) ;
57-
58- nextEvents . resolve ( {
59- data : undefined ,
60- error : new Error ( "No access token" ) ,
61- response : new Response ( null , { status : 401 } ) ,
62- } ) ;
63- await vi . waitFor ( ( ) => expect ( controller . state . events . status ) . toBe ( "error" ) ) ;
64- expect ( "previous" in controller . state . events ) . toBe ( false ) ;
53+ expect ( controller . state . summary ) . toEqual ( { status : "idle" } ) ;
54+ expect ( controller . state . breakdowns ) . toEqual ( { } ) ;
55+ expect ( controller . state . events ) . toEqual ( { status : "idle" } ) ;
56+ expect ( controller . state . flags ) . toEqual ( { status : "idle" } ) ;
57+ expect ( api . summary ) . toHaveBeenCalledTimes ( 1 ) ;
58+ expect ( api . events ) . toHaveBeenCalledTimes ( 1 ) ;
59+ expect ( api . flags ) . toHaveBeenCalledTimes ( 1 ) ;
6560 } ) ;
6661
6762 it ( "keeps the newest document scope when an older route request finishes last" , async ( ) => {
@@ -115,6 +110,84 @@ describe("AnalyticsDashboardController", () => {
115110 expect ( controller . state . expandedBreakdown ) . toBeUndefined ( ) ;
116111 } ) ;
117112
113+ it ( "stores only canonical breakdown query state for a grouped dialog" , async ( ) => {
114+ const api = dashboardApi ( ) ;
115+ const controller = new AnalyticsDashboardController ( vi . fn ( ) , api , environment ( ) ) ;
116+ controller . connect ( ) ;
117+ await vi . waitFor ( ( ) => expect ( controller . state . summary . status ) . toBe ( "success" ) ) ;
118+ await controller . openBreakdown ( "UtmCampaign" , "UTM campaigns" ) ;
119+
120+ expect ( controller . state . expandedBreakdown ) . toMatchObject ( {
121+ dimension : "UtmCampaign" ,
122+ headline : "UTM campaigns" ,
123+ search : "" ,
124+ report : { status : "success" } ,
125+ } ) ;
126+ } ) ;
127+
128+ it ( "does not reuse rows from a different breakdown query" , async ( ) => {
129+ const api = dashboardApi ( ) ;
130+ const controller = new AnalyticsDashboardController ( vi . fn ( ) , api , environment ( ) ) ;
131+ controller . connect ( ) ;
132+ await vi . waitFor ( ( ) => expect ( controller . state . summary . status ) . toBe ( "success" ) ) ;
133+ api . breakdown . mockResolvedValueOnce ( ok ( {
134+ dimension : "ReferrerHostname" ,
135+ rows : [ { value : "example.com" , visitors : 12 , pageViews : 18 } ] ,
136+ } ) ) ;
137+ await controller . openBreakdown ( "ReferrerHostname" , "Referrers" ) ;
138+ const pending = deferred < ReturnType < typeof ok < { dimension : "UtmSource" ; rows : never [ ] } > > > ( ) ;
139+ api . breakdown . mockReturnValueOnce ( pending . promise ) ;
140+
141+ const switching = controller . openBreakdown ( "UtmSource" , "UTM sources" ) ;
142+
143+ expect ( controller . state . expandedBreakdown ?. report ) . toEqual ( { status : "loading" } ) ;
144+ pending . resolve ( ok ( { dimension : "UtmSource" , rows : [ ] } ) ) ;
145+ await switching ;
146+ } ) ;
147+
148+ it ( "retains rows while the same breakdown query refreshes" , async ( ) => {
149+ const api = dashboardApi ( ) ;
150+ const controller = new AnalyticsDashboardController ( vi . fn ( ) , api , environment ( ) ) ;
151+ controller . connect ( ) ;
152+ await vi . waitFor ( ( ) => expect ( controller . state . summary . status ) . toBe ( "success" ) ) ;
153+ api . breakdown . mockResolvedValueOnce ( ok ( {
154+ dimension : "Country" ,
155+ rows : [ { value : "DK" , visitors : 12 , pageViews : 18 } ] ,
156+ } ) ) ;
157+ await controller . openBreakdown ( "Country" , "Countries" ) ;
158+ const pending = deferred < ReturnType < typeof ok < { dimension : "Country" ; rows : never [ ] } > > > ( ) ;
159+ api . breakdown . mockReturnValueOnce ( pending . promise ) ;
160+
161+ const refreshing = controller . openBreakdown ( "Country" , "Countries" ) ;
162+
163+ expect ( controller . state . expandedBreakdown ?. report ) . toEqual ( {
164+ status : "loading" ,
165+ previous : [ { value : "DK" , visitors : 12 , pageViews : 18 } ] ,
166+ } ) ;
167+ pending . resolve ( ok ( { dimension : "Country" , rows : [ ] } ) ) ;
168+ await refreshing ;
169+ } ) ;
170+
171+ it ( "does not reuse unfiltered rows for a searched breakdown" , async ( ) => {
172+ const api = dashboardApi ( ) ;
173+ const controller = new AnalyticsDashboardController ( vi . fn ( ) , api , environment ( ) ) ;
174+ controller . connect ( ) ;
175+ await vi . waitFor ( ( ) => expect ( controller . state . summary . status ) . toBe ( "success" ) ) ;
176+ api . breakdown . mockResolvedValueOnce ( ok ( {
177+ dimension : "Country" ,
178+ rows : [ { value : "DK" , visitors : 12 , pageViews : 18 } ] ,
179+ } ) ) ;
180+ await controller . openBreakdown ( "Country" , "Countries" ) ;
181+ const pending = deferred < ReturnType < typeof ok < { dimension : "Country" ; rows : never [ ] } > > > ( ) ;
182+ api . breakdown . mockReturnValueOnce ( pending . promise ) ;
183+
184+ const searching = controller . openBreakdown ( "Country" , "Countries" , { search : "denmark" } ) ;
185+
186+ expect ( controller . state . expandedBreakdown ?. report ) . toEqual ( { status : "loading" } ) ;
187+ pending . resolve ( ok ( { dimension : "Country" , rows : [ ] } ) ) ;
188+ await searching ;
189+ } ) ;
190+
118191 it ( "does not restore event details after the dialog closes during a request" , async ( ) => {
119192 const api = dashboardApi ( ) ;
120193 const controller = new AnalyticsDashboardController ( vi . fn ( ) , api , environment ( ) ) ;
@@ -448,6 +521,41 @@ describe("AnalyticsDashboardController", () => {
448521 expect ( controller . cards ( ) . some ( ( card ) => card . kind === "tabbed-breakdown" && card . id === "utm" ) ) . toBe ( true ) ;
449522 } ) ;
450523
524+ it ( "does not request optional reports disabled for the connection" , async ( ) => {
525+ const api = dashboardApi ( ) ;
526+ api . connections . mockResolvedValue ( ok ( {
527+ enabled : true ,
528+ defaultRangeDays : 30 ,
529+ connections : [ {
530+ key : "11111111-1111-1111-1111-111111111111" ,
531+ displayName : "Traffic only" ,
532+ provider : "Vercel" ,
533+ capabilities : {
534+ ...fullCapabilities ,
535+ dimensions : fullCapabilities . dimensions . filter ( ( dimension ) => dimension !== "EventName" ) ,
536+ events : false ,
537+ eventDetails : false ,
538+ eventProperties : false ,
539+ globalEventFiltering : false ,
540+ flags : false ,
541+ } ,
542+ isDefault : true ,
543+ isConfigured : true ,
544+ baseUrl : "https://example.com" ,
545+ warnings : [ ] ,
546+ } ] ,
547+ } ) ) ;
548+ const controller = new AnalyticsDashboardController ( vi . fn ( ) , api , environment ( ) ) ;
549+
550+ controller . connect ( ) ;
551+
552+ await vi . waitFor ( ( ) => expect ( controller . state . summary . status ) . toBe ( "success" ) ) ;
553+ expect ( api . events ) . not . toHaveBeenCalled ( ) ;
554+ expect ( api . flags ) . not . toHaveBeenCalled ( ) ;
555+ expect ( controller . state . events . status ) . toBe ( "idle" ) ;
556+ expect ( controller . state . flags . status ) . toBe ( "idle" ) ;
557+ } ) ;
558+
451559 it ( "does not create a global event filter for providers that do not support it" , async ( ) => {
452560 const api = dashboardApi ( ) ;
453561 const controller = new AnalyticsDashboardController ( vi . fn ( ) , api , environment ( ) ) ;
0 commit comments