import { Component, ViewChild } from "@angular/core";
import {
ComponentFixture,
TestBed,
fakeAsync,
tick,
} from "@angular/core/testing";
import { NoopAnimationsModule } from "@angular/platform-browser/animations";
import { RIGHT_ARROW } from "@angular/cdk/keycodes";
import { SamTabsNextModule, MdTabGroup, MdTabChangeEvent } from "./index";
@Component({
template: `
Content One
Content Two
Content Three
`,
standalone: false,
})
class HostComponent {
selectedIndex = 0;
@ViewChild(MdTabGroup) tabGroup: MdTabGroup;
}
@Component({
template: `
Custom Label
Content One
Content Two
`,
standalone: false,
})
class TemplateLabelHostComponent {
selectedIndex = 0;
@ViewChild(MdTabGroup) tabGroup: MdTabGroup;
}
describe("The Sam Tabs Next component", () => {
let fixture: ComponentFixture;
let host: HostComponent;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [HostComponent],
imports: [SamTabsNextModule, NoopAnimationsModule],
});
fixture = TestBed.createComponent(HostComponent);
host = fixture.componentInstance;
});
it("should render a label for each tab without throwing", () => {
expect(() => fixture.detectChanges()).not.toThrow();
fixture.detectChanges();
const labels = fixture.nativeElement.querySelectorAll(".mat-tab-label");
expect(labels.length).toBe(3);
expect(host.tabGroup.selectedIndex).toBe(0);
});
it("should clamp an out-of-range selectedIndex to the last tab", () => {
host.selectedIndex = 10;
fixture.detectChanges();
fixture.detectChanges();
expect(host.tabGroup.selectedIndex).toBe(2);
});
it("should emit selectChange with the newly selected tab when selectedIndex changes", fakeAsync(() => {
fixture.detectChanges();
fixture.detectChanges();
const emitted: MdTabChangeEvent[] = [];
host.tabGroup.selectChange.subscribe((event) => emitted.push(event));
host.selectedIndex = 1;
fixture.detectChanges();
fixture.detectChanges();
tick();
expect(emitted.length).toBe(1);
expect(emitted[0].index).toBe(1);
}));
it("should emit selectedIndexChange derived from selectChange", fakeAsync(() => {
fixture.detectChanges();
fixture.detectChanges();
const emitted: number[] = [];
host.tabGroup.selectedIndexChange.subscribe((index) => emitted.push(index));
host.selectedIndex = 2;
fixture.detectChanges();
fixture.detectChanges();
tick();
expect(emitted).toEqual([2]);
}));
it("should emit focusChange when a label wrapper reports a focus change", () => {
fixture.detectChanges();
fixture.detectChanges();
const emitted: MdTabChangeEvent[] = [];
host.tabGroup.focusChange.subscribe((event) => emitted.push(event));
const tabListContainer = fixture.nativeElement.querySelector(
".mat-tab-label-container"
) as HTMLElement;
const event = new KeyboardEvent("keydown", {
bubbles: true,
cancelable: true,
});
Object.defineProperty(event, "keyCode", { get: () => RIGHT_ARROW });
tabListContainer.dispatchEvent(event);
expect(emitted.length).toBe(1);
expect(emitted[0].index).toBe(1);
});
it("should build unique label and content ids per tab index, reflected on the rendered DOM", () => {
fixture.detectChanges();
fixture.detectChanges();
const labels = fixture.nativeElement.querySelectorAll(".mat-tab-label");
expect(labels[0].id).not.toBe(labels[1].id);
expect(labels[0].getAttribute("aria-controls")).toContain(
"md-tab-content-"
);
});
it("should coerce the dynamicHeight input to a boolean", () => {
fixture.detectChanges();
host.tabGroup.dynamicHeight = "false" as unknown as boolean;
expect(host.tabGroup.dynamicHeight).toBe(false);
host.tabGroup.dynamicHeight = true;
expect(host.tabGroup.dynamicHeight).toBe(true);
});
it("should select a tab label via keyboard (Enter) same as click", () => {
fixture.detectChanges();
fixture.detectChanges();
const labels = fixture.nativeElement.querySelectorAll(".mat-tab-label");
labels[1].dispatchEvent(new KeyboardEvent("keydown", { key: "Enter" }));
fixture.detectChanges();
expect(host.tabGroup.selectedIndex).toBe(1);
});
it("should select a tab label via keyboard (Space) same as click, and prevent page scroll", () => {
fixture.detectChanges();
fixture.detectChanges();
const labels = fixture.nativeElement.querySelectorAll(".mat-tab-label");
const spaceEvent = new KeyboardEvent("keydown", { key: " " });
const preventDefaultSpy = vi.spyOn(spaceEvent, "preventDefault");
labels[1].dispatchEvent(spaceEvent);
fixture.detectChanges();
expect(host.tabGroup.selectedIndex).toBe(1);
expect(preventDefaultSpy).toHaveBeenCalled();
});
it("should not activate a disabled tab via click or keyboard, and should remove it from the tab order", () => {
fixture.detectChanges();
host.tabGroup._tabs.toArray()[1].disabled = true;
fixture.detectChanges();
fixture.detectChanges();
const labels = fixture.nativeElement.querySelectorAll(".mat-tab-label");
const disabledLabel = labels[1] as HTMLElement;
expect(disabledLabel.getAttribute("tabindex")).toBe("-1");
disabledLabel.dispatchEvent(new MouseEvent("click"));
disabledLabel.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter" }));
fixture.detectChanges();
expect(host.tabGroup.selectedIndex).toBe(0);
});
it("should render an as the tab's label content via the CdkPortalOutlet", () => {
TestBed.resetTestingModule();
TestBed.configureTestingModule({
declarations: [TemplateLabelHostComponent],
imports: [SamTabsNextModule, NoopAnimationsModule],
});
const templateFixture = TestBed.createComponent(TemplateLabelHostComponent);
templateFixture.detectChanges();
templateFixture.detectChanges();
const labels =
templateFixture.nativeElement.querySelectorAll(".mat-tab-label");
expect(labels[0].querySelector(".custom-label")?.textContent).toBe(
"Custom Label"
);
expect(labels[1].textContent).toContain("Tab Two");
});
});