@@ -4,142 +4,63 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
44import { NuqsTestingAdapter } from 'nuqs/adapters/testing'
55import { createRoot , type Root } from 'react-dom/client'
66import { afterEach , beforeEach , describe , expect , it , vi } from 'vitest'
7- import { useDashboardInteractions } from '@/components/dashboards/dashboard-interactions'
87import { DashboardPreview } from '@/components/dashboards/dashboard-preview'
98import { tableAnalyticsKeys } from '@/hooks/queries/table-analytics'
109
11- const mocks = vi . hoisted ( ( ) => ( {
12- controls : vi . fn ( ) ,
13- layout : vi . fn ( ) ,
14- url : vi . fn ( ) ,
15- timezone : vi . fn ( ) ,
16- } ) )
17- vi . mock ( '@/lib/core/utils/timezone' , async ( importOriginal ) => ( {
18- ...( await importOriginal < typeof import ( '@/lib/core/utils/timezone' ) > ( ) ) ,
19- getBrowserTimezone : mocks . timezone ,
20- } ) )
21- vi . mock ( '@/components/dashboards/dashboard-controls' , ( ) => ( {
22- DashboardControls : ( props : unknown ) => {
23- mocks . controls ( props )
24- return null
25- } ,
26- } ) )
27- vi . mock ( '@/components/dashboards/dashboard-layout' , ( ) => ( {
28- DashboardLayout : ( props : unknown ) => {
29- mocks . layout ( props , useDashboardInteractions ( ) )
30- return null
31- } ,
32- } ) )
10+ /** Panels would issue analytics requests; this suite exercises the controls and query cache. */
11+ vi . mock ( '@/components/dashboards/dashboard-layout' , ( ) => ( { DashboardLayout : ( ) => null } ) )
12+
3313const content =
3414 'title: Example\ntime: 7d\nsource: {tableId: table-1}\nblocks: [{stat: Total, source: {aggregate: {total: {op: count}}}}]'
35- const zoom = { from : '2026-09-20T17:26:54.766Z ' , to : '2026-09-21T03:49:57.697Z ' }
15+ const range = { from : '2026-09-17T00:00:00.000Z ' , to : '2026-09-24T00:00:00.000Z ' }
3616
37- describe ( 'dashboard view state ' , ( ) => {
17+ describe ( 'dashboard refresh ' , ( ) => {
3818 let root : Root
19+ let container : HTMLDivElement
3920 let client : QueryClient
4021 beforeEach ( ( ) => {
41- mocks . timezone . mockReturnValue ( 'America/Los_Angeles' )
4222 vi . stubGlobal ( 'IS_REACT_ACT_ENVIRONMENT' , true )
4323 client = new QueryClient ( )
44- root = createRoot ( document . createElement ( 'div' ) )
24+ container = document . createElement ( 'div' )
25+ document . body . append ( container )
26+ root = createRoot ( container )
4527 } )
4628 afterEach ( ( ) => {
4729 act ( ( ) => root . unmount ( ) )
30+ container . remove ( )
4831 client . clear ( )
49- vi . unstubAllGlobals ( )
5032 } )
51- async function render ( searchParams = '' ) {
52- await act ( async ( ) =>
53- root . render (
54- < QueryClientProvider client = { client } >
55- < NuqsTestingAdapter hasMemory searchParams = { searchParams } onUrlUpdate = { mocks . url } >
56- < DashboardPreview content = { content } workspaceId = 'workspace-1' fileId = 'example' />
57- </ NuqsTestingAdapter >
58- </ QueryClientProvider >
59- )
60- )
61- }
62- const controls = ( ) => mocks . controls . mock . lastCall ! [ 0 ]
63- const layout = ( ) => mocks . layout . mock . lastCall ! [ 0 ]
64- const interactions = ( ) => mocks . layout . mock . lastCall ! [ 1 ]
6533
66- it . each ( [ 'America/Los_Angeles' , 'Asia/Tokyo' ] ) (
67- 'defaults to the viewer timezone %s and allows UTC without changing the interval' ,
68- async ( timezone ) => {
69- mocks . timezone . mockReturnValue ( timezone )
70- await render ( )
71- expect ( controls ( ) . zone ) . toBe ( 'local' )
72- expect ( interactions ( ) . timeZone ) . toBe ( timezone )
73- const original = layout ( ) . range
74- await act ( async ( ) => controls ( ) . onZoneChange ( 'utc' ) )
75- expect ( interactions ( ) . timeZone ) . toBe ( 'UTC' )
76- expect ( layout ( ) . range ) . toEqual ( original )
77- await vi . waitFor ( ( ) => {
78- expect ( mocks . url . mock . lastCall ! [ 0 ] . searchParams . get ( 'dash-example-zone' ) ) . toBe ( 'utc' )
79- } )
80- await act ( async ( ) => controls ( ) . onZoneChange ( 'local' ) )
81- expect ( interactions ( ) . timeZone ) . toBe ( timezone )
82- await vi . waitFor ( ( ) => {
83- expect ( mocks . url . mock . lastCall ! [ 0 ] . searchParams . has ( 'dash-example-zone' ) ) . toBe ( false )
84- } )
85- }
86- )
87-
88- it ( 'sets exact shared bounds without navigation and leaves zoom by choosing a preset' , async ( ) => {
89- await render ( )
90- await act ( async ( ) => interactions ( ) . onZoom ( zoom ) )
91- expect ( layout ( ) . range ) . toEqual ( zoom )
92- expect ( controls ( ) . period ) . toBe ( 'custom' )
93- await vi . waitFor ( ( ) => expect ( mocks . url . mock . lastCall ?. [ 0 ] . options . shallow ) . toBe ( true ) )
94- await act ( async ( ) => controls ( ) . onPeriodChange ( '7d' ) )
95- expect ( controls ( ) . period ) . toBe ( '7d' )
96- expect ( Date . parse ( layout ( ) . range . to ) - Date . parse ( layout ( ) . range . from ) ) . toBe ( 7 * 86400000 )
97- } )
98- it ( 'commits the calendar dates and custom mode together, preserving the view on invalid input' , async ( ) => {
99- await render ( '?dash-example-range=30d&dash-example-zone=utc' )
100- expect ( controls ( ) . zone ) . toBe ( 'utc' )
101- await act ( async ( ) => {
102- expect ( controls ( ) . onCalendarChange ( '2026-09-17T00:00' , '2026-09-23T23:59:59' ) ) . toBe ( true )
103- } )
104- expect ( controls ( ) . period ) . toBe ( 'custom' )
105- expect ( layout ( ) . range ) . toEqual ( {
106- from : '2026-09-17T00:00:00.000Z' ,
107- to : '2026-09-24T00:00:00.000Z' ,
108- } )
109- const selected = layout ( ) . range
110- await act ( async ( ) => {
111- expect ( controls ( ) . onCalendarChange ( 'invalid' , 'invalid' ) ) . toBe ( false )
112- } )
113- expect ( layout ( ) . range ) . toEqual ( selected )
114- } )
115- it ( 'keeps exact bounds through repeated zooms and timezone changes' , async ( ) => {
116- await render (
117- '?dash-example-range=custom&dash-example-from=2026-09-17T00:00:00&dash-example-to=2026-09-24T00:00:00'
118- )
119- await act ( async ( ) => interactions ( ) . onZoom ( zoom ) )
120- const selected = { ...zoom , to : '2026-09-20T18:00:00.000Z' }
121- await act ( async ( ) => interactions ( ) . onZoom ( selected ) )
122- expect ( layout ( ) . range ) . toEqual ( selected )
123- await act ( async ( ) => controls ( ) . onZoneChange ( 'utc' ) )
124- expect ( layout ( ) . range ) . toEqual ( selected )
125- expect ( controls ( ) . zone ) . toBe ( 'utc' )
126- } )
127- it ( 'refreshes custom-range queries only for this workspace and this dashboard tables' , async ( ) => {
34+ it ( 'refreshes only this workspace and this dashboard tables' , async ( ) => {
12835 const key = ( workspaceId : string , tableId : string ) =>
12936 tableAnalyticsKeys . query ( tableId , {
13037 workspaceId,
131- query : { ...zoom , aggregate : { total : { op : 'count' } } } ,
38+ query : { ...range , aggregate : { total : { op : 'count' } } } ,
13239 } )
13340 const matching = key ( 'workspace-1' , 'table-1' )
13441 const otherTable = key ( 'workspace-1' , 'other-table' )
13542 const otherWorkspace = key ( 'workspace-2' , 'table-1' )
13643 for ( const queryKey of [ matching , otherTable , otherWorkspace ] ) client . setQueryData ( queryKey , { } )
137- await render (
138- '?dash-example-range=custom&dash-example-from=2026-09-17T00:00:00&dash-example-to=2026-09-24T00:00:00'
44+ await act ( async ( ) =>
45+ root . render (
46+ < QueryClientProvider client = { client } >
47+ < NuqsTestingAdapter
48+ hasMemory
49+ searchParams = '?dash-example-range=custom& dash-example-from = 2026 - 09 - 17 T00 :00 :00 & dash-example-to = 2026 - 09 - 24 T00 :00 :00 & dash-example-zone = utc '
50+ >
51+ <DashboardPreview content={content} workspaceId=' workspace-1 ' fileId = 'example' />
52+ </ NuqsTestingAdapter >
53+ < / Q u e r y C l i e n t P r o v i d e r >
54+ )
13955 )
140- const original = layout ( ) . range
141- await act ( async ( ) => controls ( ) . onRefresh ( ) )
142- expect ( layout ( ) . range ) . toEqual ( original )
56+ const refresh = await vi . waitFor ( ( ) => {
57+ const button = container . querySelector < HTMLButtonElement > (
58+ 'button[aria-label="Refresh dashboard"]'
59+ )
60+ if ( ! button ) throw new Error ( 'Refresh button not rendered' )
61+ return button
62+ } )
63+ await act ( async ( ) => refresh . click ( ) )
14364 expect ( client . getQueryState ( matching ) ?. isInvalidated ) . toBe ( true )
14465 expect ( client . getQueryState ( otherTable ) ?. isInvalidated ) . toBe ( false )
14566 expect ( client . getQueryState ( otherWorkspace ) ?. isInvalidated ) . toBe ( false )
0 commit comments