@@ -78,25 +78,43 @@ beforeEach(() => {
7878afterEach ( ( ) => {
7979 act ( ( ) => root . unmount ( ) )
8080 container . remove ( )
81+ vi . restoreAllMocks ( )
82+ vi . unstubAllGlobals ( )
8183} )
8284
8385function renderPreview ( ) {
86+ const preview = (
87+ < table >
88+ < tbody >
89+ < ReferenceRowPreview
90+ workspaceId = 'workspace-1'
91+ referenceTableId = 'table-accounts'
92+ referenceRowId = 'row-account-1'
93+ colSpan = { 3 }
94+ />
95+ </ tbody >
96+ </ table >
97+ )
98+
8499 act ( ( ) => {
85- root . render (
86- < table >
87- < tbody >
88- < ReferenceRowPreview
89- workspaceId = 'workspace-1'
90- referenceTableId = 'table-accounts'
91- referenceRowId = 'row-account-1'
92- colSpan = { 3 }
93- />
94- </ tbody >
95- </ table >
96- )
100+ root . render ( < div data-table-scroll > { preview } </ div > )
97101 } )
98102}
99103
104+ function horizontalRect ( left : number , right : number ) : DOMRect {
105+ return {
106+ bottom : 0 ,
107+ height : 0 ,
108+ left,
109+ right,
110+ top : 0 ,
111+ width : right - left ,
112+ x : left ,
113+ y : 0 ,
114+ toJSON : ( ) => ( { } ) ,
115+ }
116+ }
117+
100118describe ( 'ReferenceRowPreview' , ( ) => {
101119 it ( 'shows the referenced table schema and the matching row inline' , ( ) => {
102120 renderPreview ( )
@@ -120,17 +138,133 @@ describe('ReferenceRowPreview', () => {
120138 expect ( container . querySelector ( 'td > div' ) ?. className ) . toContain (
121139 `h-[${ REFERENCE_ROW_PREVIEW_HEIGHT } px]`
122140 )
123- const subtable = container . querySelector ( 'td table' )
124- expect ( subtable ?. className ) . toContain ( 'w-[100cqw]' )
125- expect ( subtable ?. className ) . toContain ( 'border-t' )
126- expect ( subtable ?. className ) . toContain ( 'border-b' )
127- expect ( subtable ?. querySelectorAll ( 'col' ) ) . toHaveLength ( 3 )
128- expect ( container . querySelector ( '.overscroll-x-contain' ) ?. className ) . toContain (
129- 'overscroll-x-contain'
130- )
141+ const subtable = container . querySelector ( '[role="table"]' )
142+ expect ( subtable ?. className ) . toContain ( 'w-full' )
143+ expect ( subtable ?. className ) . toContain ( 'h-full' )
144+ expect ( subtable ?. className ) . toContain ( 'grid-rows-2' )
145+ expect ( subtable ?. querySelectorAll ( '[role="row"]' ) ) . toHaveLength ( 2 )
146+ expect ( subtable ?. querySelectorAll ( '[role="columnheader"]' ) ) . toHaveLength ( 2 )
147+ expect ( subtable ?. querySelectorAll ( '[role="cell"]' ) ) . toHaveLength ( 2 )
148+ const subtableViewport = container . querySelector ( '.overscroll-x-contain' )
149+ expect ( subtableViewport ?. className ) . toContain ( 'overflow-x-auto' )
150+ expect ( subtableViewport ?. className ) . toContain ( 'overflow-y-hidden' )
151+ expect ( subtableViewport ?. className ) . toContain ( 'border-y' )
131152 expect ( container . innerHTML ) . not . toContain ( 'rounded-md' )
132153 } )
133154
155+ it ( 'sizes the inner scroller to the visible portion of the preview cell' , ( ) => {
156+ let previewCellRight = 1_500
157+ vi . spyOn ( HTMLElement . prototype , 'getBoundingClientRect' ) . mockImplementation ( function ( ) {
158+ if ( this . matches ( '[data-table-scroll]' ) ) return horizontalRect ( 100 , 920 )
159+ if ( this . matches ( 'tbody > tr > td' ) ) return horizontalRect ( - 500 , previewCellRight )
160+ return horizontalRect ( 0 , 0 )
161+ } )
162+ vi . spyOn ( Element . prototype , 'clientWidth' , 'get' ) . mockImplementation ( function ( ) {
163+ return this . matches ( '[data-table-scroll]' ) ? 800 : 0
164+ } )
165+ renderPreview ( )
166+
167+ const previewShell = container . querySelector < HTMLElement > ( 'tbody > tr > td > div > div' )
168+ expect ( previewShell ?. style . getPropertyValue ( '--reference-preview-width' ) ) . toBe ( '800px' )
169+
170+ previewCellRight = 780
171+ const scrollRoot = container . querySelector < HTMLElement > ( '[data-table-scroll]' )
172+ if ( ! scrollRoot ) throw new Error ( 'Expected the table scroll root to be rendered' )
173+ scrollRoot . scrollLeft = 120
174+ act ( ( ) => {
175+ scrollRoot . dispatchEvent ( new Event ( 'scroll' ) )
176+ } )
177+
178+ expect ( previewShell ?. style . getPropertyValue ( '--reference-preview-width' ) ) . toBe ( '680px' )
179+ } )
180+
181+ it ( 'updates on resize and releases its observer and scroll listener' , ( ) => {
182+ let previewCellRight = 1_500
183+ let resizeCallback : ResizeObserverCallback | null = null
184+ let resizeObserver : ResizeObserver | null = null
185+ const observe = vi . fn ( )
186+ const disconnect = vi . fn ( )
187+
188+ class MockResizeObserver implements ResizeObserver {
189+ constructor ( callback : ResizeObserverCallback ) {
190+ resizeCallback = callback
191+ resizeObserver = this
192+ }
193+
194+ observe ( target : Element , options ?: ResizeObserverOptions ) {
195+ observe ( target , options )
196+ }
197+
198+ unobserve ( ) { }
199+
200+ disconnect ( ) {
201+ disconnect ( )
202+ }
203+ }
204+
205+ vi . stubGlobal ( 'ResizeObserver' , MockResizeObserver )
206+ vi . spyOn ( HTMLElement . prototype , 'getBoundingClientRect' ) . mockImplementation ( function ( ) {
207+ if ( this . matches ( '[data-table-scroll]' ) ) return horizontalRect ( 100 , 900 )
208+ if ( this . matches ( 'tbody > tr > td' ) ) return horizontalRect ( - 500 , previewCellRight )
209+ return horizontalRect ( 0 , 0 )
210+ } )
211+ vi . spyOn ( Element . prototype , 'clientWidth' , 'get' ) . mockImplementation ( function ( ) {
212+ return this . matches ( '[data-table-scroll]' ) ? 800 : 0
213+ } )
214+ const registeredListeners : Array < {
215+ target : EventTarget
216+ type : string
217+ listener : EventListenerOrEventListenerObject | null
218+ } > = [ ]
219+ const removedListeners : typeof registeredListeners = [ ]
220+ const originalAddEventListener = EventTarget . prototype . addEventListener
221+ const originalRemoveEventListener = EventTarget . prototype . removeEventListener
222+ vi . spyOn ( EventTarget . prototype , 'addEventListener' ) . mockImplementation (
223+ function ( type , listener , options ) {
224+ registeredListeners . push ( { target : this , type, listener } )
225+ originalAddEventListener . call ( this , type , listener , options )
226+ }
227+ )
228+ vi . spyOn ( EventTarget . prototype , 'removeEventListener' ) . mockImplementation (
229+ function ( type , listener , options ) {
230+ removedListeners . push ( { target : this , type, listener } )
231+ originalRemoveEventListener . call ( this , type , listener , options )
232+ }
233+ )
234+
235+ renderPreview ( )
236+
237+ const previewShell = container . querySelector < HTMLElement > ( 'tbody > tr > td > div > div' )
238+ const scrollRoot = container . querySelector < HTMLElement > ( '[data-table-scroll]' )
239+ const previewCell = container . querySelector < HTMLElement > ( 'tbody > tr > td' )
240+ if ( ! scrollRoot ) throw new Error ( 'Expected the table scroll root to be rendered' )
241+ if ( ! previewCell ) throw new Error ( 'Expected the preview cell to be rendered' )
242+ const scrollListener = registeredListeners . find (
243+ ( { target, type } ) => target === scrollRoot && type === 'scroll'
244+ ) ?. listener
245+ if ( ! scrollListener ) throw new Error ( 'Expected the scroll listener to be registered' )
246+ expect ( observe ) . toHaveBeenCalledTimes ( 2 )
247+ expect ( observe . mock . calls . some ( ( [ target ] ) => target === scrollRoot ) ) . toBe ( true )
248+ expect ( observe . mock . calls . some ( ( [ target ] ) => target === previewCell ) ) . toBe ( true )
249+
250+ previewCellRight = 780
251+ if ( ! resizeCallback || ! resizeObserver ) {
252+ throw new Error ( 'Expected the resize observer to be initialized' )
253+ }
254+ act ( ( ) => resizeCallback ( [ ] , resizeObserver ) )
255+
256+ expect ( previewShell ?. style . getPropertyValue ( '--reference-preview-width' ) ) . toBe ( '680px' )
257+
258+ act ( ( ) => root . render ( null ) )
259+
260+ expect ( disconnect ) . toHaveBeenCalledOnce ( )
261+ expect ( removedListeners ) . toContainEqual ( {
262+ target : scrollRoot ,
263+ type : 'scroll' ,
264+ listener : scrollListener ,
265+ } )
266+ } )
267+
134268 it ( 'shows no match when the stored row ID does not resolve' , ( ) => {
135269 rowQuery . data = null
136270
0 commit comments