@@ -195,6 +195,67 @@ define(function (require, exports, module) {
195195 return getTab ( filePath ) . find ( ".tab-close" ) . length > 0 ;
196196 }
197197
198+ /**
199+ * Helper function to check if the overflow button is visible
200+ * @returns {boolean } - True if the overflow button is visible, false otherwise
201+ */
202+ function isOverflowButtonVisible ( ) {
203+ return $ ( "#overflow-button" ) . is ( ":visible" ) ;
204+ }
205+
206+ /**
207+ * Helper function to get the overflow button element
208+ * @returns {jQuery } - The overflow button element
209+ */
210+ function getOverflowButton ( ) {
211+ return $ ( "#overflow-button" ) ;
212+ }
213+
214+ /**
215+ * Helper function to check if a tab is visible in the tab bar (not hidden by overflow)
216+ * @param {string } filePath - The path of the file to check
217+ * @returns {boolean } - True if the tab is visible, false otherwise
218+ */
219+ function isTabVisible ( filePath ) {
220+ const $tab = getTab ( filePath ) ;
221+ if ( ! $tab . length ) {
222+ return false ;
223+ }
224+
225+ const $tabBar = $ ( "#phoenix-tab-bar" ) ;
226+ const tabBarRect = $tabBar [ 0 ] . getBoundingClientRect ( ) ;
227+ const tabRect = $tab [ 0 ] . getBoundingClientRect ( ) ;
228+
229+ // A tab is considered visible if it is completely within the tab bar's visible area
230+ // with a small margin of error (2px)
231+ return tabRect . left >= tabBarRect . left && tabRect . right <= tabBarRect . right + 2 ;
232+ }
233+
234+ /**
235+ * Helper function to get the overflow dropdown menu
236+ * @returns {jQuery } - The overflow dropdown menu element
237+ */
238+ function getOverflowDropdown ( ) {
239+ return $ ( ".dropdown-overflow-menu" ) ;
240+ }
241+
242+ /**
243+ * Helper function to get the items in the overflow dropdown
244+ * @returns {jQuery } - The overflow dropdown items
245+ */
246+ function getOverflowDropdownItems ( ) {
247+ return $ ( ".dropdown-overflow-menu .dropdown-tab-item" ) ;
248+ }
249+
250+ /**
251+ * Helper function to get a specific item in the overflow dropdown by file path
252+ * @param {string } filePath - The path of the file to find
253+ * @returns {jQuery } - The dropdown item element
254+ */
255+ function getOverflowDropdownItem ( filePath ) {
256+ return $ ( `.dropdown-overflow-menu .dropdown-tab-item[data-tab-path="${ filePath } "]` ) ;
257+ }
258+
198259 describe ( "Visibility" , function ( ) {
199260 it ( "should show tab bar when the feature is enabled" , async function ( ) {
200261 // Enable the tab bar feature
@@ -581,6 +642,269 @@ define(function (require, exports, module) {
581642 } ) ;
582643 } ) ;
583644
645+ describe ( "Overflow" , function ( ) {
646+ beforeEach ( async function ( ) {
647+ // Enable the tab bar feature
648+ PreferencesManager . set ( "tabBar.options" , { showTabBar : true , numberOfTabs : - 1 } ) ;
649+
650+ // Close all files to start with a clean state
651+ await testWindow . closeAllFiles ( ) ;
652+ } ) ;
653+
654+ it ( "should show overflow button when there are too many tabs to fit" , async function ( ) {
655+ // Create several test files to ensure overflow
656+ const testFiles = [ ] ;
657+ for ( let i = 0 ; i < 15 ; i ++ ) {
658+ const filePath = SpecRunnerUtils . getTempDirectory ( ) + `/overflow-test-${ i } .js` ;
659+ testFiles . push ( filePath ) ;
660+ await jsPromise ( SpecRunnerUtils . createTextFile ( filePath , `// Overflow test file ${ i } ` , FileSystem ) ) ;
661+ }
662+
663+ // Open all the test files
664+ for ( const filePath of testFiles ) {
665+ await awaitsForDone (
666+ CommandManager . execute ( Commands . FILE_OPEN , { fullPath : filePath } ) ,
667+ `Open file ${ filePath } `
668+ ) ;
669+ }
670+
671+ // Wait for all tabs to appear
672+ await awaitsFor (
673+ function ( ) {
674+ return getTabCount ( ) >= testFiles . length ;
675+ } ,
676+ "All tabs to appear" ,
677+ 1000
678+ ) ;
679+
680+ // Wait for the overflow button to appear
681+ await awaitsFor (
682+ function ( ) {
683+ return isOverflowButtonVisible ( ) ;
684+ } ,
685+ "Overflow button to appear" ,
686+ 1000
687+ ) ;
688+
689+ // Verify the overflow button is visible
690+ expect ( isOverflowButtonVisible ( ) ) . toBe ( true ) ;
691+
692+ // Verify that some tabs are not visible
693+ let visibleTabs = 0 ;
694+ let hiddenTabs = 0 ;
695+ for ( const filePath of testFiles ) {
696+ if ( isTabVisible ( filePath ) ) {
697+ visibleTabs ++ ;
698+ } else {
699+ hiddenTabs ++ ;
700+ }
701+ }
702+
703+ // There should be at least one hidden tab
704+ expect ( hiddenTabs ) . toBeGreaterThan ( 0 ) ;
705+ expect ( visibleTabs + hiddenTabs ) . toBe ( testFiles . length ) ;
706+
707+ // Clean up - close all the test files
708+ for ( const filePath of testFiles ) {
709+ const fileToClose = FileSystem . getFileForPath ( filePath ) ;
710+ const promise = CommandManager . execute ( Commands . FILE_CLOSE , { file : fileToClose } ) ;
711+ testWindow . brackets . test . Dialogs . cancelModalDialogIfOpen (
712+ testWindow . brackets . test . DefaultDialogs . DIALOG_ID_SAVE_CLOSE ,
713+ testWindow . brackets . test . DefaultDialogs . DIALOG_BTN_DONTSAVE
714+ ) ;
715+ await awaitsForDone ( promise , `Close file ${ filePath } ` ) ;
716+ }
717+ } ) ;
718+
719+ it ( "should display dropdown with hidden tabs when overflow button is clicked" , async function ( ) {
720+ // Create several test files to ensure overflow
721+ const testFiles = [ ] ;
722+ for ( let i = 0 ; i < 15 ; i ++ ) {
723+ const filePath = SpecRunnerUtils . getTempDirectory ( ) + `/overflow-test-${ i } .js` ;
724+ testFiles . push ( filePath ) ;
725+ await jsPromise ( SpecRunnerUtils . createTextFile ( filePath , `// Overflow test file ${ i } ` , FileSystem ) ) ;
726+ }
727+
728+ // Open all the test files
729+ for ( const filePath of testFiles ) {
730+ await awaitsForDone (
731+ CommandManager . execute ( Commands . FILE_OPEN , { fullPath : filePath } ) ,
732+ `Open file ${ filePath } `
733+ ) ;
734+ }
735+
736+ // Wait for all tabs to appear
737+ await awaitsFor (
738+ function ( ) {
739+ return getTabCount ( ) >= testFiles . length ;
740+ } ,
741+ "All tabs to appear" ,
742+ 1000
743+ ) ;
744+
745+ // Wait for the overflow button to appear
746+ await awaitsFor (
747+ function ( ) {
748+ return isOverflowButtonVisible ( ) ;
749+ } ,
750+ "Overflow button to appear" ,
751+ 1000
752+ ) ;
753+
754+ // Get the list of hidden tabs
755+ const hiddenFiles = testFiles . filter ( ( filePath ) => ! isTabVisible ( filePath ) ) ;
756+ expect ( hiddenFiles . length ) . toBeGreaterThan ( 0 ) ;
757+
758+ // Click the overflow button
759+ getOverflowButton ( ) . click ( ) ;
760+
761+ // Wait for the dropdown to appear
762+ await awaitsFor (
763+ function ( ) {
764+ return getOverflowDropdown ( ) . length > 0 ;
765+ } ,
766+ "Overflow dropdown to appear" ,
767+ 1000
768+ ) ;
769+
770+ // Verify the dropdown is visible
771+ expect ( getOverflowDropdown ( ) . length ) . toBeGreaterThan ( 0 ) ;
772+
773+ // Verify the dropdown contains items for all hidden tabs
774+ const dropdownItems = getOverflowDropdownItems ( ) ;
775+ expect ( dropdownItems . length ) . toBe ( hiddenFiles . length ) ;
776+
777+ // Verify each hidden file has an item in the dropdown
778+ for ( const filePath of hiddenFiles ) {
779+ const item = getOverflowDropdownItem ( filePath ) ;
780+ expect ( item . length ) . toBe ( 1 ) ;
781+ }
782+
783+ // Clean up - close the dropdown by clicking elsewhere
784+ $ ( "body" ) . click ( ) ;
785+
786+ // Wait for the dropdown to disappear
787+ await awaitsFor (
788+ function ( ) {
789+ return getOverflowDropdown ( ) . length === 0 ;
790+ } ,
791+ "Overflow dropdown to disappear" ,
792+ 1000
793+ ) ;
794+
795+ // Clean up - close all the test files
796+ for ( const filePath of testFiles ) {
797+ const fileToClose = FileSystem . getFileForPath ( filePath ) ;
798+ const promise = CommandManager . execute ( Commands . FILE_CLOSE , { file : fileToClose } ) ;
799+ testWindow . brackets . test . Dialogs . cancelModalDialogIfOpen (
800+ testWindow . brackets . test . DefaultDialogs . DIALOG_ID_SAVE_CLOSE ,
801+ testWindow . brackets . test . DefaultDialogs . DIALOG_BTN_DONTSAVE
802+ ) ;
803+ await awaitsForDone ( promise , `Close file ${ filePath } ` ) ;
804+ }
805+ } ) ;
806+
807+ it ( "should make tab visible and file active when clicking on item in overflow dropdown" , async function ( ) {
808+ // Create several test files to ensure overflow
809+ const testFiles = [ ] ;
810+ for ( let i = 0 ; i < 15 ; i ++ ) {
811+ const filePath = SpecRunnerUtils . getTempDirectory ( ) + `/overflow-test-${ i } .js` ;
812+ testFiles . push ( filePath ) ;
813+ await jsPromise ( SpecRunnerUtils . createTextFile ( filePath , `// Overflow test file ${ i } ` , FileSystem ) ) ;
814+ }
815+
816+ // Open all the test files
817+ for ( const filePath of testFiles ) {
818+ await awaitsForDone (
819+ CommandManager . execute ( Commands . FILE_OPEN , { fullPath : filePath } ) ,
820+ `Open file ${ filePath } `
821+ ) ;
822+ }
823+
824+ // Wait for all tabs to appear
825+ await awaitsFor (
826+ function ( ) {
827+ return getTabCount ( ) >= testFiles . length ;
828+ } ,
829+ "All tabs to appear" ,
830+ 1000
831+ ) ;
832+
833+ // Wait for the overflow button to appear
834+ await awaitsFor (
835+ function ( ) {
836+ return isOverflowButtonVisible ( ) ;
837+ } ,
838+ "Overflow button to appear" ,
839+ 1000
840+ ) ;
841+
842+ // Get the list of hidden tabs
843+ const hiddenFiles = testFiles . filter ( ( filePath ) => ! isTabVisible ( filePath ) ) ;
844+ expect ( hiddenFiles . length ) . toBeGreaterThan ( 0 ) ;
845+
846+ // Select a hidden file to test
847+ const testHiddenFile = hiddenFiles [ 0 ] ;
848+
849+ // Click the overflow button
850+ getOverflowButton ( ) . click ( ) ;
851+
852+ // Wait for the dropdown to appear
853+ await awaitsFor (
854+ function ( ) {
855+ return getOverflowDropdown ( ) . length > 0 ;
856+ } ,
857+ "Overflow dropdown to appear" ,
858+ 1000
859+ ) ;
860+
861+ // Get the dropdown item for the test file
862+ const dropdownItem = getOverflowDropdownItem ( testHiddenFile ) ;
863+ expect ( dropdownItem . length ) . toBe ( 1 ) ;
864+
865+ // Click the dropdown item
866+ dropdownItem . click ( ) ;
867+
868+ // Wait for the file to become active
869+ await awaitsFor (
870+ function ( ) {
871+ return (
872+ isTabActive ( testHiddenFile ) &&
873+ MainViewManager . getCurrentlyViewedFile ( ) . fullPath === testHiddenFile
874+ ) ;
875+ } ,
876+ "Hidden file to become active after dropdown item click" ,
877+ 1000
878+ ) ;
879+
880+ // Verify the file is active
881+ expect ( isTabActive ( testHiddenFile ) ) . toBe ( true ) ;
882+ expect ( MainViewManager . getCurrentlyViewedFile ( ) . fullPath ) . toBe ( testHiddenFile ) ;
883+
884+ // Verify the tab is now visible (scrolled into view)
885+ await awaitsFor (
886+ function ( ) {
887+ return isTabVisible ( testHiddenFile ) ;
888+ } ,
889+ "Tab to become visible after dropdown item click" ,
890+ 1000
891+ ) ;
892+
893+ expect ( isTabVisible ( testHiddenFile ) ) . toBe ( true ) ;
894+
895+ // Clean up - close all the test files
896+ for ( const filePath of testFiles ) {
897+ const fileToClose = FileSystem . getFileForPath ( filePath ) ;
898+ const promise = CommandManager . execute ( Commands . FILE_CLOSE , { file : fileToClose } ) ;
899+ testWindow . brackets . test . Dialogs . cancelModalDialogIfOpen (
900+ testWindow . brackets . test . DefaultDialogs . DIALOG_ID_SAVE_CLOSE ,
901+ testWindow . brackets . test . DefaultDialogs . DIALOG_BTN_DONTSAVE
902+ ) ;
903+ await awaitsForDone ( promise , `Close file ${ filePath } ` ) ;
904+ }
905+ } ) ;
906+ } ) ;
907+
584908 describe ( "Tab Items" , function ( ) {
585909 beforeEach ( async function ( ) {
586910 // Enable the tab bar feature
0 commit comments