import { Component, ViewChild } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { TabsetConfig, TabsModule, TabsetComponent } from '../index'; @Component({ selector: 'tabs-test', template: '' }) class TestTabsetComponent { isVertical = false; isJustified = false; // eslint-disable-next-line @typescript-eslint/no-explicit-any tabs: any[] = [ { title: 'tab1', content: 'tab1 content', customClass: 'testCustomClass' }, { title: 'tab2', content: 'tab2 content', disabled: true }, { title: 'tab3', content: 'tab3 content', removable: true } ]; @ViewChild('tabset', { static: false }) tabset?: TabsetComponent; constructor(config: TabsetConfig) { Object.assign(this, config); } _select(e: TabsModule): TabsModule { return e; } _deselect(e: TabsModule): TabsModule { return e; } _removed(e: TabsModule): TabsModule { return e; } } const html = ` tab0 content {{ tab.content }} `; function getTabItems(nativeEl: HTMLElement): NodeListOf { return nativeEl.querySelectorAll('.nav-item'); } function getTabTitles(nativeEl: HTMLElement): NodeListOf { return nativeEl.querySelectorAll('.nav-link'); } function getTabContent(nativeEl: HTMLElement): NodeListOf { return nativeEl.querySelectorAll('.tab-content .tab-pane'); } function expectActiveTabs(nativeEl: HTMLElement, active: boolean[]): void { const tabItems = getTabItems(nativeEl); const tabContent = getTabContent(nativeEl); expect(tabItems.length).toBe(active.length); expect(tabContent.length).toBe(active.length); for (let i = 0; i < active.length; i++) { if (active[i]) { expect(tabItems[i].classList).toContain('active'); expect(tabContent[i].classList).toContain('active'); } else { expect(tabItems[i].classList).not.toContain('active'); expect(tabContent[i].classList).not.toContain('active'); } } } describe('Component: Tabs', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any let fixture: ComponentFixture; // eslint-disable-next-line @typescript-eslint/no-explicit-any let context: any; // eslint-disable-next-line @typescript-eslint/no-explicit-any let element: any; // beforeEach(waitForAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { // return tcb // .overrideTemplate(TestTabsetComponent, html) // .createAsync(TestTabsetComponent) // .then((f: ComponentFixture) => { // fixture = f; // context = fixture.componentInstance; // jest.spyOn(context, '_select'); // jest.spyOn(context, '_deselect'); // jest.spyOn(context, '_removed'); // element = fixture.nativeElement; // fixture.detectChanges(); // }); // }))); beforeEach(() => { TestBed.configureTestingModule({ declarations: [TestTabsetComponent], imports: [TabsModule.forRoot()] }); TestBed.overrideComponent(TestTabsetComponent, { set: { template: html } }); fixture = TestBed.createComponent(TestTabsetComponent); context = fixture.componentInstance; jest.spyOn(context, '_select'); jest.spyOn(context, '_deselect'); jest.spyOn(context, '_removed'); element = fixture.nativeElement; fixture.detectChanges(); }); it('should select first tab as active by default', () => { expectActiveTabs(element, [true, false, false, false]); }); it('should set tab header', () => { const str = 'testing title'; context.tabs[1].title = str; fixture.detectChanges(); const tabTitles = getTabTitles(element); expect( (tabTitles[2] as HTMLAnchorElement).getElementsByTagName('span')[0] .textContent ).toBe(str); }); it('should mark the requested tab as active', () => { context.tabs[0].active = true; fixture.detectChanges(); expectActiveTabs(element, [false, true, false, false]); }); it('should ignore click on disabled tab', () => { const tabTitles = getTabTitles(element); (tabTitles[2] as HTMLAnchorElement).click(); fixture.detectChanges(); expectActiveTabs(element, [true, false, false, false]); }); it('should appear additional button if removable is true', () => { const tabTitles = getTabTitles(element); expect( (tabTitles[3] as HTMLAnchorElement).querySelectorAll( 'span.bs-remove-tab' ).length ).toEqual(1); }); it('should remove tab on click on remove icon', () => { const tabTitlesBefore = getTabTitles(element); expect(tabTitlesBefore.length).toEqual(4); const el = (tabTitlesBefore[3] as HTMLAnchorElement).querySelectorAll( 'span.bs-remove-tab' )[0]; (el as HTMLSpanElement).click(); fixture.detectChanges(); const tabTitlesAfter = getTabTitles(element); expect(tabTitlesAfter.length).toEqual(3); }); it('should select another tab if the active tab is removed', () => { context.tabset.tabs[0].active = true; context.tabset.removeTab(context.tabset.tabs[0]); fixture.detectChanges(); expectActiveTabs(element, [true, false, false]); }); it('should not select another tab if the active tab is removed and reselect is set to false', () => { context.tabset.tabs[0].active = true; context.tabset.removeTab(context.tabset.tabs[0], { reselect: false, emit: false }); fixture.detectChanges(); expectActiveTabs(element, [false, false, false]); }); it('should set tab as active on click and disable another active', () => { const tabTitles = getTabTitles(element); (tabTitles[1] as HTMLAnchorElement).click(); fixture.detectChanges(); expectActiveTabs(element, [false, true, false, false]); (tabTitles[0] as HTMLAnchorElement).click(); fixture.detectChanges(); expectActiveTabs(element, [true, false, false, false]); }); it('should have only one active tab if several marked as active', () => { context.tabs[0].active = true; context.tabs[1].active = true; fixture.detectChanges(); expectActiveTabs(element, [false, true, false, false]); }); it('should add class nav-stacked for vertical mode', () => { expect(element.querySelectorAll('ul.nav')[0].classList).not.toContain( 'nav-stacked' ); context.isVertical = true; fixture.detectChanges(); expect(element.querySelectorAll('ul.nav')[0].classList).toContain( 'nav-stacked' ); }); it('should add class nav-justified for justified', () => { expect(element.querySelector('ul.nav').classList).not.toContain( 'nav-justified' ); context.isJustified = true; fixture.detectChanges(); expect(element.querySelector('ul.nav').classList).toContain( 'nav-justified' ); }); it('should emit select/deselect', () => { const tabTitles = getTabTitles(element); (tabTitles[1] as HTMLAnchorElement).click(); fixture.detectChanges(); expect(context._deselect).toHaveBeenCalled(); expect(context._select).toHaveBeenCalledWith( expect.objectContaining({ heading: 'tab1' }) ); }); it('should emit remove on remove tab', () => { const tabTitles = getTabTitles(element); const el = (tabTitles[3] as HTMLAnchorElement).querySelectorAll( 'span.bs-remove-tab' )[0]; (el as HTMLSpanElement).click(); fixture.detectChanges(); expect(context._removed).toHaveBeenCalledWith( expect.objectContaining({ heading: 'tab3' }) ); }); it('should set class on a tab item through [customClass]', () => { const tabItems = getTabItems(element); expect(tabItems[1].classList).toContain('testCustomClass'); }); it('should prevent interference of [customClass] and state classes', () => { const tabItems = getTabItems(element); const tabTitles = getTabTitles(element); expect(tabItems[1].classList).toContain('testCustomClass'); (tabTitles[1] as HTMLAnchorElement).click(); fixture.detectChanges(); expect(tabItems[1].classList).toContain('testCustomClass'); expectActiveTabs(element, [false, true, false, false]); context.tabs[0].customClass = 'otherCustomClass'; fixture.detectChanges(); expect(tabItems[1].classList).not.toContain('testCustomClass'); expect(tabItems[1].classList).toContain('otherCustomClass'); expectActiveTabs(element, [false, true, false, false]); }); });