1- import { Text } from 'ink' ;
1+ import { Text , useStdout } from 'ink' ;
22import { render } from 'ink-testing-library' ;
33import { useState } from 'react' ;
44
55import { SessionManager } from './SessionManager' ;
66
7- const selectionState = vi . hoisted ( ( ) => ( {
8- instanceId : '' ,
9- mountCount : 0 ,
10- onCancel : null as ( ( ) => void ) | null ,
11- onChange : null as ( ( value : string ) => void ) | null ,
12- options : [ ] as { label : string ; value : string } [ ] ,
7+ const { mockColumns, selectionState } = vi . hoisted ( ( ) => ( {
8+ mockColumns : {
9+ value : 100 ,
10+ } ,
11+ selectionState : {
12+ instanceId : '' ,
13+ mountCount : 0 ,
14+ onCancel : null as ( ( ) => void ) | null ,
15+ onChange : null as ( ( value : string ) => void ) | null ,
16+ options : [ ] as { label : string ; value : string } [ ] ,
17+ } ,
1318} ) ) ;
1419
1520const sessions = vi . hoisted ( ( ) => [
@@ -29,6 +34,15 @@ const sessions = vi.hoisted(() => [
2934 } ,
3035] ) ;
3136
37+ vi . mock ( 'ink' , async ( ) => ( {
38+ ...( await vi . importActual ( 'ink' ) ) ,
39+ useStdout : vi . fn ( ( ) => ( {
40+ stdout : {
41+ columns : mockColumns . value ,
42+ } ,
43+ } ) ) ,
44+ } ) ) ;
45+
3246vi . mock ( './SelectPrompt' , ( ) => ( {
3347 SelectPrompt : ( {
3448 onCancel,
@@ -69,6 +83,8 @@ vi.mock('../utils/session', () => ({
6983
7084describe ( 'SessionManager' , ( ) => {
7185 beforeEach ( ( ) => {
86+ mockColumns . value = 100 ;
87+ vi . mocked ( useStdout ) . mockClear ( ) ;
7288 selectionState . instanceId = '' ;
7389 selectionState . mountCount = 0 ;
7490 selectionState . onCancel = null ;
@@ -94,7 +110,53 @@ describe('SessionManager', () => {
94110 ) ;
95111 } ) ;
96112
97- it ( 'renders the current session, other sessions, and management actions' , ( ) => {
113+ it ( 'truncates long session labels to the available width' , ( ) => {
114+ mockColumns . value = 36 ;
115+ sessions [ 1 ] = {
116+ ...sessions [ 1 ] ,
117+ title :
118+ 'testing a really long input with a lot of words, writing stuff just to fill space' ,
119+ } ;
120+
121+ const sessionManager = (
122+ < SessionManager
123+ currentSessionId = "session-1"
124+ onClose = { vi . fn ( ) }
125+ onDelete = { vi . fn ( ) }
126+ onNew = { vi . fn ( ) }
127+ onOpen = { vi . fn ( ) }
128+ />
129+ ) ;
130+ const { lastFrame, rerender } = render ( sessionManager ) ;
131+
132+ selectionState . onChange ?.( 'open-menu' ) ;
133+ rerender ( sessionManager ) ;
134+
135+ expect ( lastFrame ( ) ) . toContain ( '…' ) ;
136+ expect ( lastFrame ( ) ) . not . toContain ( 'fill space' ) ;
137+ } ) ;
138+
139+ it ( 'handles extremely narrow terminal by truncating entire label' , ( ) => {
140+ mockColumns . value = 5 ;
141+
142+ const sessionManager = (
143+ < SessionManager
144+ currentSessionId = "session-1"
145+ onClose = { vi . fn ( ) }
146+ onDelete = { vi . fn ( ) }
147+ onNew = { vi . fn ( ) }
148+ onOpen = { vi . fn ( ) }
149+ />
150+ ) ;
151+ const { lastFrame, rerender } = render ( sessionManager ) ;
152+
153+ selectionState . onChange ?.( 'open-menu' ) ;
154+ rerender ( sessionManager ) ;
155+
156+ expect ( lastFrame ( ) ) . toContain ( '…' ) ;
157+ } ) ;
158+
159+ it ( 'renders the main session actions' , ( ) => {
98160 const { lastFrame } = render (
99161 < SessionManager
100162 currentSessionId = "session-1"
@@ -107,9 +169,10 @@ describe('SessionManager', () => {
107169
108170 expect ( lastFrame ( ) ) . toContain ( 'Sessions' ) ;
109171 expect ( lastFrame ( ) ) . toContain ( 'Select session' ) ;
110- expect ( lastFrame ( ) ) . toContain ( 'Current: First session' ) ;
111- expect ( lastFrame ( ) ) . toContain ( 'Second session' ) ;
172+ expect ( lastFrame ( ) ) . toContain ( 'Open session' ) ;
112173 expect ( lastFrame ( ) ) . toContain ( 'Delete session' ) ;
174+ expect ( lastFrame ( ) ) . not . toContain ( 'Current: First session' ) ;
175+ expect ( lastFrame ( ) ) . not . toContain ( 'Second session' ) ;
113176 } ) ;
114177
115178 it ( 'shows an error when onOpen throws' , ( ) => {
@@ -127,6 +190,8 @@ describe('SessionManager', () => {
127190 ) ;
128191 const { lastFrame, rerender } = render ( sessionManager ) ;
129192
193+ selectionState . onChange ?.( 'open-menu' ) ;
194+ rerender ( sessionManager ) ;
130195 selectionState . onChange ?.( 'open:session-2' ) ;
131196 rerender ( sessionManager ) ;
132197
@@ -172,6 +237,8 @@ describe('SessionManager', () => {
172237 ) ;
173238 const { lastFrame, rerender } = render ( sessionManager ) ;
174239
240+ selectionState . onChange ?.( 'open-menu' ) ;
241+ rerender ( sessionManager ) ;
175242 selectionState . onChange ?.( 'open:session-2' ) ;
176243 rerender ( sessionManager ) ;
177244
@@ -204,16 +271,19 @@ describe('SessionManager', () => {
204271
205272 it ( 'opens the selected session' , ( ) => {
206273 const onOpen = vi . fn ( ) ;
207- render (
274+ const sessionManager = (
208275 < SessionManager
209276 currentSessionId = "session-1"
210277 onClose = { vi . fn ( ) }
211278 onDelete = { vi . fn ( ) }
212279 onNew = { vi . fn ( ) }
213280 onOpen = { onOpen }
214- /> ,
281+ />
215282 ) ;
283+ const { rerender } = render ( sessionManager ) ;
216284
285+ selectionState . onChange ?.( 'open-menu' ) ;
286+ rerender ( sessionManager ) ;
217287 selectionState . onChange ?.( 'open:session-2' ) ;
218288
219289 expect ( onOpen ) . toHaveBeenCalledWith ( 'session-2' ) ;
@@ -254,7 +324,7 @@ describe('SessionManager', () => {
254324 expect ( onClose ) . toHaveBeenCalledTimes ( 2 ) ;
255325 } ) ;
256326
257- it ( 'includes the delete-menu option ' , ( ) => {
327+ it ( 'includes the open-menu and delete-menu options ' , ( ) => {
258328 render (
259329 < SessionManager
260330 currentSessionId = "session-1"
@@ -265,11 +335,41 @@ describe('SessionManager', () => {
265335 /> ,
266336 ) ;
267337
338+ expect ( selectionState . options . map ( ( { value } ) => value ) ) . toContain (
339+ 'open-menu' ,
340+ ) ;
268341 expect ( selectionState . options . map ( ( { value } ) => value ) ) . toContain (
269342 'delete-menu' ,
270343 ) ;
271344 } ) ;
272345
346+ it ( 'shows sessions in open mode' , ( ) => {
347+ const sessionManager = (
348+ < SessionManager
349+ currentSessionId = "session-1"
350+ onClose = { vi . fn ( ) }
351+ onDelete = { vi . fn ( ) }
352+ onNew = { vi . fn ( ) }
353+ onOpen = { vi . fn ( ) }
354+ />
355+ ) ;
356+ const { lastFrame, rerender } = render ( sessionManager ) ;
357+
358+ selectionState . onChange ?.( 'open-menu' ) ;
359+ rerender ( sessionManager ) ;
360+
361+ expect ( lastFrame ( ) ) . toContain ( 'Open session' ) ;
362+ expect ( lastFrame ( ) ) . toContain ( 'Second session' ) ;
363+ expect ( lastFrame ( ) ) . not . toContain ( 'Current: First session' ) ;
364+ expect ( selectionState . options . map ( ( { value } ) => value ) ) . toContain (
365+ 'open:session-2' ,
366+ ) ;
367+ expect ( selectionState . options . map ( ( { value } ) => value ) ) . not . toContain (
368+ 'open:session-1' ,
369+ ) ;
370+ expect ( selectionState . options . map ( ( { value } ) => value ) ) . toContain ( 'back' ) ;
371+ } ) ;
372+
273373 it ( 'deletes the selected session in delete mode' , ( ) => {
274374 const onDelete = vi . fn ( ) ;
275375 render (
@@ -343,7 +443,30 @@ describe('SessionManager', () => {
343443 ) ;
344444 } ) ;
345445
346- it ( 'remounts the select prompt when switching between delete and main views' , ( ) => {
446+ it ( 'returns to main view when back is selected in open mode' , ( ) => {
447+ const sessionManager = (
448+ < SessionManager
449+ currentSessionId = "session-1"
450+ onClose = { vi . fn ( ) }
451+ onDelete = { vi . fn ( ) }
452+ onNew = { vi . fn ( ) }
453+ onOpen = { vi . fn ( ) }
454+ />
455+ ) ;
456+ const { rerender } = render ( sessionManager ) ;
457+
458+ selectionState . onChange ?.( 'open-menu' ) ;
459+ rerender ( sessionManager ) ;
460+ expect ( selectionState . options . map ( ( { value } ) => value ) ) . toContain ( 'back' ) ;
461+
462+ selectionState . onChange ?.( 'back' ) ;
463+ rerender ( sessionManager ) ;
464+ expect ( selectionState . options . map ( ( { value } ) => value ) ) . toContain (
465+ 'open-menu' ,
466+ ) ;
467+ } ) ;
468+
469+ it ( 'remounts the select prompt when switching between main, open, and delete views' , ( ) => {
347470 const sessionManager = (
348471 < SessionManager
349472 currentSessionId = "session-1"
@@ -357,14 +480,20 @@ describe('SessionManager', () => {
357480
358481 const mainInstanceId = selectionState . instanceId ;
359482
360- selectionState . onChange ?.( 'delete -menu' ) ;
483+ selectionState . onChange ?.( 'open -menu' ) ;
361484 rerender ( sessionManager ) ;
362- const deleteInstanceId = selectionState . instanceId ;
485+ const openInstanceId = selectionState . instanceId ;
363486
364487 selectionState . onChange ?.( 'back' ) ;
365488 rerender ( sessionManager ) ;
366489 const nextMainInstanceId = selectionState . instanceId ;
367490
491+ selectionState . onChange ?.( 'delete-menu' ) ;
492+ rerender ( sessionManager ) ;
493+ const deleteInstanceId = selectionState . instanceId ;
494+
495+ expect ( openInstanceId ) . not . toBe ( mainInstanceId ) ;
496+ expect ( nextMainInstanceId ) . not . toBe ( openInstanceId ) ;
368497 expect ( deleteInstanceId ) . not . toBe ( mainInstanceId ) ;
369498 expect ( nextMainInstanceId ) . not . toBe ( deleteInstanceId ) ;
370499 } ) ;
0 commit comments