@@ -7,6 +7,7 @@ import { triggerResize, clearResizeObservers } from '../test/setup';
77// These match the mock in test/setup.ts
88const CONTAINER_WIDTH = 1024 ;
99const CONTAINER_HEIGHT = 768 ;
10+ const DEFAULT_DIVIDER_SIZE = 1 ;
1011
1112describe ( 'SplitPane' , ( ) => {
1213 beforeEach ( ( ) => {
@@ -122,10 +123,14 @@ describe('SplitPane initial size calculation', () => {
122123 const panes = container . querySelectorAll ( '[data-pane="true"]' ) ;
123124 expect ( panes ) . toHaveLength ( 2 ) ;
124125
125- // First pane: 30% of 1024 = 307.2px
126- expect ( panes [ 0 ] ) . toHaveStyle ( { width : `${ CONTAINER_WIDTH * 0.3 } px` } ) ;
127- // Second pane: remaining 70% = 716.8px
128- expect ( panes [ 1 ] ) . toHaveStyle ( { width : `${ CONTAINER_WIDTH * 0.7 } px` } ) ;
126+ // Available space = container - divider = 1024 - 1 = 1023px
127+ const availableWidth = CONTAINER_WIDTH - DEFAULT_DIVIDER_SIZE ;
128+ // First pane: 30% of available space (306.9px)
129+ const pane1Width = parseFloat ( ( panes [ 0 ] as HTMLElement ) . style . width ) ;
130+ expect ( pane1Width ) . toBeCloseTo ( availableWidth * 0.3 , 1 ) ;
131+ // Second pane: remaining 70% of available space (716.1px)
132+ const pane2Width = parseFloat ( ( panes [ 1 ] as HTMLElement ) . style . width ) ;
133+ expect ( pane2Width ) . toBeCloseTo ( availableWidth * 0.7 , 1 ) ;
129134 } ) ;
130135
131136 it ( 'distributes remaining space equally among multiple auto-sized panes' , async ( ) => {
@@ -144,10 +149,12 @@ describe('SplitPane initial size calculation', () => {
144149 const panes = container . querySelectorAll ( '[data-pane="true"]' ) ;
145150 expect ( panes ) . toHaveLength ( 3 ) ;
146151
152+ // Available space = container - (2 dividers * 1px) = 1024 - 2 = 1022px
153+ const availableWidth = CONTAINER_WIDTH - DEFAULT_DIVIDER_SIZE * 2 ;
147154 // First pane: 200px fixed
148155 expect ( panes [ 0 ] ) . toHaveStyle ( { width : '200px' } ) ;
149- // Remaining (1024 - 200) = 824px split equally: 412px each
150- const remainingEach = ( CONTAINER_WIDTH - 200 ) / 2 ;
156+ // Remaining (1022 - 200) = 822px split equally: 411px each
157+ const remainingEach = ( availableWidth - 200 ) / 2 ;
151158 expect ( panes [ 1 ] ) . toHaveStyle ( { width : `${ remainingEach } px` } ) ;
152159 expect ( panes [ 2 ] ) . toHaveStyle ( { width : `${ remainingEach } px` } ) ;
153160 } ) ;
@@ -167,8 +174,10 @@ describe('SplitPane initial size calculation', () => {
167174 const panes = container . querySelectorAll ( '[data-pane="true"]' ) ;
168175 expect ( panes ) . toHaveLength ( 2 ) ;
169176
170- // Each pane gets 50% = 512px
171- const halfWidth = CONTAINER_WIDTH / 2 ;
177+ // Available space = container - divider = 1024 - 1 = 1023px
178+ // Each pane gets 50% = 511.5px
179+ const availableWidth = CONTAINER_WIDTH - DEFAULT_DIVIDER_SIZE ;
180+ const halfWidth = availableWidth / 2 ;
172181 expect ( panes [ 0 ] ) . toHaveStyle ( { width : `${ halfWidth } px` } ) ;
173182 expect ( panes [ 1 ] ) . toHaveStyle ( { width : `${ halfWidth } px` } ) ;
174183 } ) ;
@@ -189,12 +198,14 @@ describe('SplitPane initial size calculation', () => {
189198 const panes = container . querySelectorAll ( '[data-pane="true"]' ) ;
190199 expect ( panes ) . toHaveLength ( 3 ) ;
191200
192- // First two panes: 25% each = 256px
193- const quarterWidth = CONTAINER_WIDTH * 0.25 ;
201+ // Available space = container - (2 dividers * 1px) = 1024 - 2 = 1022px
202+ const availableWidth = CONTAINER_WIDTH - DEFAULT_DIVIDER_SIZE * 2 ;
203+ // First two panes: 25% each of available space
204+ const quarterWidth = availableWidth * 0.25 ;
194205 expect ( panes [ 0 ] ) . toHaveStyle ( { width : `${ quarterWidth } px` } ) ;
195206 expect ( panes [ 1 ] ) . toHaveStyle ( { width : `${ quarterWidth } px` } ) ;
196- // Third pane: remaining 50% = 512px
197- expect ( panes [ 2 ] ) . toHaveStyle ( { width : `${ CONTAINER_WIDTH * 0.5 } px` } ) ;
207+ // Third pane: remaining 50% of available space
208+ expect ( panes [ 2 ] ) . toHaveStyle ( { width : `${ availableWidth * 0.5 } px` } ) ;
198209 } ) ;
199210
200211 it ( 'handles vertical direction correctly' , async ( ) => {
@@ -212,10 +223,12 @@ describe('SplitPane initial size calculation', () => {
212223 const panes = container . querySelectorAll ( '[data-pane="true"]' ) ;
213224 expect ( panes ) . toHaveLength ( 2 ) ;
214225
215- // First pane: 25% of 768 = 192px
216- expect ( panes [ 0 ] ) . toHaveStyle ( { height : `${ CONTAINER_HEIGHT * 0.25 } px` } ) ;
217- // Second pane: remaining 75% = 576px
218- expect ( panes [ 1 ] ) . toHaveStyle ( { height : `${ CONTAINER_HEIGHT * 0.75 } px` } ) ;
226+ // Available height = container - divider = 768 - 1 = 767px
227+ const availableHeight = CONTAINER_HEIGHT - DEFAULT_DIVIDER_SIZE ;
228+ // First pane: 25% of available height
229+ expect ( panes [ 0 ] ) . toHaveStyle ( { height : `${ availableHeight * 0.25 } px` } ) ;
230+ // Second pane: remaining 75% of available height
231+ expect ( panes [ 1 ] ) . toHaveStyle ( { height : `${ availableHeight * 0.75 } px` } ) ;
219232 } ) ;
220233
221234 it ( 'respects controlled size prop over defaultSize' , async ( ) => {
@@ -235,10 +248,12 @@ describe('SplitPane initial size calculation', () => {
235248 const panes = container . querySelectorAll ( '[data-pane="true"]' ) ;
236249 expect ( panes ) . toHaveLength ( 2 ) ;
237250
251+ // Available space = container - divider = 1024 - 1 = 1023px
252+ const availableWidth = CONTAINER_WIDTH - DEFAULT_DIVIDER_SIZE ;
238253 // First pane: controlled size of 400px (not 30%)
239254 expect ( panes [ 0 ] ) . toHaveStyle ( { width : '400px' } ) ;
240- // Second pane: remaining 624px
241- expect ( panes [ 1 ] ) . toHaveStyle ( { width : `${ CONTAINER_WIDTH - 400 } px` } ) ;
255+ // Second pane: remaining available space minus first pane
256+ expect ( panes [ 1 ] ) . toHaveStyle ( { width : `${ availableWidth - 400 } px` } ) ;
242257 } ) ;
243258
244259 it ( 'updates pane sizes when controlled size prop changes' , async ( ) => {
@@ -355,19 +370,27 @@ describe('SplitPane container resize behavior', () => {
355370
356371 const panes = container . querySelectorAll ( '[data-pane="true"]' ) ;
357372
358- // Initial: 200px + 824px = 1024px
373+ // Available space = container - divider = 1024 - 1 = 1023px
374+ const initialAvailable = CONTAINER_WIDTH - DEFAULT_DIVIDER_SIZE ;
375+ // Initial: 200px + 823px = 1023px (available space)
359376 expect ( panes [ 0 ] ) . toHaveStyle ( { width : '200px' } ) ;
360- expect ( panes [ 1 ] ) . toHaveStyle ( { width : `${ CONTAINER_WIDTH - 200 } px` } ) ;
377+ expect ( panes [ 1 ] ) . toHaveStyle ( { width : `${ initialAvailable - 200 } px` } ) ;
361378
362379 // Simulate container resize to 2048px (double)
363380 await act ( async ( ) => {
364381 triggerResize ( 2048 , 768 ) ;
365382 await vi . runAllTimersAsync ( ) ;
366383 } ) ;
367384
385+ // Available space at new size = 2048 - 1 = 2047px
386+ const newAvailable = 2048 - DEFAULT_DIVIDER_SIZE ;
368387 // Uncontrolled panes should scale proportionally
369- expect ( panes [ 0 ] ) . toHaveStyle ( { width : '400px' } ) ;
370- expect ( panes [ 1 ] ) . toHaveStyle ( { width : `${ 2048 - 400 } px` } ) ;
388+ // Original ratio: 200/1023 and 823/1023
389+ const expectedPane1 = ( 200 / initialAvailable ) * newAvailable ;
390+ const expectedPane2 =
391+ ( ( initialAvailable - 200 ) / initialAvailable ) * newAvailable ;
392+ expect ( panes [ 0 ] ) . toHaveStyle ( { width : `${ expectedPane1 } px` } ) ;
393+ expect ( panes [ 1 ] ) . toHaveStyle ( { width : `${ expectedPane2 } px` } ) ;
371394 } ) ;
372395
373396 it ( 'maintains controlled sizes in vertical direction on container resize' , async ( ) => {
@@ -398,3 +421,101 @@ describe('SplitPane container resize behavior', () => {
398421 expect ( panes [ 1 ] ) . toHaveStyle ( { height : '300px' } ) ;
399422 } ) ;
400423} ) ;
424+
425+ describe ( 'SplitPane divider size accounting' , ( ) => {
426+ beforeEach ( ( ) => {
427+ clearResizeObservers ( ) ;
428+ } ) ;
429+
430+ it ( 'accounts for divider width when calculating percentage-based pane sizes' , async ( ) => {
431+ // Custom divider with a known width
432+ const DividerWithWidth = ( props : {
433+ direction : string ;
434+ onMouseDown ?: ( ) => void ;
435+ onTouchStart ?: ( ) => void ;
436+ onTouchEnd ?: ( ) => void ;
437+ onKeyDown ?: ( ) => void ;
438+ } ) => (
439+ < div
440+ role = "separator"
441+ style = { { width : props . direction === 'horizontal' ? '10px' : undefined } }
442+ data-testid = "divider"
443+ onMouseDown = { props . onMouseDown }
444+ onTouchStart = { props . onTouchStart }
445+ onTouchEnd = { props . onTouchEnd }
446+ onKeyDown = { props . onKeyDown }
447+ />
448+ ) ;
449+
450+ const { container } = render (
451+ < SplitPane
452+ direction = "horizontal"
453+ divider = { DividerWithWidth }
454+ dividerSize = { 10 }
455+ >
456+ < Pane defaultSize = "33%" > Pane 1</ Pane >
457+ < Pane defaultSize = "34%" > Pane 2</ Pane >
458+ < Pane defaultSize = "33%" > Pane 3</ Pane >
459+ </ SplitPane >
460+ ) ;
461+
462+ await act ( async ( ) => {
463+ await vi . runAllTimersAsync ( ) ;
464+ } ) ;
465+
466+ const panes = container . querySelectorAll ( '[data-pane="true"]' ) ;
467+ expect ( panes ) . toHaveLength ( 3 ) ;
468+
469+ // Get actual pane widths
470+ const pane1Width = parseFloat (
471+ ( panes [ 0 ] as HTMLElement ) . style . width . replace ( 'px' , '' )
472+ ) ;
473+ const pane2Width = parseFloat (
474+ ( panes [ 1 ] as HTMLElement ) . style . width . replace ( 'px' , '' )
475+ ) ;
476+ const pane3Width = parseFloat (
477+ ( panes [ 2 ] as HTMLElement ) . style . width . replace ( 'px' , '' )
478+ ) ;
479+
480+ // With 2 dividers at 10px each, available space for panes is 1024 - 20 = 1004px
481+ // Currently the bug causes panes to total 100% of 1024 = 1024px (overflow!)
482+ // After fix: panes should total 1004px (accounting for divider widths)
483+ const totalPaneWidth = pane1Width + pane2Width + pane3Width ;
484+ const dividerWidth = 10 ;
485+ const dividerCount = 2 ;
486+ const expectedTotalPaneWidth =
487+ CONTAINER_WIDTH - dividerWidth * dividerCount ;
488+
489+ // The total pane width should not exceed container minus dividers
490+ expect ( totalPaneWidth ) . toBeLessThanOrEqual ( CONTAINER_WIDTH ) ;
491+ expect ( totalPaneWidth ) . toBeCloseTo ( expectedTotalPaneWidth , 0 ) ;
492+ } ) ;
493+
494+ it ( 'accounts for default 1px divider width in size calculations' , async ( ) => {
495+ const { container } = render (
496+ < SplitPane direction = "horizontal" >
497+ < Pane defaultSize = "50%" > Pane 1</ Pane >
498+ < Pane defaultSize = "50%" > Pane 2</ Pane >
499+ </ SplitPane >
500+ ) ;
501+
502+ await act ( async ( ) => {
503+ await vi . runAllTimersAsync ( ) ;
504+ } ) ;
505+
506+ const panes = container . querySelectorAll ( '[data-pane="true"]' ) ;
507+ const pane1Width = parseFloat (
508+ ( panes [ 0 ] as HTMLElement ) . style . width . replace ( 'px' , '' )
509+ ) ;
510+ const pane2Width = parseFloat (
511+ ( panes [ 1 ] as HTMLElement ) . style . width . replace ( 'px' , '' )
512+ ) ;
513+
514+ // With 1 divider at 1px, available space is 1024 - 1 = 1023px
515+ // Each pane should be 50% of 1023 = 511.5px
516+ const totalPaneWidth = pane1Width + pane2Width ;
517+ const expectedTotal = CONTAINER_WIDTH - 1 ; // minus 1px divider
518+
519+ expect ( totalPaneWidth ) . toBeCloseTo ( expectedTotal , 0 ) ;
520+ } ) ;
521+ } ) ;
0 commit comments