@@ -30,6 +30,7 @@ define(function (require, exports, module) {
3030 const Strings = require ( "strings" ) ;
3131
3232 const IS_WINDOWS = Phoenix . platform === "win" ;
33+ const IS_MAC = Phoenix . platform === "mac" ;
3334
3435 describe ( "integration:Terminal" , function ( ) {
3536 let testWindow ,
@@ -209,19 +210,19 @@ define(function (require, exports, module) {
209210 . is ( ":visible" ) ) . toBeTrue ( ) ;
210211 expect ( getTerminalCount ( ) ) . toBe ( 1 ) ;
211212
212- if ( IS_WINDOWS ) {
213+ if ( IS_WINDOWS || IS_MAC ) {
213214 // On Windows, PowerShell/cmd set the title to
214- // their own executable path rather than
215- // "user@host: /path/to/cwd". Verify the shell
216- // started and updated its title from the default
217- // profile name.
215+ // their own executable path. On macOS, zsh does
216+ // not emit title escape sequences by default.
217+ // Just verify the shell started and updated its
218+ // title from the default profile name.
218219 await waitForShellReady ( ) ;
219220 const flyoutTitle = testWindow . $ (
220221 ".terminal-flyout-item.active"
221222 ) . attr ( "title" ) || "" ;
222223 expect ( flyoutTitle ) . not . toBe ( "" ) ;
223224 } else {
224- // On Linux/macOS , bash/zsh set the title to
225+ // On Linux, bash/zsh set the title to
225226 // include the cwd (e.g. "user@host: /path").
226227 const expectedPath = getNativeProjectPath ( ) ;
227228 const projectDirName = expectedPath
@@ -430,6 +431,116 @@ define(function (require, exports, module) {
430431 } ) ;
431432 } ) ;
432433
434+ describe ( "Terminal title management" , function ( ) {
435+ it ( "should retain custom title while child process runs" ,
436+ async function ( ) {
437+ await openTerminal ( ) ;
438+ await awaitsFor ( function ( ) {
439+ return getTerminalCount ( ) === 1 ;
440+ } , "terminal to be created" , 10000 ) ;
441+
442+ await waitForShellReady ( ) ;
443+
444+ // Start a long-running node process that sets a
445+ // custom terminal title via escape sequence.
446+ await writeToTerminal (
447+ 'node -e "process.stdout.write('
448+ + "'\\x1b]0;MyAppTitle\\x07'"
449+ + ');setTimeout(()=>{},60000)"\r'
450+ ) ;
451+ await waitForActiveProcess ( "node" ) ;
452+
453+ // Verify the custom title appears
454+ await awaitsFor ( function ( ) {
455+ triggerFlyoutRefresh ( ) ;
456+ const title = testWindow . $ (
457+ ".terminal-flyout-item.active"
458+ ) . attr ( "title" ) || "" ;
459+ return title . indexOf ( "MyAppTitle" ) !== - 1 ;
460+ } , "custom title to appear" , 10000 ) ;
461+
462+ // Trigger several flyout refreshes — the title
463+ // must remain stable while the process runs.
464+ triggerFlyoutRefresh ( ) ;
465+ const title = testWindow . $ (
466+ ".terminal-flyout-item.active"
467+ ) . attr ( "title" ) || "" ;
468+ expect ( title ) . toContain ( "MyAppTitle" ) ;
469+
470+ // Kill the node process so next test starts clean
471+ await writeToTerminal ( "\x03" ) ; // Ctrl+C
472+ await awaitsFor ( function ( ) {
473+ triggerFlyoutRefresh ( ) ;
474+ const label = testWindow . $ (
475+ ".terminal-flyout-item.active "
476+ + ".terminal-flyout-title"
477+ ) . text ( ) ;
478+ return label && label . indexOf ( "node" ) === - 1 ;
479+ } , "node process to exit" , 10000 ) ;
480+
481+ // Clean up
482+ clickPanelCloseButton ( ) ;
483+ await awaitsFor ( function ( ) {
484+ return ! testWindow . $ ( "#terminal-panel" )
485+ . is ( ":visible" ) ;
486+ } , "terminal panel to close" , 5000 ) ;
487+ } ) ;
488+
489+ it ( "should clear stale title after child process exits" ,
490+ async function ( ) {
491+ await openTerminal ( ) ;
492+ await awaitsFor ( function ( ) {
493+ return getTerminalCount ( ) === 1 ;
494+ } , "terminal to be created" , 10000 ) ;
495+
496+ await waitForShellReady ( ) ;
497+
498+ // Start a node process that sets a custom title
499+ // then exits after a short delay.
500+ await writeToTerminal (
501+ 'node -e "process.stdout.write('
502+ + "'\\x1b]0;TestCustomTitle\\x07'"
503+ + ');setTimeout(()=>{},3000)"\r'
504+ ) ;
505+ await waitForActiveProcess ( "node" ) ;
506+
507+ // Verify the custom title appears in the flyout
508+ await awaitsFor ( function ( ) {
509+ triggerFlyoutRefresh ( ) ;
510+ const title = testWindow . $ (
511+ ".terminal-flyout-item.active"
512+ ) . attr ( "title" ) || "" ;
513+ return title . indexOf ( "TestCustomTitle" ) !== - 1 ;
514+ } , "custom title to appear" , 10000 ) ;
515+
516+ // Wait for the node process to exit (3s timeout)
517+ // and the flyout to reflect the shell again.
518+ await awaitsFor ( function ( ) {
519+ triggerFlyoutRefresh ( ) ;
520+ const title = testWindow . $ (
521+ ".terminal-flyout-item.active"
522+ ) . attr ( "title" ) || "" ;
523+ return title . indexOf ( "TestCustomTitle" ) === - 1 ;
524+ } , "stale title to be cleared after exit" , 15000 ) ;
525+
526+ // The flyout label should be back to the shell
527+ triggerFlyoutRefresh ( ) ;
528+ const label = testWindow . $ (
529+ ".terminal-flyout-item.active "
530+ + ".terminal-flyout-title"
531+ ) . text ( ) ;
532+ expect ( label ) . not . toBe ( "" ) ;
533+ expect ( label ) . not . toContain ( "node" ) ;
534+
535+ // Clean up
536+ clickPanelCloseButton ( ) ;
537+ await awaitsFor ( function ( ) {
538+ return ! testWindow . $ ( "#terminal-panel" )
539+ . is ( ":visible" ) ;
540+ } , "terminal panel to close" , 5000 ) ;
541+ } ) ;
542+ } ) ;
543+
433544 describe ( "Programmatic hide vs user close" , function ( ) {
434545 it ( "should keep terminals alive after panel.hide()" ,
435546 async function ( ) {
0 commit comments