Skip to content

fix(tabs): allow tab selection without content panel and remove router dependency #16169

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ describe('IgxBottomNav', () => {
});

describe('', () => {
it('should not navigate to an URL blocked by activate guard', fakeAsync(() => {
it('should allow tab selection for routing tabs regardless of router guard', fakeAsync(() => {
fixture = TestBed.createComponent(BottomNavRoutingGuardTestComponent);
fixture.detectChanges();

Expand All @@ -269,15 +269,18 @@ describe('IgxBottomNav', () => {
expect(tabItems[0].selected).toBe(true);
expect(tabItems[1].selected).toBe(false);

// Even when router guard blocks navigation, tab should still be selected
fixture.ngZone.run(() => {
UIInteractions.simulateClickAndSelectEvent(headers[1]);
});
tick();
// Navigation blocked by guard, so URL stays the same
expect(location.path()).toBe('/view1');
fixture.detectChanges();
expect(bottomNav.selectedIndex).toBe(0);
expect(tabItems[0].selected).toBe(true);
expect(tabItems[1].selected).toBe(false);
// But tab selection should still work
expect(bottomNav.selectedIndex).toBe(1);
expect(tabItems[0].selected).toBe(false);
expect(tabItems[1].selected).toBe(true);
}));
});
});
Expand Down Expand Up @@ -388,7 +391,7 @@ describe('IgxBottomNav', () => {
indexChangingSpy = spyOn(bottomNav.selectedIndexChanging, 'emit');
}));

it('Validate the events are not fired on clicking tab headers before pressing enter/space key.', fakeAsync(() => {
it('Validate the events are fired on clicking tab headers for routing tabs.', fakeAsync(() => {
fixture.ngZone.run(() => router.initialNavigation());
tick();
expect(location.path()).toBe('/');
Expand All @@ -398,28 +401,20 @@ describe('IgxBottomNav', () => {
});
tick();
expect(location.path()).toBe('/view2');
expect(bottomNav.selectedIndex).toBe(-1);

expect(indexChangingSpy).not.toHaveBeenCalled();
expect(indexChangeSpy).not.toHaveBeenCalled();
expect(itemChangeSpy).not.toHaveBeenCalled();

headers[1].dispatchEvent(KEY_ENTER_EVENT);
tick(200);
fixture.detectChanges();
expect(bottomNav.selectedIndex).toBe(1);

expect(itemChangeSpy).toHaveBeenCalledWith({
owner: bottomNav,
oldItem: undefined,
newItem: tabItems[1]
});
expect(indexChangingSpy).toHaveBeenCalledWith({
owner: bottomNav,
cancel: false,
oldIndex: -1,
newIndex: 1
});
expect(indexChangeSpy).toHaveBeenCalledWith(1);
expect(itemChangeSpy).toHaveBeenCalledWith({
owner: bottomNav,
oldItem: undefined,
newItem: tabItems[1]
});
}));
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ export abstract class IgxTabHeaderDirective implements IgxTabHeaderBase {
/** @hidden */
@HostListener('click')
public onClick() {
if (this.tab.panelComponent) {
this.tabs.selectTab(this.tab, true);
}
this.tabs.selectTab(this.tab, true);
}

/** @hidden */
Expand Down
53 changes: 39 additions & 14 deletions projects/igniteui-angular/src/lib/tabs/tabs/tabs.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,7 @@ describe('IgxTabs', () => {
expect(document.activeElement).toBe(headerElements[3]);
}));

it('should not navigate to an URL blocked by activate guard', fakeAsync(() => {
it('should allow tab selection for routing tabs regardless of router guard', fakeAsync(() => {
fixture = TestBed.createComponent(TabsRoutingGuardTestComponent);
tabsComp = fixture.componentInstance.tabs;
fixture.detectChanges();
Expand All @@ -729,15 +729,18 @@ describe('IgxTabs', () => {
expect(tabItems[0].selected).toBe(true);
expect(tabItems[1].selected).toBe(false);

// Even when router guard blocks navigation, tab should still be selected
fixture.ngZone.run(() => {
UIInteractions.simulateClickAndSelectEvent(headerElements[1]);
});
tick();
// Navigation blocked by guard, so URL stays the same
expect(location.path()).toBe('/view1');
fixture.detectChanges();
expect(tabsComp.selectedIndex).toBe(0);
expect(tabItems[0].selected).toBe(true);
expect(tabItems[1].selected).toBe(false);
// But tab selection should still work
expect(tabsComp.selectedIndex).toBe(1);
expect(tabItems[0].selected).toBe(false);
expect(tabItems[1].selected).toBe(true);
}));

it('should set auto activation mode by default and change selectedIndex on arrow keys', fakeAsync(() => {
Expand Down Expand Up @@ -865,6 +868,36 @@ describe('IgxTabs', () => {
fixture.detectChanges();
expect(tabsComp.selectedIndicator.nativeElement.style.visibility).toBe('hidden');
});

it('should allow tab selection by clicking on tabs without content', fakeAsync(() => {
// Initially tab 1 (index 1) is selected
expect(tabsComp.selectedIndex).toBe(1);
expect(tabItems[1].selected).toBe(true);
expect(tabItems[0].selected).toBe(false);
expect(tabItems[2].selected).toBe(false);

// Click on tab 0 (no content)
headerElements[0].dispatchEvent(new Event('click', { bubbles: true }));
tick(200);
fixture.detectChanges();

// Should now be selected
expect(tabsComp.selectedIndex).toBe(0);
expect(tabItems[0].selected).toBe(true);
expect(tabItems[1].selected).toBe(false);
expect(tabItems[2].selected).toBe(false);

// Click on tab 2 (no content)
headerElements[2].dispatchEvent(new Event('click', { bubbles: true }));
tick(200);
fixture.detectChanges();

// Should now be selected
expect(tabsComp.selectedIndex).toBe(2);
expect(tabItems[2].selected).toBe(true);
expect(tabItems[0].selected).toBe(false);
expect(tabItems[1].selected).toBe(false);
}));
});

describe('Tabs-only Mode With Initial Selection Set on Tabs Component Tests', () => {
Expand Down Expand Up @@ -1059,7 +1092,7 @@ describe('IgxTabs', () => {
indexChangingSpy = spyOn(tabs.selectedIndexChanging, 'emit');
}));

it('Validate the events are not fired on clicking tab headers before pressing enter/space key.', fakeAsync(() => {
it('Validate the events are fired on clicking tab headers for routing tabs.', fakeAsync(() => {
fixture.ngZone.run(() => router.initialNavigation());
tick();
expect(location.path()).toBe('/');
Expand All @@ -1069,15 +1102,7 @@ describe('IgxTabs', () => {
});
tick();
expect(location.path()).toBe('/view2');
expect(tabs.selectedIndex).toBe(-1);

expect(indexChangingSpy).not.toHaveBeenCalled();
expect(indexChangeSpy).not.toHaveBeenCalled();
expect(itemChangeSpy).not.toHaveBeenCalled();

headers[1].dispatchEvent(KEY_ENTER_EVENT);
tick(200);
fixture.detectChanges();
expect(tabs.selectedIndex).toBe(1);

expect(indexChangingSpy).toHaveBeenCalledWith({
owner: tabs,
Expand Down
29 changes: 29 additions & 0 deletions src/app/tabs-showcase/tabs-showcase.sample.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,35 @@
}
</igc-tabs>
</div>

<div class="sample-wrapper">
<strong>Tabs without content (test for fix)</strong>
<p>These tabs have headers but no content - clicking should still allow selection</p>
<igx-tabs>
<igx-tab-item>
<igx-tab-header>
<igx-icon igxTabHeaderIcon>info</igx-icon>
<span igxTabHeaderLabel>View 1</span>
</igx-tab-header>
<!-- No igx-tab-content -->
</igx-tab-item>
<igx-tab-item [selected]="true">
<igx-tab-header>
<igx-icon igxTabHeaderIcon>settings</igx-icon>
<span igxTabHeaderLabel>View 2</span>
</igx-tab-header>
<!-- No igx-tab-content -->
</igx-tab-item>
<igx-tab-item>
<igx-tab-header>
<igx-icon igxTabHeaderIcon>help</igx-icon>
<span igxTabHeaderLabel>View 3</span>
</igx-tab-header>
<!-- No igx-tab-content -->
</igx-tab-item>
</igx-tabs>
<p><small>✅ After the fix, clicking any tab header should select it even without content</small></p>
</div>
</div>

<ng-template #customControlsTemplate>
Expand Down
Loading