11import {
22 assertActiveTab ,
3- assertActiveTabInNestedPanel ,
43 assertGroupStructure ,
54 assertPanelLayout ,
65 assertTabCount ,
7- assertTabInNestedPanel ,
8- closeMultipleTabs ,
96 findPanelById ,
107 type GroupNode ,
118 getLayout ,
129 getNestedPanel ,
1310 getPanelTree ,
1411 openMultipleFiles ,
15- splitAndAssert ,
16- testSizePreservation ,
1712 withRootGroup ,
1813} from "@test/panelTestHelpers" ;
1914import { beforeEach , describe , expect , it } from "vitest" ;
@@ -44,16 +39,17 @@ describe("panelLayoutStore", () => {
4439
4540 withRootGroup ( "task-1" , ( root : GroupNode ) => {
4641 assertGroupStructure ( root , {
47- direction : "horizontal " ,
42+ direction : "vertical " ,
4843 childCount : 2 ,
4944 sizes : [ 70 , 30 ] ,
5045 } ) ;
5146
5247 assertPanelLayout ( root , [
48+ { panelId : "main-panel" , expectedTabs : [ "logs" ] , activeTab : "logs" } ,
5349 {
54- panelId : "main -panel" ,
55- expectedTabs : [ "logs" , " shell"] ,
56- activeTab : "logs " ,
50+ panelId : "terminal -panel" ,
51+ expectedTabs : [ "shell" ] ,
52+ activeTab : "shell " ,
5753 } ,
5854 ] ) ;
5955 } ) ;
@@ -68,11 +64,11 @@ describe("panelLayoutStore", () => {
6864 it ( "adds file tab to main panel" , ( ) => {
6965 usePanelLayoutStore . getState ( ) . openFile ( "task-1" , "src/App.tsx" ) ;
7066
71- assertTabCount ( getPanelTree ( "task-1" ) , "main-panel" , 3 ) ;
67+ assertTabCount ( getPanelTree ( "task-1" ) , "main-panel" , 2 ) ;
7268 assertPanelLayout ( getPanelTree ( "task-1" ) , [
7369 {
7470 panelId : "main-panel" ,
75- expectedTabs : [ "logs" , "shell" , " file-src/App.tsx"] ,
71+ expectedTabs : [ "logs" , "file-src/App.tsx" ] ,
7672 } ,
7773 ] ) ;
7874 } ) ;
@@ -173,13 +169,15 @@ describe("panelLayoutStore", () => {
173169 assertActiveTab ( getPanelTree ( "task-1" ) , "main-panel" , "file-src/App.tsx" ) ;
174170 } ) ;
175171
176- it ( "falls back to shell when last file tab closed" , ( ) => {
177- closeMultipleTabs ( "task-1" , "main-panel" , [
178- "file-src/App.tsx" ,
179- "file-src/Other.tsx" ,
180- ] ) ;
172+ it ( "falls back to logs when last file tab closed" , ( ) => {
173+ usePanelLayoutStore
174+ . getState ( )
175+ . closeTab ( "task-1" , "main-panel" , "file-src/App.tsx" ) ;
176+ usePanelLayoutStore
177+ . getState ( )
178+ . closeTab ( "task-1" , "main-panel" , "file-src/Other.tsx" ) ;
181179
182- assertActiveTab ( getPanelTree ( "task-1" ) , "main-panel" , "shell " ) ;
180+ assertActiveTab ( getPanelTree ( "task-1" ) , "main-panel" , "logs " ) ;
183181 } ) ;
184182 } ) ;
185183
@@ -222,48 +220,45 @@ describe("panelLayoutStore", () => {
222220 usePanelLayoutStore . getState ( ) . initializeTask ( "task-1" ) ;
223221 } ) ;
224222
225- it (
226- "preserves custom panel sizes when opening a file" ,
227- testSizePreservation ( "opening a file" , ( ) => {
228- openMultipleFiles ( "task-1" , [ "src/App.tsx" ] ) ;
229- } , [ 50 , 50 ] ) ,
230- ) ;
223+ it ( "preserves custom panel sizes when opening a file" , ( ) => {
224+ usePanelLayoutStore
225+ . getState ( )
226+ . updateSizes ( "task-1" , "left-group" , [ 60 , 40 ] ) ;
231227
232- it (
233- "preserves custom panel sizes when switching tabs" ,
234- testSizePreservation ( "switching tabs" , ( ) => {
235- openMultipleFiles ( "task-1" , [ "src/App.tsx" , "src/Other.tsx" ] ) ;
236- usePanelLayoutStore
237- . getState ( )
238- . setActiveTab ( "task-1" , "main-panel" , "file-src/App.tsx" ) ;
239- } , [ 60 , 40 ] ) ,
240- ) ;
228+ openMultipleFiles ( "task-1" , [ "src/App.tsx" ] ) ;
241229
242- it (
243- "preserves custom panel sizes when closing tabs" ,
244- testSizePreservation ( "closing tabs" , ( ) => {
245- openMultipleFiles ( "task-1" , [ "src/App.tsx" , "src/Other.tsx" ] ) ;
246- usePanelLayoutStore
247- . getState ( )
248- . closeTab ( "task-1" , "main-panel" , "file-src/Other.tsx" ) ;
249- } ) ,
250- ) ;
230+ withRootGroup ( "task-1" , ( root ) => {
231+ expect ( root . sizes ) . toEqual ( [ 60 , 40 ] ) ;
232+ } ) ;
233+ } ) ;
251234
252- it (
253- "preserves custom panel sizes in nested groups when splitting panels" ,
254- testSizePreservation ( "splitting panels" , ( ) => {
255- openMultipleFiles ( "task-1" , [ "src/App.tsx" , "src/Other.tsx" ] ) ;
256- usePanelLayoutStore
257- . getState ( )
258- . splitPanel (
259- "task-1" ,
260- "file-src/Other.tsx" ,
261- "main-panel" ,
262- "main-panel" ,
263- "right" ,
264- ) ;
265- } , [ 65 , 35 ] ) ,
266- ) ;
235+ it ( "preserves custom panel sizes when switching tabs" , ( ) => {
236+ usePanelLayoutStore
237+ . getState ( )
238+ . updateSizes ( "task-1" , "left-group" , [ 55 , 45 ] ) ;
239+ openMultipleFiles ( "task-1" , [ "src/App.tsx" , "src/Other.tsx" ] ) ;
240+ usePanelLayoutStore
241+ . getState ( )
242+ . setActiveTab ( "task-1" , "main-panel" , "file-src/App.tsx" ) ;
243+
244+ withRootGroup ( "task-1" , ( root ) => {
245+ expect ( root . sizes ) . toEqual ( [ 55 , 45 ] ) ;
246+ } ) ;
247+ } ) ;
248+
249+ it ( "preserves custom panel sizes when closing tabs" , ( ) => {
250+ usePanelLayoutStore
251+ . getState ( )
252+ . updateSizes ( "task-1" , "left-group" , [ 80 , 20 ] ) ;
253+ openMultipleFiles ( "task-1" , [ "src/App.tsx" , "src/Other.tsx" ] ) ;
254+ usePanelLayoutStore
255+ . getState ( )
256+ . closeTab ( "task-1" , "main-panel" , "file-src/Other.tsx" ) ;
257+
258+ withRootGroup ( "task-1" , ( root ) => {
259+ expect ( root . sizes ) . toEqual ( [ 80 , 20 ] ) ;
260+ } ) ;
261+ } ) ;
267262 } ) ;
268263
269264 describe ( "persistence" , ( ) => {
@@ -352,21 +347,23 @@ describe("panelLayoutStore", () => {
352347 } ) ;
353348
354349 it ( "reorders tabs within a panel" , ( ) => {
355- usePanelLayoutStore . getState ( ) . reorderTabs ( "task-1" , "main-panel" , 2 , 3 ) ;
350+ // tabs: [logs, file-src/App.tsx, file-src/Other.tsx, file-src/Third.tsx]
351+ // move index 1 to index 3
352+ usePanelLayoutStore . getState ( ) . reorderTabs ( "task-1" , "main-panel" , 1 , 3 ) ;
356353
357354 const panel = findPanelById ( getPanelTree ( "task-1" ) , "main-panel" ) ;
358355 const tabIds = panel ?. content . tabs . map ( ( t : { id : string } ) => t . id ) ;
359- expect ( tabIds ?. [ 2 ] ) . toBe ( "file-src/Other.tsx" ) ;
356+ expect ( tabIds ?. [ 1 ] ) . toBe ( "file-src/Other.tsx" ) ;
360357 expect ( tabIds ?. [ 3 ] ) . toBe ( "file-src/App.tsx" ) ;
361358 } ) ;
362359
363360 it ( "preserves active tab after reorder" , ( ) => {
364361 usePanelLayoutStore
365362 . getState ( )
366363 . setActiveTab ( "task-1" , "main-panel" , "file-src/App.tsx" ) ;
367- usePanelLayoutStore . getState ( ) . reorderTabs ( "task-1" , "main-panel" , 0 , 2 ) ;
364+ usePanelLayoutStore . getState ( ) . reorderTabs ( "task-1" , "main-panel" , 1 , 3 ) ;
368365
369- assertActiveTabInNestedPanel ( "task-1" , "file-src/App.tsx" , "left ") ;
366+ assertActiveTab ( getPanelTree ( "task-1" ) , "main-panel" , " file-src/App.tsx") ;
370367 } ) ;
371368 } ) ;
372369
@@ -376,39 +373,42 @@ describe("panelLayoutStore", () => {
376373 usePanelLayoutStore . getState ( ) . openFile ( "task-1" , "src/App.tsx" ) ;
377374 } ) ;
378375
379- it ( "moves tab to different panel " , ( ) => {
376+ it ( "moves tab between panels " , ( ) => {
380377 usePanelLayoutStore
381378 . getState ( )
382- . moveTab ( "task-1" , "file-src/App.tsx" , "main-panel" , "top-right " ) ;
379+ . moveTab ( "task-1" , "file-src/App.tsx" , "main-panel" , "terminal-panel " ) ;
383380
384- assertTabInNestedPanel ( "task-1" , "file-src/App.tsx" , false , "left" ) ;
385- assertTabInNestedPanel (
386- "task-1" ,
387- "file-src/App.tsx" ,
388- true ,
389- "right" ,
390- "left" ,
381+ const mainPanel = findPanelById ( getPanelTree ( "task-1" ) , "main-panel" ) ;
382+ const terminalPanel = findPanelById (
383+ getPanelTree ( "task-1" ) ,
384+ "terminal-panel" ,
391385 ) ;
386+
387+ expect (
388+ mainPanel ?. content . tabs . find ( ( t ) => t . id === "file-src/App.tsx" ) ,
389+ ) . toBeUndefined ( ) ;
390+ expect (
391+ terminalPanel ?. content . tabs . find ( ( t ) => t . id === "file-src/App.tsx" ) ,
392+ ) . toBeDefined ( ) ;
392393 } ) ;
393394
394395 it ( "sets moved tab as active in target panel" , ( ) => {
395396 usePanelLayoutStore
396397 . getState ( )
397- . moveTab ( "task-1" , "file-src/App.tsx" , "main-panel" , "top-right " ) ;
398+ . moveTab ( "task-1" , "file-src/App.tsx" , "main-panel" , "terminal-panel " ) ;
398399
399- assertActiveTabInNestedPanel (
400- "task-1" ,
400+ assertActiveTab (
401+ getPanelTree ( "task-1" ) ,
402+ "terminal-panel" ,
401403 "file-src/App.tsx" ,
402- "right" ,
403- "left" ,
404404 ) ;
405405 } ) ;
406406 } ) ;
407407
408408 describe ( "splitPanel" , ( ) => {
409409 beforeEach ( ( ) => {
410410 usePanelLayoutStore . getState ( ) . initializeTask ( "task-1" ) ;
411- usePanelLayoutStore . getState ( ) . openFile ( "task-1" , "src/App.tsx" ) ;
411+ openMultipleFiles ( "task-1" , [ "src/App.tsx" , "src/Other.tsx" ] ) ;
412412 } ) ;
413413
414414 it . each ( [
@@ -419,12 +419,23 @@ describe("panelLayoutStore", () => {
419419 ] as const ) (
420420 "splits panel %s creates %s layout" ,
421421 ( direction , expectedDirection ) => {
422- splitAndAssert (
423- "task-1" ,
424- "file-src/App.tsx" ,
425- direction ,
426- expectedDirection ,
427- ) ;
422+ usePanelLayoutStore
423+ . getState ( )
424+ . splitPanel (
425+ "task-1" ,
426+ "file-src/App.tsx" ,
427+ "main-panel" ,
428+ "main-panel" ,
429+ direction ,
430+ ) ;
431+
432+ // After split, main-panel becomes a group
433+ const mainPanelNode = getNestedPanel ( "task-1" , 0 ) ;
434+ expect ( mainPanelNode . type ) . toBe ( "group" ) ;
435+ if ( mainPanelNode . type === "group" ) {
436+ expect ( mainPanelNode . direction ) . toBe ( expectedDirection ) ;
437+ expect ( mainPanelNode . children ) . toHaveLength ( 2 ) ;
438+ }
428439 } ,
429440 ) ;
430441
@@ -439,19 +450,19 @@ describe("panelLayoutStore", () => {
439450 "right" ,
440451 ) ;
441452
442- assertTabInNestedPanel (
443- "task-1" ,
444- "file-src/App.tsx" ,
445- true ,
446- "left" ,
447- "right" ,
448- ) ;
449- assertActiveTabInNestedPanel (
450- "task-1" ,
451- "file-src/App.tsx" ,
452- "left" ,
453- "right" ,
454- ) ;
453+ // After right split: main-panel becomes a group with [original, new]
454+ const mainPanelNode = getNestedPanel ( "task-1" , 0 ) ;
455+ expect ( mainPanelNode . type ) . toBe ( "group" ) ;
456+ if ( mainPanelNode . type === "group" ) {
457+ const newPanel = mainPanelNode . children [ 1 ] ;
458+ expect ( newPanel . type ) . toBe ( "leaf" ) ;
459+ if ( newPanel . type === "leaf" ) {
460+ expect (
461+ newPanel . content . tabs . some ( ( t ) => t . id === "file-src/App.tsx" ) ,
462+ ) . toBe ( true ) ;
463+ expect ( newPanel . content . activeTabId ) . toBe ( "file-src/App.tsx" ) ;
464+ }
465+ }
455466 } ) ;
456467 } ) ;
457468
@@ -461,31 +472,20 @@ describe("panelLayoutStore", () => {
461472 } ) ;
462473
463474 it ( "updates panel group sizes" , ( ) => {
464- usePanelLayoutStore . getState ( ) . updateSizes ( "task-1" , "root" , [ 60 , 40 ] ) ;
465-
466- withRootGroup ( "task-1" , ( root : GroupNode ) => {
467- expect ( root . sizes ) . toEqual ( [ 60 , 40 ] ) ;
468- } ) ;
469- } ) ;
470-
471- it ( "updates nested group sizes" , ( ) => {
472475 usePanelLayoutStore
473476 . getState ( )
474- . updateSizes ( "task-1" , "right -group" , [ 30 , 70 ] ) ;
477+ . updateSizes ( "task-1" , "left -group" , [ 60 , 40 ] ) ;
475478
476- const rightGroup = getNestedPanel ( "task-1" , "right" ) ;
477- assertGroupStructure ( rightGroup , {
478- direction : "vertical" ,
479- childCount : 2 ,
480- sizes : [ 30 , 70 ] ,
479+ withRootGroup ( "task-1" , ( root : GroupNode ) => {
480+ expect ( root . sizes ) . toEqual ( [ 60 , 40 ] ) ;
481481 } ) ;
482482 } ) ;
483483 } ) ;
484484
485485 describe ( "tree cleanup" , ( ) => {
486486 beforeEach ( ( ) => {
487487 usePanelLayoutStore . getState ( ) . initializeTask ( "task-1" ) ;
488- usePanelLayoutStore . getState ( ) . openFile ( "task-1" , "src/App.tsx" ) ;
488+ openMultipleFiles ( "task-1" , [ "src/App.tsx" , "src/Other.tsx" ] ) ;
489489 } ) ;
490490
491491 it ( "removes empty panels after closing all tabs" , ( ) => {
@@ -499,13 +499,18 @@ describe("panelLayoutStore", () => {
499499 "right" ,
500500 ) ;
501501
502- const newPanel = getNestedPanel ( "task-1" , "left" , "right" ) ;
503- usePanelLayoutStore
504- . getState ( )
505- . closeTab ( "task-1" , newPanel . id , "file-src/App.tsx" ) ;
502+ // Find the new panel and close its tab
503+ const mainPanelNode = getNestedPanel ( "task-1" , 0 ) ;
504+ if ( mainPanelNode . type === "group" ) {
505+ const newPanel = mainPanelNode . children [ 1 ] ;
506+ usePanelLayoutStore
507+ . getState ( )
508+ . closeTab ( "task-1" , newPanel . id , "file-src/App.tsx" ) ;
509+ }
506510
507- const updatedLeftPanel = getNestedPanel ( "task-1" , "left" ) ;
508- expect ( updatedLeftPanel . type ) . toBe ( "leaf" ) ;
511+ // After closing, the group should simplify back to a leaf
512+ const updatedMainPanel = getNestedPanel ( "task-1" , 0 ) ;
513+ expect ( updatedMainPanel . type ) . toBe ( "leaf" ) ;
509514 } ) ;
510515 } ) ;
511516} ) ;
0 commit comments