1670 lines
53 KiB
TypeScript
1670 lines
53 KiB
TypeScript
import {LEFT_ARROW, RIGHT_ARROW} from '@angular/cdk/keycodes';
|
|
import {dispatchFakeEvent, dispatchKeyboardEvent} from '@angular/cdk/testing/private';
|
|
import {AsyncPipe} from '@angular/common';
|
|
import {Component, DebugElement, OnInit, QueryList, ViewChild, ViewChildren} from '@angular/core';
|
|
import {
|
|
ComponentFixture,
|
|
TestBed,
|
|
fakeAsync,
|
|
flush,
|
|
tick,
|
|
waitForAsync,
|
|
} from '@angular/core/testing';
|
|
import {By} from '@angular/platform-browser';
|
|
import {BrowserAnimationsModule, NoopAnimationsModule} from '@angular/platform-browser/animations';
|
|
import {Observable} from 'rxjs';
|
|
import {
|
|
MAT_TABS_CONFIG,
|
|
MatTab,
|
|
MatTabGroup,
|
|
MatTabHeader,
|
|
MatTabHeaderPosition,
|
|
MatTabsModule,
|
|
} from './index';
|
|
|
|
describe('MatTabGroup', () => {
|
|
beforeEach(fakeAsync(() => {
|
|
TestBed.configureTestingModule({
|
|
imports: [
|
|
MatTabsModule,
|
|
NoopAnimationsModule,
|
|
SimpleTabsTestApp,
|
|
SimpleDynamicTabsTestApp,
|
|
BindedTabsTestApp,
|
|
AsyncTabsTestApp,
|
|
DisabledTabsTestApp,
|
|
TabGroupWithSimpleApi,
|
|
TemplateTabs,
|
|
TabGroupWithAriaInputs,
|
|
TabGroupWithIsActiveBinding,
|
|
NestedTabs,
|
|
TabGroupWithIndirectDescendantTabs,
|
|
TabGroupWithSpaceAbove,
|
|
NestedTabGroupWithLabel,
|
|
TabsWithClassesTestApp,
|
|
],
|
|
});
|
|
}));
|
|
|
|
describe('basic behavior', () => {
|
|
let fixture: ComponentFixture<SimpleTabsTestApp>;
|
|
let element: HTMLElement;
|
|
|
|
beforeEach(() => {
|
|
fixture = TestBed.createComponent(SimpleTabsTestApp);
|
|
element = fixture.nativeElement;
|
|
});
|
|
|
|
it('should default to the first tab', () => {
|
|
checkSelectedIndex(1, fixture);
|
|
});
|
|
|
|
it('will properly load content on first change detection pass', () => {
|
|
fixture.detectChanges();
|
|
const tabBodies = element.querySelectorAll('.mat-mdc-tab-body');
|
|
expect(tabBodies[1].querySelectorAll('span').length).toBe(3);
|
|
});
|
|
|
|
it('should change selected index on click', () => {
|
|
let component = fixture.debugElement.componentInstance;
|
|
component.selectedIndex = 0;
|
|
checkSelectedIndex(0, fixture);
|
|
|
|
// select the second tab
|
|
let tabLabel = fixture.debugElement.queryAll(By.css('.mat-mdc-tab'))[1];
|
|
tabLabel.nativeElement.click();
|
|
checkSelectedIndex(1, fixture);
|
|
|
|
// select the third tab
|
|
tabLabel = fixture.debugElement.queryAll(By.css('.mat-mdc-tab'))[2];
|
|
tabLabel.nativeElement.click();
|
|
checkSelectedIndex(2, fixture);
|
|
});
|
|
|
|
it('should support two-way binding for selectedIndex', fakeAsync(() => {
|
|
let component = fixture.componentInstance;
|
|
component.selectedIndex = 0;
|
|
|
|
fixture.detectChanges();
|
|
|
|
let tabLabel = fixture.debugElement.queryAll(By.css('.mat-mdc-tab'))[1];
|
|
tabLabel.nativeElement.click();
|
|
fixture.detectChanges();
|
|
tick();
|
|
|
|
expect(component.selectedIndex).toBe(1);
|
|
}));
|
|
|
|
// Note: needs to be `async` in order to fail when we expect it to.
|
|
it('should set to correct tab on fast change', waitForAsync(() => {
|
|
let component = fixture.componentInstance;
|
|
component.selectedIndex = 0;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
setTimeout(() => {
|
|
component.selectedIndex = 1;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
setTimeout(() => {
|
|
component.selectedIndex = 0;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
fixture.whenStable().then(() => {
|
|
expect(component.selectedIndex).toBe(0);
|
|
});
|
|
}, 1);
|
|
}, 1);
|
|
}));
|
|
|
|
it('should change tabs based on selectedIndex', fakeAsync(() => {
|
|
let component = fixture.componentInstance;
|
|
let tabComponent = fixture.debugElement.query(By.css('mat-tab-group')).componentInstance;
|
|
|
|
spyOn(component, 'handleSelection').and.callThrough();
|
|
|
|
checkSelectedIndex(1, fixture);
|
|
|
|
tabComponent.selectedIndex = 2;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
|
|
checkSelectedIndex(2, fixture);
|
|
tick();
|
|
|
|
expect(component.handleSelection).toHaveBeenCalledTimes(1);
|
|
expect(component.selectEvent.index).toBe(2);
|
|
}));
|
|
|
|
it('should update tab positions when selected index is changed', () => {
|
|
fixture.detectChanges();
|
|
const component: MatTabGroup = fixture.debugElement.query(
|
|
By.css('mat-tab-group'),
|
|
).componentInstance;
|
|
const tabs: MatTab[] = component._tabs.toArray();
|
|
|
|
expect(tabs[0].position).toBeLessThan(0);
|
|
expect(tabs[1].position).toBe(0);
|
|
expect(tabs[2].position).toBeGreaterThan(0);
|
|
|
|
// Move to third tab
|
|
component.selectedIndex = 2;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
expect(tabs[0].position).toBeLessThan(0);
|
|
expect(tabs[1].position).toBeLessThan(0);
|
|
expect(tabs[2].position).toBe(0);
|
|
|
|
// Move to the first tab
|
|
component.selectedIndex = 0;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
expect(tabs[0].position).toBe(0);
|
|
expect(tabs[1].position).toBeGreaterThan(0);
|
|
expect(tabs[2].position).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should clamp the selected index to the size of the number of tabs', () => {
|
|
fixture.detectChanges();
|
|
const component: MatTabGroup = fixture.debugElement.query(
|
|
By.css('mat-tab-group'),
|
|
).componentInstance;
|
|
|
|
// Set the index to be negative, expect first tab selected
|
|
fixture.componentInstance.selectedIndex = -1;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
expect(component.selectedIndex).toBe(0);
|
|
|
|
// Set the index beyond the size of the tabs, expect last tab selected
|
|
fixture.componentInstance.selectedIndex = 3;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
expect(component.selectedIndex).toBe(2);
|
|
});
|
|
|
|
it('should not crash when setting the selected index to NaN', () => {
|
|
let component = fixture.debugElement.componentInstance;
|
|
|
|
expect(() => {
|
|
component.selectedIndex = NaN;
|
|
fixture.detectChanges();
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it('should show ripples for tab-group labels', () => {
|
|
fixture.detectChanges();
|
|
|
|
const testElement = fixture.nativeElement;
|
|
const tabLabel = fixture.debugElement.queryAll(By.css('.mat-mdc-tab'))[1];
|
|
|
|
expect(testElement.querySelectorAll('.mat-ripple-element').length)
|
|
.withContext('Expected no ripples to show up initially.')
|
|
.toBe(0);
|
|
|
|
dispatchFakeEvent(tabLabel.nativeElement, 'mousedown');
|
|
|
|
expect(testElement.querySelectorAll('.mat-ripple-element').length)
|
|
.withContext('Expected one ripple to show up on label mousedown.')
|
|
.toBe(1);
|
|
});
|
|
|
|
it('should allow disabling ripples for tab-group labels', () => {
|
|
fixture.componentInstance.disableRipple = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
const testElement = fixture.nativeElement;
|
|
const tabLabel = fixture.debugElement.queryAll(By.css('.mat-mdc-tab'))[1];
|
|
|
|
expect(testElement.querySelectorAll('.mat-ripple-element').length)
|
|
.withContext('Expected no ripples to show up initially.')
|
|
.toBe(0);
|
|
|
|
dispatchFakeEvent(tabLabel.nativeElement, 'mousedown');
|
|
dispatchFakeEvent(tabLabel.nativeElement, 'mouseup');
|
|
|
|
expect(testElement.querySelectorAll('.mat-ripple-element').length)
|
|
.withContext('Expected no ripple to show up on label mousedown.')
|
|
.toBe(0);
|
|
});
|
|
|
|
it('should set the isActive flag on each of the tabs', fakeAsync(() => {
|
|
fixture.detectChanges();
|
|
tick();
|
|
|
|
const tabs = fixture.componentInstance.tabs.toArray();
|
|
|
|
expect(tabs[0].isActive).toBe(false);
|
|
expect(tabs[1].isActive).toBe(true);
|
|
expect(tabs[2].isActive).toBe(false);
|
|
|
|
fixture.componentInstance.selectedIndex = 2;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
tick();
|
|
|
|
expect(tabs[0].isActive).toBe(false);
|
|
expect(tabs[1].isActive).toBe(false);
|
|
expect(tabs[2].isActive).toBe(true);
|
|
}));
|
|
|
|
it('should fire animation done event', fakeAsync(() => {
|
|
fixture.detectChanges();
|
|
|
|
spyOn(fixture.componentInstance, 'animationDone');
|
|
let tabLabel = fixture.debugElement.queryAll(By.css('.mat-mdc-tab'))[1];
|
|
tabLabel.nativeElement.click();
|
|
fixture.detectChanges();
|
|
tick();
|
|
|
|
expect(fixture.componentInstance.animationDone).toHaveBeenCalledTimes(1);
|
|
}));
|
|
|
|
it('should add the proper `aria-setsize` and `aria-posinset`', () => {
|
|
fixture.detectChanges();
|
|
|
|
const labels = Array.from(element.querySelectorAll('.mat-mdc-tab'));
|
|
|
|
expect(labels.map(label => label.getAttribute('aria-posinset'))).toEqual(['1', '2', '3']);
|
|
expect(labels.every(label => label.getAttribute('aria-setsize') === '3')).toBe(true);
|
|
});
|
|
|
|
it('should emit focusChange event on click', () => {
|
|
spyOn(fixture.componentInstance, 'handleFocus');
|
|
fixture.detectChanges();
|
|
|
|
const tabLabels = fixture.debugElement.queryAll(By.css('.mat-mdc-tab'));
|
|
|
|
expect(fixture.componentInstance.handleFocus).toHaveBeenCalledTimes(0);
|
|
|
|
tabLabels[2].nativeElement.click();
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.componentInstance.handleFocus).toHaveBeenCalledTimes(1);
|
|
expect(fixture.componentInstance.handleFocus).toHaveBeenCalledWith(
|
|
jasmine.objectContaining({index: 2}),
|
|
);
|
|
});
|
|
|
|
it('should emit focusChange on arrow key navigation', () => {
|
|
spyOn(fixture.componentInstance, 'handleFocus');
|
|
fixture.detectChanges();
|
|
|
|
const tabLabels = fixture.debugElement.queryAll(By.css('.mat-mdc-tab'));
|
|
const tabLabelContainer = fixture.debugElement.query(By.css('.mat-mdc-tab-label-container'))
|
|
.nativeElement as HTMLElement;
|
|
|
|
expect(fixture.componentInstance.handleFocus).toHaveBeenCalledTimes(0);
|
|
|
|
// In order to verify that the `focusChange` event also fires with the correct
|
|
// index, we focus the third tab before testing the keyboard navigation.
|
|
tabLabels[2].nativeElement.click();
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.componentInstance.handleFocus).toHaveBeenCalledTimes(1);
|
|
|
|
dispatchKeyboardEvent(tabLabelContainer, 'keydown', LEFT_ARROW);
|
|
|
|
expect(fixture.componentInstance.handleFocus).toHaveBeenCalledTimes(2);
|
|
expect(fixture.componentInstance.handleFocus).toHaveBeenCalledWith(
|
|
jasmine.objectContaining({index: 1}),
|
|
);
|
|
});
|
|
|
|
it('should clean up the tabs QueryList on destroy', () => {
|
|
const component: MatTabGroup = fixture.debugElement.query(
|
|
By.css('mat-tab-group'),
|
|
)!.componentInstance;
|
|
const spy = jasmine.createSpy('complete spy');
|
|
const subscription = component._tabs.changes.subscribe({complete: spy});
|
|
|
|
fixture.destroy();
|
|
|
|
expect(spy).toHaveBeenCalled();
|
|
subscription.unsubscribe();
|
|
});
|
|
|
|
it('should have a focus indicator', () => {
|
|
const tabLabelNativeElements = [
|
|
...fixture.debugElement.nativeElement.querySelectorAll('.mat-mdc-tab'),
|
|
];
|
|
|
|
expect(tabLabelNativeElements.every(el => el.classList.contains('mat-focus-indicator'))).toBe(
|
|
true,
|
|
);
|
|
});
|
|
|
|
it('should emit focusChange when a tab receives focus', fakeAsync(() => {
|
|
spyOn(fixture.componentInstance, 'handleFocus');
|
|
fixture.detectChanges();
|
|
|
|
const tabLabels = fixture.debugElement.queryAll(By.css('.mat-mdc-tab'));
|
|
|
|
expect(fixture.componentInstance.handleFocus).toHaveBeenCalledTimes(0);
|
|
|
|
// In order to verify that the `focusChange` event also fires with the correct
|
|
// index, we focus the second tab before testing the keyboard navigation.
|
|
dispatchFakeEvent(tabLabels[2].nativeElement, 'focus');
|
|
fixture.detectChanges();
|
|
flush();
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.componentInstance.handleFocus).toHaveBeenCalledTimes(1);
|
|
expect(fixture.componentInstance.handleFocus).toHaveBeenCalledWith(
|
|
jasmine.objectContaining({index: 2}),
|
|
);
|
|
}));
|
|
|
|
it('should be able to programmatically focus a particular tab', () => {
|
|
fixture.detectChanges();
|
|
const tabGroup: MatTabGroup = fixture.debugElement.query(
|
|
By.css('mat-tab-group'),
|
|
).componentInstance;
|
|
const tabHeader: MatTabHeader = fixture.debugElement.query(
|
|
By.css('mat-tab-header'),
|
|
).componentInstance;
|
|
|
|
expect(tabHeader.focusIndex).not.toBe(3);
|
|
|
|
tabGroup.focusTab(3);
|
|
fixture.detectChanges();
|
|
|
|
expect(tabHeader.focusIndex).not.toBe(3);
|
|
});
|
|
|
|
it('should be able to set a tabindex on the inner content element', () => {
|
|
fixture.componentInstance.contentTabIndex = 1;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
const contentElements = Array.from<HTMLElement>(
|
|
fixture.nativeElement.querySelectorAll('mat-tab-body'),
|
|
);
|
|
|
|
expect(contentElements.map(e => e.getAttribute('tabindex'))).toEqual([null, '1', null]);
|
|
|
|
fixture.componentInstance.selectedIndex = 0;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(contentElements.map(e => e.getAttribute('tabindex'))).toEqual(['1', null, null]);
|
|
});
|
|
|
|
it('should update the tabindex of the labels when navigating via keyboard', () => {
|
|
fixture.detectChanges();
|
|
|
|
const tabLabels = fixture.debugElement
|
|
.queryAll(By.css('.mat-mdc-tab'))
|
|
.map(label => label.nativeElement);
|
|
const tabLabelContainer = fixture.debugElement.query(By.css('.mat-mdc-tab-label-container'))
|
|
.nativeElement as HTMLElement;
|
|
|
|
expect(tabLabels.map(label => label.getAttribute('tabindex'))).toEqual(['-1', '0', '-1']);
|
|
|
|
dispatchKeyboardEvent(tabLabelContainer, 'keydown', RIGHT_ARROW);
|
|
fixture.detectChanges();
|
|
|
|
expect(tabLabels.map(label => label.getAttribute('tabindex'))).toEqual(['-1', '-1', '0']);
|
|
});
|
|
|
|
it('should be able to set the aria-label of the tablist', fakeAsync(() => {
|
|
fixture.detectChanges();
|
|
tick();
|
|
|
|
const tabList = fixture.nativeElement.querySelector('.mat-mdc-tab-list') as HTMLElement;
|
|
expect(tabList.hasAttribute('aria-label')).toBe(false);
|
|
|
|
fixture.componentInstance.ariaLabel = 'hello';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
expect(tabList.getAttribute('aria-label')).toBe('hello');
|
|
|
|
fixture.componentInstance.ariaLabel = '';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
expect(tabList.hasAttribute('aria-label')).toBe(false);
|
|
}));
|
|
|
|
it('should be able to set the aria-labelledby of the tablist', fakeAsync(() => {
|
|
fixture.detectChanges();
|
|
tick();
|
|
|
|
const tabList = fixture.nativeElement.querySelector('.mat-mdc-tab-list') as HTMLElement;
|
|
expect(tabList.hasAttribute('aria-labelledby')).toBe(false);
|
|
|
|
fixture.componentInstance.ariaLabelledby = 'some-label';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
expect(tabList.getAttribute('aria-labelledby')).toBe('some-label');
|
|
|
|
fixture.componentInstance.ariaLabelledby = '';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
expect(tabList.hasAttribute('aria-labelledby')).toBe(false);
|
|
}));
|
|
});
|
|
|
|
describe('aria labelling', () => {
|
|
let fixture: ComponentFixture<TabGroupWithAriaInputs>;
|
|
let tab: HTMLElement;
|
|
|
|
beforeEach(fakeAsync(() => {
|
|
fixture = TestBed.createComponent(TabGroupWithAriaInputs);
|
|
fixture.detectChanges();
|
|
tick();
|
|
tab = fixture.nativeElement.querySelector('.mat-mdc-tab');
|
|
}));
|
|
|
|
it('should not set aria-label or aria-labelledby attributes if they are not passed in', () => {
|
|
expect(tab.hasAttribute('aria-label')).toBe(false);
|
|
expect(tab.hasAttribute('aria-labelledby')).toBe(false);
|
|
});
|
|
|
|
it('should set the aria-label attribute', () => {
|
|
fixture.componentInstance.ariaLabel = 'Fruit';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(tab.getAttribute('aria-label')).toBe('Fruit');
|
|
});
|
|
|
|
it('should set the aria-labelledby attribute', () => {
|
|
fixture.componentInstance.ariaLabelledby = 'fruit-label';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(tab.getAttribute('aria-labelledby')).toBe('fruit-label');
|
|
});
|
|
|
|
it('should not be able to set both an aria-label and aria-labelledby', () => {
|
|
fixture.componentInstance.ariaLabel = 'Fruit';
|
|
fixture.componentInstance.ariaLabelledby = 'fruit-label';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(tab.getAttribute('aria-label')).toBe('Fruit');
|
|
expect(tab.hasAttribute('aria-labelledby')).toBe(false);
|
|
|
|
fixture.componentInstance.ariaLabel = 'Veggie';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
expect(tab.getAttribute('aria-label')).toBe('Veggie');
|
|
});
|
|
});
|
|
|
|
describe('aria labelling of tab panels', () => {
|
|
let fixture: ComponentFixture<BindedTabsTestApp>;
|
|
let tabPanels: HTMLElement[];
|
|
|
|
beforeEach(fakeAsync(() => {
|
|
fixture = TestBed.createComponent(BindedTabsTestApp);
|
|
fixture.detectChanges();
|
|
tick();
|
|
tabPanels = Array.from(fixture.nativeElement.querySelectorAll('.mat-mdc-tab-body'));
|
|
}));
|
|
|
|
it('should set `aria-hidden="true"` on inactive tab panels', () => {
|
|
fixture.detectChanges();
|
|
|
|
expect(tabPanels[0].getAttribute('aria-hidden')).not.toBe('true');
|
|
expect(tabPanels[1].getAttribute('aria-hidden')).toBe('true');
|
|
});
|
|
});
|
|
|
|
describe('disable tabs', () => {
|
|
let fixture: ComponentFixture<DisabledTabsTestApp>;
|
|
|
|
beforeEach(() => {
|
|
fixture = TestBed.createComponent(DisabledTabsTestApp);
|
|
});
|
|
|
|
it('should have one disabled tab', () => {
|
|
fixture.detectChanges();
|
|
const labels = fixture.debugElement.queryAll(By.css('.mat-mdc-tab-disabled'));
|
|
expect(labels.length).toBe(1);
|
|
expect(labels[0].nativeElement.getAttribute('aria-disabled')).toBe('true');
|
|
});
|
|
|
|
it('should set the disabled flag on tab', () => {
|
|
fixture.detectChanges();
|
|
|
|
const tabs = fixture.componentInstance.tabs.toArray();
|
|
let labels = fixture.debugElement.queryAll(By.css('.mat-mdc-tab-disabled'));
|
|
expect(tabs[2].disabled).toBe(false);
|
|
expect(labels.length).toBe(1);
|
|
expect(labels[0].nativeElement.getAttribute('aria-disabled')).toBe('true');
|
|
|
|
fixture.componentInstance.isDisabled = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(tabs[2].disabled).toBe(true);
|
|
labels = fixture.debugElement.queryAll(By.css('.mat-mdc-tab-disabled'));
|
|
expect(labels.length).toBe(2);
|
|
expect(
|
|
labels.every(label => label.nativeElement.getAttribute('aria-disabled') === 'true'),
|
|
).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('dynamic binding tabs', () => {
|
|
let fixture: ComponentFixture<SimpleDynamicTabsTestApp>;
|
|
|
|
beforeEach(fakeAsync(() => {
|
|
fixture = TestBed.createComponent(SimpleDynamicTabsTestApp);
|
|
fixture.detectChanges();
|
|
tick();
|
|
fixture.detectChanges();
|
|
}));
|
|
|
|
it('should be able to add a new tab, select it, and have correct origin position', fakeAsync(() => {
|
|
const component: MatTabGroup = fixture.debugElement.query(
|
|
By.css('mat-tab-group'),
|
|
).componentInstance;
|
|
|
|
let tabs: MatTab[] = component._tabs.toArray();
|
|
expect(tabs[0].origin).toBe(null);
|
|
expect(tabs[1].origin).toBe(0);
|
|
expect(tabs[2].origin).toBe(null);
|
|
|
|
// Add a new tab on the right and select it, expect an origin >= than 0 (animate right)
|
|
fixture.componentInstance.tabs.push({label: 'New tab', content: 'to right of index'});
|
|
fixture.componentInstance.selectedIndex = 4;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
tick();
|
|
|
|
tabs = component._tabs.toArray();
|
|
expect(tabs[3].origin).toBeGreaterThanOrEqual(0);
|
|
|
|
// Add a new tab in the beginning and select it, expect an origin < than 0 (animate left)
|
|
fixture.componentInstance.selectedIndex = 0;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
tick();
|
|
|
|
fixture.componentInstance.tabs.push({label: 'New tab', content: 'to left of index'});
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
tick();
|
|
|
|
tabs = component._tabs.toArray();
|
|
expect(tabs[0].origin).toBeLessThan(0);
|
|
}));
|
|
|
|
it('should update selected index if the last tab removed while selected', fakeAsync(() => {
|
|
const component: MatTabGroup = fixture.debugElement.query(
|
|
By.css('mat-tab-group'),
|
|
).componentInstance;
|
|
|
|
const numberOfTabs = component._tabs.length;
|
|
fixture.componentInstance.selectedIndex = numberOfTabs - 1;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
tick();
|
|
|
|
// Remove last tab while last tab is selected, expect next tab over to be selected
|
|
fixture.componentInstance.tabs.pop();
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
tick();
|
|
|
|
expect(component.selectedIndex).toBe(numberOfTabs - 2);
|
|
}));
|
|
|
|
it('should maintain the selected tab if a new tab is added', () => {
|
|
fixture.detectChanges();
|
|
const component: MatTabGroup = fixture.debugElement.query(
|
|
By.css('mat-tab-group'),
|
|
).componentInstance;
|
|
|
|
fixture.componentInstance.selectedIndex = 1;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
// Add a new tab at the beginning.
|
|
fixture.componentInstance.tabs.unshift({label: 'New tab', content: 'at the start'});
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(component.selectedIndex).toBe(2);
|
|
expect(component._tabs.toArray()[2].isActive).toBe(true);
|
|
});
|
|
|
|
it('should maintain the selected tab if a tab is removed', () => {
|
|
// Select the second tab.
|
|
fixture.componentInstance.selectedIndex = 1;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
const component: MatTabGroup = fixture.debugElement.query(
|
|
By.css('mat-tab-group'),
|
|
).componentInstance;
|
|
|
|
// Remove the first tab that is right before the selected one.
|
|
fixture.componentInstance.tabs.splice(0, 1);
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
// Since the first tab has been removed and the second one was selected before, the selected
|
|
// tab moved one position to the right. Meaning that the tab is now the first tab.
|
|
expect(component.selectedIndex).toBe(0);
|
|
expect(component._tabs.toArray()[0].isActive).toBe(true);
|
|
});
|
|
|
|
it('should be able to select a new tab after creation', fakeAsync(() => {
|
|
fixture.detectChanges();
|
|
const component: MatTabGroup = fixture.debugElement.query(
|
|
By.css('mat-tab-group'),
|
|
).componentInstance;
|
|
|
|
fixture.componentInstance.tabs.push({label: 'Last tab', content: 'at the end'});
|
|
fixture.componentInstance.selectedIndex = 3;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
|
|
fixture.detectChanges();
|
|
tick();
|
|
|
|
expect(component.selectedIndex).toBe(3);
|
|
expect(component._tabs.toArray()[3].isActive).toBe(true);
|
|
}));
|
|
|
|
it('should not fire `selectedTabChange` when the amount of tabs changes', fakeAsync(() => {
|
|
fixture.detectChanges();
|
|
fixture.componentInstance.selectedIndex = 1;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
// Add a new tab at the beginning.
|
|
spyOn(fixture.componentInstance, 'handleSelection');
|
|
fixture.componentInstance.tabs.unshift({label: 'New tab', content: 'at the start'});
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
tick();
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.componentInstance.handleSelection).not.toHaveBeenCalled();
|
|
}));
|
|
|
|
it('should update the newly-selected tab if the previously-selected tab is replaced', fakeAsync(() => {
|
|
const component: MatTabGroup = fixture.debugElement.query(
|
|
By.css('mat-tab-group'),
|
|
)!.componentInstance;
|
|
|
|
spyOn(fixture.componentInstance, 'handleSelection');
|
|
|
|
fixture.componentInstance.tabs[fixture.componentInstance.selectedIndex] = {
|
|
label: 'New',
|
|
content: 'New',
|
|
};
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
tick();
|
|
|
|
expect(component._tabs.get(1)?.isActive).toBe(true);
|
|
expect(fixture.componentInstance.handleSelection).toHaveBeenCalledWith(
|
|
jasmine.objectContaining({index: 1}),
|
|
);
|
|
}));
|
|
|
|
it('should be able to disable the pagination', fakeAsync(() => {
|
|
fixture.componentInstance.disablePagination = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
tick();
|
|
|
|
for (let i = 0; i < 50; i++) {
|
|
fixture.componentInstance.tabs.push({label: `Extra ${i}`, content: ''});
|
|
}
|
|
fixture.changeDetectorRef.markForCheck();
|
|
|
|
fixture.detectChanges();
|
|
tick();
|
|
|
|
expect(
|
|
fixture.nativeElement.querySelector('.mat-mdc-tab-header-pagination-controls-enabled'),
|
|
).toBeFalsy();
|
|
}));
|
|
});
|
|
|
|
describe('async tabs', () => {
|
|
let fixture: ComponentFixture<AsyncTabsTestApp>;
|
|
|
|
it('should show tabs when they are available', fakeAsync(() => {
|
|
fixture = TestBed.createComponent(AsyncTabsTestApp);
|
|
|
|
expect(fixture.debugElement.queryAll(By.css('.mat-mdc-tab')).length).toBe(0);
|
|
|
|
fixture.detectChanges();
|
|
tick();
|
|
fixture.detectChanges();
|
|
tick();
|
|
|
|
expect(fixture.debugElement.queryAll(By.css('.mat-mdc-tab')).length).toBe(2);
|
|
}));
|
|
});
|
|
|
|
describe('with simple api', () => {
|
|
let fixture: ComponentFixture<TabGroupWithSimpleApi>;
|
|
let tabGroup: MatTabGroup;
|
|
|
|
beforeEach(fakeAsync(() => {
|
|
fixture = TestBed.createComponent(TabGroupWithSimpleApi);
|
|
fixture.detectChanges();
|
|
tick();
|
|
|
|
tabGroup = fixture.debugElement.query(By.directive(MatTabGroup))
|
|
.componentInstance as MatTabGroup;
|
|
}));
|
|
|
|
it('should support a tab-group with the simple api', fakeAsync(() => {
|
|
expect(getSelectedLabel(fixture).textContent).toMatch('Junk food');
|
|
expect(getSelectedContent(fixture).textContent).toMatch('Pizza, fries');
|
|
|
|
tabGroup.selectedIndex = 2;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
tick();
|
|
|
|
expect(getSelectedLabel(fixture).textContent).toMatch('Fruit');
|
|
expect(getSelectedContent(fixture).textContent).toMatch('Apples, grapes');
|
|
|
|
fixture.componentInstance.otherLabel = 'Chips';
|
|
fixture.componentInstance.otherContent = 'Salt, vinegar';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(getSelectedLabel(fixture).textContent).toMatch('Chips');
|
|
expect(getSelectedContent(fixture).textContent).toMatch('Salt, vinegar');
|
|
}));
|
|
|
|
it('should support @ViewChild in the tab content', () => {
|
|
expect(fixture.componentInstance.legumes).toBeTruthy();
|
|
});
|
|
|
|
it('should only have the active tab in the DOM', fakeAsync(() => {
|
|
expect(fixture.nativeElement.textContent).toContain('Pizza, fries');
|
|
expect(fixture.nativeElement.textContent).not.toContain('Peanuts');
|
|
|
|
tabGroup.selectedIndex = 3;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
tick();
|
|
|
|
expect(fixture.nativeElement.textContent).not.toContain('Pizza, fries');
|
|
expect(fixture.nativeElement.textContent).toContain('Peanuts');
|
|
}));
|
|
|
|
it('should support setting the header position', () => {
|
|
let tabGroupNode = fixture.debugElement.query(By.css('mat-tab-group')).nativeElement;
|
|
|
|
expect(tabGroupNode.classList).not.toContain('mat-mdc-tab-group-inverted-header');
|
|
|
|
tabGroup.headerPosition = 'below';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(tabGroupNode.classList).toContain('mat-mdc-tab-group-inverted-header');
|
|
});
|
|
|
|
it('should be able to opt into keeping the inactive tab content in the DOM', fakeAsync(() => {
|
|
fixture.componentInstance.preserveContent = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.nativeElement.textContent).toContain('Pizza, fries');
|
|
expect(fixture.nativeElement.textContent).not.toContain('Peanuts');
|
|
|
|
tabGroup.selectedIndex = 3;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
tick();
|
|
|
|
expect(fixture.nativeElement.textContent).toContain('Pizza, fries');
|
|
expect(fixture.nativeElement.textContent).toContain('Peanuts');
|
|
}));
|
|
|
|
it('should visibly hide the content of inactive tabs', fakeAsync(() => {
|
|
const contentElements: HTMLElement[] = Array.from(
|
|
fixture.nativeElement.querySelectorAll('.mat-mdc-tab-body-content'),
|
|
);
|
|
|
|
expect(contentElements.map(element => element.style.visibility)).toEqual([
|
|
'visible',
|
|
'hidden',
|
|
'hidden',
|
|
'hidden',
|
|
]);
|
|
|
|
tabGroup.selectedIndex = 2;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
tick();
|
|
|
|
expect(contentElements.map(element => element.style.visibility)).toEqual([
|
|
'hidden',
|
|
'hidden',
|
|
'visible',
|
|
'hidden',
|
|
]);
|
|
|
|
tabGroup.selectedIndex = 1;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
tick();
|
|
|
|
expect(contentElements.map(element => element.style.visibility)).toEqual([
|
|
'hidden',
|
|
'visible',
|
|
'hidden',
|
|
'hidden',
|
|
]);
|
|
}));
|
|
});
|
|
|
|
describe('lazy loaded tabs', () => {
|
|
it('should lazy load the second tab', fakeAsync(() => {
|
|
const fixture = TestBed.createComponent(TemplateTabs);
|
|
fixture.detectChanges();
|
|
tick();
|
|
|
|
const secondLabel = fixture.debugElement.queryAll(By.css('.mat-mdc-tab'))[1];
|
|
secondLabel.nativeElement.click();
|
|
fixture.detectChanges();
|
|
tick();
|
|
fixture.detectChanges();
|
|
|
|
const child = fixture.debugElement.query(By.css('.child'));
|
|
expect(child.nativeElement).toBeDefined();
|
|
}));
|
|
});
|
|
|
|
describe('special cases', () => {
|
|
it('should not throw an error when binding isActive to the view', fakeAsync(() => {
|
|
const fixture = TestBed.createComponent(TabGroupWithIsActiveBinding);
|
|
|
|
expect(() => {
|
|
fixture.detectChanges();
|
|
tick();
|
|
fixture.detectChanges();
|
|
}).not.toThrow();
|
|
|
|
expect(fixture.nativeElement.textContent).toContain('pizza is active');
|
|
}));
|
|
|
|
it('should not pick up mat-tab-label from a child tab', fakeAsync(() => {
|
|
const fixture = TestBed.createComponent(NestedTabGroupWithLabel);
|
|
fixture.detectChanges();
|
|
tick();
|
|
fixture.detectChanges();
|
|
|
|
const labels = fixture.nativeElement.querySelectorAll('.mdc-tab__text-label');
|
|
const contents = Array.from<HTMLElement>(labels).map(label => label.textContent?.trim());
|
|
|
|
expect(contents).toEqual([
|
|
'Parent 1',
|
|
'Parent 2',
|
|
'Parent 3',
|
|
'Child 1',
|
|
'Child 2',
|
|
'Child 3',
|
|
]);
|
|
}));
|
|
});
|
|
|
|
describe('nested tabs', () => {
|
|
it('should not pick up the tabs from descendant tab groups', fakeAsync(() => {
|
|
const fixture = TestBed.createComponent(NestedTabs);
|
|
fixture.detectChanges();
|
|
tick();
|
|
fixture.detectChanges();
|
|
|
|
const groups = fixture.componentInstance.groups.toArray();
|
|
|
|
expect(groups.length).toBe(2);
|
|
expect(groups[0]._tabs.map((tab: MatTab) => tab.textLabel)).toEqual(['One', 'Two']);
|
|
expect(groups[1]._tabs.map((tab: MatTab) => tab.textLabel)).toEqual([
|
|
'Inner tab one',
|
|
'Inner tab two',
|
|
]);
|
|
}));
|
|
|
|
it('should pick up indirect descendant tabs', fakeAsync(() => {
|
|
const fixture = TestBed.createComponent(TabGroupWithIndirectDescendantTabs);
|
|
fixture.detectChanges();
|
|
tick();
|
|
fixture.detectChanges();
|
|
|
|
const tabs = fixture.componentInstance.tabGroup._tabs;
|
|
expect(tabs.map((tab: MatTab) => tab.textLabel)).toEqual(['One', 'Two']);
|
|
}));
|
|
});
|
|
|
|
describe('tall tabs', () => {
|
|
beforeEach(() => {
|
|
window.scrollTo({top: 0});
|
|
});
|
|
|
|
it('should not scroll when changing tabs by clicking', fakeAsync(() => {
|
|
const fixture = TestBed.createComponent(TabGroupWithSpaceAbove);
|
|
fixture.detectChanges();
|
|
tick();
|
|
fixture.detectChanges();
|
|
|
|
window.scrollBy(0, 250);
|
|
expect(window.scrollY).toBe(250);
|
|
|
|
// select the second tab
|
|
let tabLabel = fixture.debugElement.queryAll(By.css('.mat-mdc-tab'))[1];
|
|
tabLabel.nativeElement.click();
|
|
checkSelectedIndex(1, fixture);
|
|
|
|
expect(window.scrollY).toBe(250);
|
|
tick();
|
|
}));
|
|
|
|
it('should not scroll when changing tabs programatically', fakeAsync(() => {
|
|
const fixture = TestBed.createComponent(TabGroupWithSpaceAbove);
|
|
fixture.detectChanges();
|
|
tick();
|
|
fixture.detectChanges();
|
|
|
|
window.scrollBy(0, 250);
|
|
expect(window.scrollY).toBe(250);
|
|
|
|
fixture.componentInstance.tabGroup.selectedIndex = 1;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(window.scrollY).toBe(250);
|
|
tick();
|
|
}));
|
|
});
|
|
|
|
describe('tabs with custom css classes', () => {
|
|
let fixture: ComponentFixture<TabsWithClassesTestApp>;
|
|
let labelElements: DebugElement[];
|
|
let bodyElements: DebugElement[];
|
|
|
|
beforeEach(() => {
|
|
fixture = TestBed.createComponent(TabsWithClassesTestApp);
|
|
fixture.detectChanges();
|
|
labelElements = fixture.debugElement.queryAll(By.css('.mdc-tab'));
|
|
bodyElements = fixture.debugElement.queryAll(By.css('mat-tab-body'));
|
|
});
|
|
|
|
it('should apply label/body classes', () => {
|
|
expect(labelElements[1].nativeElement.classList).toContain('hardcoded-label-class');
|
|
expect(bodyElements[1].nativeElement.classList).toContain('hardcoded-body-class');
|
|
});
|
|
|
|
it('should set classes as strings dynamically', () => {
|
|
expect(labelElements[0].nativeElement.classList).not.toContain('custom-label-class');
|
|
expect(bodyElements[0].nativeElement.classList).not.toContain('custom-body-class');
|
|
|
|
fixture.componentInstance.labelClassList = 'custom-label-class';
|
|
fixture.componentInstance.bodyClassList = 'custom-body-class';
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(labelElements[0].nativeElement.classList).toContain('custom-label-class');
|
|
expect(bodyElements[0].nativeElement.classList).toContain('custom-body-class');
|
|
|
|
delete fixture.componentInstance.labelClassList;
|
|
delete fixture.componentInstance.bodyClassList;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(labelElements[0].nativeElement.classList).not.toContain('custom-label-class');
|
|
expect(bodyElements[0].nativeElement.classList).not.toContain('custom-body-class');
|
|
});
|
|
|
|
it('should set classes as strings array dynamically', () => {
|
|
expect(labelElements[0].nativeElement.classList).not.toContain('custom-label-class');
|
|
expect(bodyElements[0].nativeElement.classList).not.toContain('custom-body-class');
|
|
|
|
fixture.componentInstance.labelClassList = ['custom-label-class'];
|
|
fixture.componentInstance.bodyClassList = ['custom-body-class'];
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(labelElements[0].nativeElement.classList).toContain('custom-label-class');
|
|
expect(bodyElements[0].nativeElement.classList).toContain('custom-body-class');
|
|
|
|
delete fixture.componentInstance.labelClassList;
|
|
delete fixture.componentInstance.bodyClassList;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
expect(labelElements[0].nativeElement.classList).not.toContain('custom-label-class');
|
|
expect(bodyElements[0].nativeElement.classList).not.toContain('custom-body-class');
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Checks that the `selectedIndex` has been updated; checks that the label and body have their
|
|
* respective `active` classes
|
|
*/
|
|
function checkSelectedIndex(expectedIndex: number, fixture: ComponentFixture<any>) {
|
|
fixture.detectChanges();
|
|
|
|
let tabComponent: MatTabGroup = fixture.debugElement.query(
|
|
By.css('mat-tab-group'),
|
|
).componentInstance;
|
|
expect(tabComponent.selectedIndex).toBe(expectedIndex);
|
|
|
|
let tabLabelElement = fixture.debugElement.query(
|
|
By.css(`.mat-mdc-tab:nth-of-type(${expectedIndex + 1})`),
|
|
).nativeElement;
|
|
expect(tabLabelElement.classList.contains('mdc-tab--active')).toBe(true);
|
|
|
|
let tabContentElement = fixture.debugElement.query(
|
|
By.css(`mat-tab-body:nth-of-type(${expectedIndex + 1})`),
|
|
).nativeElement;
|
|
expect(tabContentElement.classList.contains('mat-mdc-tab-body-active')).toBe(true);
|
|
}
|
|
|
|
function getSelectedLabel(fixture: ComponentFixture<any>): HTMLElement {
|
|
return fixture.nativeElement.querySelector('.mdc-tab--active');
|
|
}
|
|
|
|
function getSelectedContent(fixture: ComponentFixture<any>): HTMLElement {
|
|
return fixture.nativeElement.querySelector('.mat-mdc-tab-body-active');
|
|
}
|
|
});
|
|
|
|
describe('nested MatTabGroup with enabled animations', () => {
|
|
beforeEach(fakeAsync(() => {
|
|
TestBed.configureTestingModule({
|
|
imports: [
|
|
MatTabsModule,
|
|
BrowserAnimationsModule,
|
|
NestedTabs,
|
|
TabsWithCustomAnimationDuration,
|
|
],
|
|
});
|
|
}));
|
|
|
|
it('should not throw when creating a component with nested tab groups', fakeAsync(() => {
|
|
expect(() => {
|
|
let fixture = TestBed.createComponent(NestedTabs);
|
|
fixture.detectChanges();
|
|
tick();
|
|
}).not.toThrow();
|
|
}));
|
|
|
|
it('should not throw when setting an animationDuration without units', fakeAsync(() => {
|
|
expect(() => {
|
|
let fixture = TestBed.createComponent(TabsWithCustomAnimationDuration);
|
|
fixture.detectChanges();
|
|
tick();
|
|
}).not.toThrow();
|
|
}));
|
|
|
|
it('should set appropiate css variable given a specified animationDuration', fakeAsync(() => {
|
|
let fixture = TestBed.createComponent(TabsWithCustomAnimationDuration);
|
|
fixture.detectChanges();
|
|
tick();
|
|
|
|
const tabGroup = fixture.nativeElement.querySelector('.mat-mdc-tab-group');
|
|
expect(tabGroup.style.getPropertyValue('--mat-tab-animation-duration')).toBe('500ms');
|
|
}));
|
|
});
|
|
|
|
describe('MatTabGroup with ink bar fit to content', () => {
|
|
let fixture: ComponentFixture<TabGroupWithInkBarFitToContent>;
|
|
|
|
beforeEach(fakeAsync(() => {
|
|
TestBed.configureTestingModule({
|
|
imports: [MatTabsModule, BrowserAnimationsModule, TabGroupWithInkBarFitToContent],
|
|
});
|
|
}));
|
|
|
|
beforeEach(() => {
|
|
fixture = TestBed.createComponent(TabGroupWithInkBarFitToContent);
|
|
fixture.detectChanges();
|
|
});
|
|
|
|
it('should properly nest the ink bar when fit to content', () => {
|
|
const tabElement = fixture.nativeElement.querySelector('.mdc-tab');
|
|
const contentElement = tabElement.querySelector('.mdc-tab__content');
|
|
const indicatorElement = tabElement.querySelector('.mdc-tab-indicator');
|
|
expect(indicatorElement.parentElement).toBeTruthy();
|
|
expect(indicatorElement.parentElement).toBe(contentElement);
|
|
});
|
|
|
|
it('should be able to move the ink bar between content and full', () => {
|
|
fixture.componentInstance.fitInkBarToContent = false;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
const tabElement = fixture.nativeElement.querySelector('.mdc-tab');
|
|
const indicatorElement = tabElement.querySelector('.mdc-tab-indicator');
|
|
expect(indicatorElement.parentElement).toBeTruthy();
|
|
expect(indicatorElement.parentElement).toBe(tabElement);
|
|
|
|
fixture.componentInstance.fitInkBarToContent = true;
|
|
fixture.changeDetectorRef.markForCheck();
|
|
fixture.detectChanges();
|
|
|
|
const contentElement = tabElement.querySelector('.mdc-tab__content');
|
|
expect(indicatorElement.parentElement).toBeTruthy();
|
|
expect(indicatorElement.parentElement).toBe(contentElement);
|
|
});
|
|
});
|
|
|
|
describe('MatTabNavBar with a default config', () => {
|
|
let fixture: ComponentFixture<SimpleTabsTestApp>;
|
|
|
|
beforeEach(fakeAsync(() => {
|
|
TestBed.configureTestingModule({
|
|
imports: [MatTabsModule, BrowserAnimationsModule, SimpleTabsTestApp],
|
|
providers: [
|
|
{
|
|
provide: MAT_TABS_CONFIG,
|
|
useValue: {fitInkBarToContent: true, dynamicHeight: true},
|
|
},
|
|
],
|
|
});
|
|
}));
|
|
|
|
beforeEach(() => {
|
|
fixture = TestBed.createComponent(SimpleTabsTestApp);
|
|
fixture.detectChanges();
|
|
});
|
|
|
|
it('should set whether the ink bar fits to content', () => {
|
|
const tabElement = fixture.nativeElement.querySelector('.mdc-tab');
|
|
const contentElement = tabElement.querySelector('.mdc-tab__content');
|
|
const indicatorElement = tabElement.querySelector('.mdc-tab-indicator');
|
|
expect(indicatorElement.parentElement).toBeTruthy();
|
|
expect(indicatorElement.parentElement).toBe(contentElement);
|
|
});
|
|
|
|
it('should set whether the height of the tab group is dynamic', () => {
|
|
expect(fixture.componentInstance.tabGroup.dynamicHeight).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('MatTabGroup labels aligned with a config', () => {
|
|
it('should work with start align', () => {
|
|
const fixture = TestBed.configureTestingModule({
|
|
imports: [MatTabsModule, BrowserAnimationsModule, TabsWithAlignConfig],
|
|
providers: [
|
|
{
|
|
provide: MAT_TABS_CONFIG,
|
|
useValue: {alignTabs: 'start'},
|
|
},
|
|
],
|
|
}).createComponent(TabsWithAlignConfig);
|
|
fixture.detectChanges();
|
|
|
|
const tabElement = fixture.nativeElement.querySelector('[mat-align-tabs="start"]');
|
|
expect(tabElement).toBeTruthy();
|
|
});
|
|
|
|
it('should work with center align', () => {
|
|
const fixture = TestBed.configureTestingModule({
|
|
imports: [MatTabsModule, BrowserAnimationsModule, TabsWithAlignConfig],
|
|
providers: [
|
|
{
|
|
provide: MAT_TABS_CONFIG,
|
|
useValue: {alignTabs: 'center'},
|
|
},
|
|
],
|
|
}).createComponent(TabsWithAlignConfig);
|
|
fixture.detectChanges();
|
|
|
|
const tabElement = fixture.nativeElement.querySelector('[mat-align-tabs="center"]');
|
|
expect(tabElement).toBeTruthy();
|
|
});
|
|
|
|
it('should work with end align', () => {
|
|
const fixture = TestBed.configureTestingModule({
|
|
imports: [MatTabsModule, BrowserAnimationsModule, TabsWithAlignConfig],
|
|
providers: [
|
|
{
|
|
provide: MAT_TABS_CONFIG,
|
|
useValue: {alignTabs: 'end'},
|
|
},
|
|
],
|
|
}).createComponent(TabsWithAlignConfig);
|
|
fixture.detectChanges();
|
|
|
|
const tabElement = fixture.nativeElement.querySelector('[mat-align-tabs="end"]');
|
|
expect(tabElement).toBeTruthy();
|
|
});
|
|
|
|
it('should not add align if default config doesnt set align', () => {
|
|
const fixture = TestBed.configureTestingModule({
|
|
imports: [MatTabsModule, BrowserAnimationsModule, TabsWithAlignConfig],
|
|
}).createComponent(TabsWithAlignConfig);
|
|
fixture.detectChanges();
|
|
|
|
let tabElement = fixture.nativeElement.querySelector('[mat-align-tabs="start"]');
|
|
expect(tabElement).toBeFalsy();
|
|
|
|
tabElement = fixture.nativeElement.querySelector('[mat-align-tabs="center"]');
|
|
expect(tabElement).toBeFalsy();
|
|
|
|
tabElement = fixture.nativeElement.querySelector('[mat-align-tabs="end"]');
|
|
expect(tabElement).toBeFalsy();
|
|
|
|
tabElement = fixture.nativeElement.querySelector('.mat-mdc-tab-group');
|
|
expect(tabElement).toBeTruthy();
|
|
});
|
|
|
|
it('should not break if config sets align on already aligned tabs', () => {
|
|
const fixture = TestBed.configureTestingModule({
|
|
imports: [MatTabsModule, BrowserAnimationsModule, TabsWithAlignCenter],
|
|
providers: [{provide: MAT_TABS_CONFIG, useValue: {alignTabs: 'end'}}],
|
|
}).createComponent(TabsWithAlignCenter);
|
|
fixture.detectChanges();
|
|
|
|
let tabElement = fixture.nativeElement.querySelector('[mat-align-tabs="start"]');
|
|
expect(tabElement).toBeFalsy();
|
|
|
|
tabElement = fixture.nativeElement.querySelector('[mat-align-tabs="center"]');
|
|
expect(tabElement).toBeTruthy();
|
|
|
|
tabElement = fixture.nativeElement.querySelector('[mat-align-tabs="end"]');
|
|
expect(tabElement).toBeFalsy();
|
|
|
|
tabElement = fixture.nativeElement.querySelector('.mat-mdc-tab-group');
|
|
expect(tabElement).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-tab-group class="tab-group"
|
|
[(selectedIndex)]="selectedIndex"
|
|
[headerPosition]="headerPosition"
|
|
[disableRipple]="disableRipple"
|
|
[contentTabIndex]="contentTabIndex"
|
|
[aria-label]="ariaLabel"
|
|
[aria-labelledby]="ariaLabelledby"
|
|
(animationDone)="animationDone()"
|
|
(focusChange)="handleFocus($event)"
|
|
(selectedTabChange)="handleSelection($event)">
|
|
<mat-tab>
|
|
<ng-template mat-tab-label>Tab One</ng-template>
|
|
Tab one content
|
|
</mat-tab>
|
|
<mat-tab>
|
|
<ng-template mat-tab-label>Tab Two</ng-template>
|
|
<span>Tab </span><span>two</span><span>content</span>
|
|
</mat-tab>
|
|
<mat-tab>
|
|
<ng-template mat-tab-label>Tab Three</ng-template>
|
|
Tab three content
|
|
</mat-tab>
|
|
</mat-tab-group>
|
|
`,
|
|
standalone: true,
|
|
imports: [MatTabsModule],
|
|
})
|
|
class SimpleTabsTestApp {
|
|
@ViewChild(MatTabGroup) tabGroup: MatTabGroup;
|
|
@ViewChildren(MatTab) tabs: QueryList<MatTab>;
|
|
selectedIndex: number = 1;
|
|
focusEvent: any;
|
|
selectEvent: any;
|
|
disableRipple: boolean = false;
|
|
contentTabIndex: number | null = null;
|
|
headerPosition: MatTabHeaderPosition = 'above';
|
|
ariaLabel: string;
|
|
ariaLabelledby: string;
|
|
handleFocus(event: any) {
|
|
this.focusEvent = event;
|
|
}
|
|
handleSelection(event: any) {
|
|
this.selectEvent = event;
|
|
}
|
|
animationDone() {}
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-tab-group class="tab-group"
|
|
[(selectedIndex)]="selectedIndex"
|
|
(focusChange)="handleFocus($event)"
|
|
(selectedTabChange)="handleSelection($event)"
|
|
[disablePagination]="disablePagination">
|
|
@for (tab of tabs; track tab) {
|
|
<mat-tab>
|
|
<ng-template mat-tab-label>{{tab.label}}</ng-template>
|
|
{{tab.content}}
|
|
</mat-tab>
|
|
}
|
|
</mat-tab-group>
|
|
`,
|
|
standalone: true,
|
|
imports: [MatTabsModule],
|
|
})
|
|
class SimpleDynamicTabsTestApp {
|
|
tabs = [
|
|
{label: 'Label 1', content: 'Content 1'},
|
|
{label: 'Label 2', content: 'Content 2'},
|
|
{label: 'Label 3', content: 'Content 3'},
|
|
];
|
|
selectedIndex: number = 1;
|
|
focusEvent: any;
|
|
selectEvent: any;
|
|
disablePagination = false;
|
|
handleFocus(event: any) {
|
|
this.focusEvent = event;
|
|
}
|
|
handleSelection(event: any) {
|
|
this.selectEvent = event;
|
|
}
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-tab-group class="tab-group" [(selectedIndex)]="selectedIndex">
|
|
@for (tab of tabs; track tab) {
|
|
<mat-tab label="{{tab.label}}">{{tab.content}}</mat-tab>
|
|
}
|
|
</mat-tab-group>
|
|
`,
|
|
standalone: true,
|
|
imports: [MatTabsModule],
|
|
})
|
|
class BindedTabsTestApp {
|
|
tabs = [
|
|
{label: 'one', content: 'one'},
|
|
{label: 'two', content: 'two'},
|
|
];
|
|
selectedIndex = 0;
|
|
|
|
addNewActiveTab(): void {
|
|
this.tabs.push({
|
|
label: 'new tab',
|
|
content: 'new content',
|
|
});
|
|
this.selectedIndex = this.tabs.length - 1;
|
|
}
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-tab-group class="tab-group">
|
|
<mat-tab>
|
|
<ng-template mat-tab-label>Tab One</ng-template>
|
|
Tab one content
|
|
</mat-tab>
|
|
<mat-tab disabled>
|
|
<ng-template mat-tab-label>Tab Two</ng-template>
|
|
Tab two content
|
|
</mat-tab>
|
|
<mat-tab [disabled]="isDisabled">
|
|
<ng-template mat-tab-label>Tab Three</ng-template>
|
|
Tab three content
|
|
</mat-tab>
|
|
</mat-tab-group>
|
|
`,
|
|
standalone: true,
|
|
imports: [MatTabsModule],
|
|
})
|
|
class DisabledTabsTestApp {
|
|
@ViewChildren(MatTab) tabs: QueryList<MatTab>;
|
|
isDisabled = false;
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-tab-group class="tab-group">
|
|
@for (tab of tabs | async; track tab) {
|
|
<mat-tab>
|
|
<ng-template mat-tab-label>{{ tab.label }}</ng-template>
|
|
{{ tab.content }}
|
|
</mat-tab>
|
|
}
|
|
</mat-tab-group>
|
|
`,
|
|
standalone: true,
|
|
imports: [MatTabsModule, AsyncPipe],
|
|
})
|
|
class AsyncTabsTestApp implements OnInit {
|
|
private _tabs = [
|
|
{label: 'one', content: 'one'},
|
|
{label: 'two', content: 'two'},
|
|
];
|
|
|
|
tabs: Observable<any>;
|
|
|
|
ngOnInit() {
|
|
// Use ngOnInit because there is some issue with scheduling the async task in the constructor.
|
|
this.tabs = new Observable((observer: any) => {
|
|
setTimeout(() => observer.next(this._tabs));
|
|
});
|
|
}
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-tab-group [preserveContent]="preserveContent">
|
|
<mat-tab label="Junk food"> Pizza, fries </mat-tab>
|
|
<mat-tab label="Vegetables"> Broccoli, spinach </mat-tab>
|
|
<mat-tab [label]="otherLabel"> {{otherContent}} </mat-tab>
|
|
<mat-tab label="Legumes"> <p #legumes>Peanuts</p> </mat-tab>
|
|
</mat-tab-group>
|
|
`,
|
|
standalone: true,
|
|
imports: [MatTabsModule],
|
|
})
|
|
class TabGroupWithSimpleApi {
|
|
preserveContent = false;
|
|
otherLabel = 'Fruit';
|
|
otherContent = 'Apples, grapes';
|
|
@ViewChild('legumes') legumes: any;
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-tab-group>
|
|
<mat-tab label="One">Tab one content</mat-tab>
|
|
<mat-tab label="Two">
|
|
Tab two content
|
|
<mat-tab-group [dynamicHeight]="true">
|
|
<mat-tab label="Inner tab one">Inner content one</mat-tab>
|
|
<mat-tab label="Inner tab two">Inner content two</mat-tab>
|
|
</mat-tab-group>
|
|
</mat-tab>
|
|
</mat-tab-group>
|
|
`,
|
|
standalone: true,
|
|
imports: [MatTabsModule],
|
|
})
|
|
class NestedTabs {
|
|
@ViewChildren(MatTabGroup) groups: QueryList<MatTabGroup>;
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-tab-group>
|
|
<mat-tab label="One">
|
|
Eager
|
|
</mat-tab>
|
|
<mat-tab label="Two">
|
|
<ng-template matTabContent>
|
|
<div class="child">Hi</div>
|
|
</ng-template>
|
|
</mat-tab>
|
|
</mat-tab-group>
|
|
`,
|
|
standalone: true,
|
|
imports: [MatTabsModule],
|
|
})
|
|
class TemplateTabs {}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-tab-group>
|
|
<mat-tab [aria-label]="ariaLabel" [aria-labelledby]="ariaLabelledby"></mat-tab>
|
|
</mat-tab-group>
|
|
`,
|
|
standalone: true,
|
|
imports: [MatTabsModule],
|
|
})
|
|
class TabGroupWithAriaInputs {
|
|
ariaLabel: string;
|
|
ariaLabelledby: string;
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-tab-group>
|
|
<mat-tab label="Junk food" #pizza> Pizza, fries </mat-tab>
|
|
<mat-tab label="Vegetables"> Broccoli, spinach </mat-tab>
|
|
</mat-tab-group>
|
|
|
|
@if (pizza.isActive) {
|
|
<div>pizza is active</div>
|
|
}
|
|
`,
|
|
standalone: true,
|
|
imports: [MatTabsModule],
|
|
})
|
|
class TabGroupWithIsActiveBinding {}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-tab-group animationDuration="500">
|
|
<mat-tab label="One">Tab one content</mat-tab>
|
|
<mat-tab label="Two">Tab two content</mat-tab>
|
|
</mat-tab-group>
|
|
`,
|
|
standalone: true,
|
|
imports: [MatTabsModule],
|
|
})
|
|
class TabsWithCustomAnimationDuration {}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-tab-group>
|
|
@if (true) {
|
|
<mat-tab label="One">Tab one content</mat-tab>
|
|
<mat-tab label="Two">Tab two content</mat-tab>
|
|
}
|
|
</mat-tab-group>
|
|
`,
|
|
standalone: true,
|
|
imports: [MatTabsModule],
|
|
})
|
|
class TabGroupWithIndirectDescendantTabs {
|
|
@ViewChild(MatTabGroup) tabGroup: MatTabGroup;
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-tab-group [fitInkBarToContent]="fitInkBarToContent">
|
|
<mat-tab label="One">Tab one content</mat-tab>
|
|
<mat-tab label="Two">Tab two content</mat-tab>
|
|
</mat-tab-group>
|
|
`,
|
|
standalone: true,
|
|
imports: [MatTabsModule],
|
|
})
|
|
class TabGroupWithInkBarFitToContent {
|
|
fitInkBarToContent = true;
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<div style="height: 300px; background-color: aqua">
|
|
Top Content here
|
|
</div>
|
|
<mat-tab-group>
|
|
<ng-container>
|
|
<mat-tab label="One">
|
|
<div style="height: 3000px; background-color: red"></div>
|
|
</mat-tab>
|
|
<mat-tab label="Two">
|
|
<div style="height: 3000px; background-color: green"></div>
|
|
</mat-tab>
|
|
</ng-container>
|
|
</mat-tab-group>
|
|
`,
|
|
standalone: true,
|
|
imports: [MatTabsModule],
|
|
})
|
|
class TabGroupWithSpaceAbove {
|
|
@ViewChild(MatTabGroup) tabGroup: MatTabGroup;
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-tab-group>
|
|
<mat-tab label="Parent 1">
|
|
<mat-tab-group>
|
|
<mat-tab label="Child 1">Content 1</mat-tab>
|
|
<mat-tab>
|
|
<ng-template mat-tab-label>Child 2</ng-template>
|
|
Content 2
|
|
</mat-tab>
|
|
<mat-tab label="Child 3">Child 3</mat-tab>
|
|
</mat-tab-group>
|
|
</mat-tab>
|
|
<mat-tab label="Parent 2">Parent 2</mat-tab>
|
|
<mat-tab label="Parent 3">Parent 3</mat-tab>
|
|
</mat-tab-group>
|
|
`,
|
|
standalone: true,
|
|
imports: [MatTabsModule],
|
|
})
|
|
class NestedTabGroupWithLabel {}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-tab-group class="tab-group">
|
|
<mat-tab label="Tab One" [labelClass]="labelClassList" [bodyClass]="bodyClassList">
|
|
Tab one content
|
|
</mat-tab>
|
|
<mat-tab label="Tab Two" labelClass="hardcoded-label-class"
|
|
bodyClass="hardcoded-body-class">
|
|
Tab two content
|
|
</mat-tab>
|
|
</mat-tab-group>
|
|
`,
|
|
standalone: true,
|
|
imports: [MatTabsModule],
|
|
})
|
|
class TabsWithClassesTestApp {
|
|
labelClassList?: string | string[];
|
|
bodyClassList?: string | string[];
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-tab-group>
|
|
<mat-tab>
|
|
First
|
|
</mat-tab>
|
|
<mat-tab>
|
|
Second
|
|
</mat-tab>
|
|
</mat-tab-group>
|
|
`,
|
|
standalone: true,
|
|
imports: [MatTabsModule],
|
|
})
|
|
class TabsWithAlignConfig {}
|
|
|
|
@Component({
|
|
template: `
|
|
<mat-tab-group mat-align-tabs="center">
|
|
<mat-tab>
|
|
First
|
|
</mat-tab>
|
|
<mat-tab>
|
|
Second
|
|
</mat-tab>
|
|
</mat-tab-group>
|
|
`,
|
|
standalone: true,
|
|
imports: [MatTabsModule],
|
|
})
|
|
class TabsWithAlignCenter {}
|