@@ -495,23 +495,23 @@ describe('Tabs', () => {
495495 ] ,
496496 } ) ;
497497
498- const tabsDebugElement = fixture . debugElement . query ( By . directive ( Tabs ) ) ;
499- const tabsDirective = tabsDebugElement . injector . get ( Tabs ) ;
500-
501- let orderedItems = tabsDirective . _collection . orderedItems ( ) ;
502- expect ( orderedItems . length ) . toBe ( 3 ) ;
503- expect ( orderedItems [ 0 ] . value ( ) ) . toBe ( 'tab1' ) ;
504- expect ( orderedItems [ 2 ] . value ( ) ) . toBe ( 'tab3' ) ;
498+ // Verify initial DOM order
499+ expect ( tabElements . length ) . toBe ( 3 ) ;
500+ expect ( tabElements [ 0 ] . textContent ?. trim ( ) ) . toBe ( 'Tab 1' ) ;
501+ expect ( tabElements [ 2 ] . textContent ?. trim ( ) ) . toBe ( 'Tab 3' ) ;
505502
503+ // Shuffle (reverse) data
506504 const items = testComponent . tabsData ( ) . reverse ( ) ;
507505 testComponent . tabsData . set ( [ ...items ] ) ;
508506 fixture . detectChanges ( ) ;
509507 await waitForMicrotasks ( ) ;
510508
511- orderedItems = tabsDirective . _collection . orderedItems ( ) ;
512- expect ( orderedItems . length ) . toBe ( 3 ) ;
513- expect ( orderedItems [ 0 ] . value ( ) ) . toBe ( 'tab3' ) ;
514- expect ( orderedItems [ 2 ] . value ( ) ) . toBe ( 'tab1' ) ;
509+ // Re-query elements to check new DOM order
510+ defineTestVariables ( ) ;
511+
512+ expect ( tabElements . length ) . toBe ( 3 ) ;
513+ expect ( tabElements [ 0 ] . textContent ?. trim ( ) ) . toBe ( 'Tab 3' ) ;
514+ expect ( tabElements [ 2 ] . textContent ?. trim ( ) ) . toBe ( 'Tab 1' ) ;
515515 } ) ;
516516 } ) ;
517517
@@ -740,6 +740,97 @@ describe('Tabs', () => {
740740 expect ( tabPanelElements [ 2 ] . hasAttribute ( 'inert' ) ) . toBe ( true ) ;
741741 } ) ;
742742 } ) ;
743+
744+ describe ( 'Dynamic tabs' , ( ) => {
745+ beforeEach ( ( ) => {
746+ setupTestTabs ( ) ;
747+ updateTabs ( {
748+ initialTabs : [
749+ { value : 'tab1' , label : 'Tab 1' , content : 'Content 1' } ,
750+ { value : 'tab2' , label : 'Tab 2' , content : 'Content 2' } ,
751+ { value : 'tab3' , label : 'Tab 3' , content : 'Content 3' } ,
752+ ] ,
753+ selectedTab : 'tab2' ,
754+ } ) ;
755+ } ) ;
756+
757+ it ( 'should update selection when active tab is removed' , ( ) => {
758+ expect ( testComponent . selectedTab ( ) ) . toBe ( 'tab2' ) ;
759+
760+ testComponent . tabsData . set ( [
761+ { value : 'tab1' , label : 'Tab 1' , content : 'Content 1' } ,
762+ { value : 'tab3' , label : 'Tab 3' , content : 'Content 3' } ,
763+ ] ) ;
764+ fixture . detectChanges ( ) ;
765+ defineTestVariables ( ) ;
766+
767+ expect ( testComponent . selectedTab ( ) ) . toBeUndefined ( ) ;
768+ } ) ;
769+
770+ it ( 'should maintain selection when a new tab is added' , ( ) => {
771+ expect ( testComponent . selectedTab ( ) ) . toBe ( 'tab2' ) ;
772+
773+ testComponent . tabsData . set ( [
774+ { value : 'tab1' , label : 'Tab 1' , content : 'Content 1' } ,
775+ { value : 'tab2' , label : 'Tab 2' , content : 'Content 2' } ,
776+ { value : 'tab3' , label : 'Tab 3' , content : 'Content 3' } ,
777+ { value : 'tab4' , label : 'Tab 4' , content : 'Content 4' } ,
778+ ] ) ;
779+ fixture . detectChanges ( ) ;
780+ defineTestVariables ( ) ;
781+
782+ expect ( testComponent . selectedTab ( ) ) . toBe ( 'tab2' ) ;
783+ } ) ;
784+ } ) ;
785+
786+ describe ( 'Content lazy rendering' , ( ) => {
787+ beforeEach ( ( ) => {
788+ setupTestTabs ( ) ;
789+ updateTabs ( {
790+ initialTabs : [
791+ { value : 'tab1' , label : 'Tab 1' , content : 'Content 1' } ,
792+ { value : 'tab2' , label : 'Tab 2' , content : 'Content 2' } ,
793+ ] ,
794+ selectedTab : 'tab1' ,
795+ } ) ;
796+ } ) ;
797+
798+ it ( 'should not render content of unselected tabs' , ( ) => {
799+ expect ( tabPanelElements [ 0 ] . textContent ?. trim ( ) ) . toContain ( 'Content 1' ) ;
800+ expect ( tabPanelElements [ 1 ] . textContent ?. trim ( ) ) . not . toContain ( 'Content 2' ) ;
801+ } ) ;
802+
803+ it ( 'should render content when tab becomes selected' , ( ) => {
804+ updateTabs ( { selectedTab : 'tab2' } ) ;
805+
806+ expect ( tabPanelElements [ 0 ] . textContent ?. trim ( ) ) . not . toContain ( 'Content 1' ) ;
807+ expect ( tabPanelElements [ 1 ] . textContent ?. trim ( ) ) . toContain ( 'Content 2' ) ;
808+ } ) ;
809+ } ) ;
810+
811+ describe ( 'Custom IDs' , ( ) => {
812+ let customIdFixture : ComponentFixture < TestTabsCustomIdComponent > ;
813+
814+ beforeEach ( ( ) => {
815+ TestBed . configureTestingModule ( {
816+ providers : [ provideFakeDirectionality ( 'ltr' ) ] ,
817+ } ) ;
818+ customIdFixture = TestBed . createComponent ( TestTabsCustomIdComponent ) ;
819+ fixture = customIdFixture as any ;
820+ customIdFixture . detectChanges ( ) ;
821+ } ) ;
822+
823+ it ( 'should use custom ID for tab and link to panel' , async ( ) => {
824+ const tabEl = customIdFixture . nativeElement . querySelector ( '#custom-tab-id' ) ;
825+ const panelEl = customIdFixture . nativeElement . querySelector ( '#custom-panel-id' ) ;
826+
827+ expect ( tabEl ) . toBeTruthy ( ) ;
828+ expect ( panelEl ) . toBeTruthy ( ) ;
829+
830+ expect ( tabEl . getAttribute ( 'aria-controls' ) ) . toBe ( 'custom-panel-id' ) ;
831+ expect ( panelEl . getAttribute ( 'aria-labelledby' ) ) . toBe ( 'custom-tab-id' ) ;
832+ } ) ;
833+ } ) ;
743834} ) ;
744835
745836@Component ( {
@@ -798,3 +889,21 @@ class TestTabsComponent {
798889 focusMode = signal < 'roving' | 'activedescendant' > ( 'roving' ) ;
799890 selectionMode = signal < 'follow' | 'explicit' > ( 'follow' ) ;
800891}
892+
893+ @Component ( {
894+ template : `
895+ <div ngTabs>
896+ <ul ngTabList [(selectedTab)]="selectedTab">
897+ <li ngTab value="tab1" id="custom-tab-id">Tab 1</li>
898+ </ul>
899+ <div ngTabPanel value="tab1" id="custom-panel-id">
900+ <ng-template ngTabContent>Content 1</ng-template>
901+ </div>
902+ </div>
903+ ` ,
904+ imports : [ Tabs , TabList , Tab , TabPanel , TabContent ] ,
905+ changeDetection : ChangeDetectionStrategy . Eager ,
906+ } )
907+ class TestTabsCustomIdComponent {
908+ selectedTab = signal ( 'tab1' ) ;
909+ }
0 commit comments