Skip to content

Commit

Permalink
fix(core): active tab does not have class _active (#7355)
Browse files Browse the repository at this point in the history
  • Loading branch information
al-march committed May 6, 2024
1 parent c44b374 commit 9925c6f
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 8 deletions.
28 changes: 20 additions & 8 deletions projects/kit/components/tabs/tabs.directive.ts
@@ -1,10 +1,12 @@
import type {AfterViewChecked} from '@angular/core';
import {
afterNextRender,
Directive,
ElementRef,
EventEmitter,
HostListener,
inject,
INJECTOR,
Input,
Output,
} from '@angular/core';
Expand All @@ -24,6 +26,7 @@ import {TUI_TABS_OPTIONS} from './tabs.options';
})
export class TuiTabsDirective implements AfterViewChecked {
private readonly el: HTMLElement = inject(ElementRef).nativeElement;
private readonly injector = inject(INJECTOR);

@Input()
public size: TuiSizeL = inject(TUI_TABS_OPTIONS).size;
Expand Down Expand Up @@ -51,14 +54,12 @@ export class TuiTabsDirective implements AfterViewChecked {
}

public ngAfterViewChecked(): void {
const {tabs, activeElement} = this;

tabs.forEach(nativeElement => {
const active = nativeElement === activeElement;

nativeElement.classList.toggle('_active', active);
nativeElement.setAttribute('tabIndex', active ? '0' : '-1');
});
afterNextRender(
() => {
this.markTabAsActive();
},
{injector: this.injector},
);
}

@HostListener(TUI_TAB_ACTIVATE, ['$event', '$event.target'])
Expand All @@ -74,4 +75,15 @@ export class TuiTabsDirective implements AfterViewChecked {
this.activeItemIndexChange.emit(index);
this.activeItemIndex = index;
}

protected markTabAsActive(): void {
const {tabs, activeElement} = this;

tabs.forEach(nativeElement => {
const active = nativeElement === activeElement;

nativeElement.classList.toggle('_active', active);
nativeElement.setAttribute('tabIndex', active ? '0' : '-1');
});
}
}
99 changes: 99 additions & 0 deletions projects/kit/components/tabs/test/tabs.component.spec.ts
@@ -0,0 +1,99 @@
import {AsyncPipe, CommonModule} from '@angular/common';
import type {DebugElement} from '@angular/core';
import {Component, Input, ViewChild} from '@angular/core';
import type {ComponentFixture} from '@angular/core/testing';
import {TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {
TuiTabDirective,
TuiTabsDirective,
TuiTabsHorizontalDirective,
} from '@taiga-ui/kit';

describe('Tabs', () => {
@Component({
standalone: true,
imports: [AsyncPipe, CommonModule, TuiTabDirective, TuiTabsHorizontalDirective],
template: `
<tui-tabs [(activeItemIndex)]="activeItemIndex">
<button
*ngFor="let tab of tabs"
tuiTab
>
{{ tab }}
</button>
</tui-tabs>
`,
})
class TestComponent {
@ViewChild(TuiTabsHorizontalDirective, {static: true})
public tabsHorizontalDirective!: TuiTabsHorizontalDirective;

@ViewChild(TuiTabsDirective, {static: true})
public tabsDirective!: TuiTabsDirective;

@Input()
public activeItemIndex = 0;

@Input()
public tabs = [1, 2, 3];
}

let fixture: ComponentFixture<TestComponent>;
let component: TestComponent;

beforeEach(async () => {
TestBed.configureTestingModule({
imports: [TestComponent],
});
await TestBed.compileComponents();

fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

function getTabs(): DebugElement[] {
return fixture.debugElement.queryAll(By.css('[tuiTab]'));
}

describe('Tabs component', () => {
it('Should render all tabs', () => {
const tabs = [1, 2, 3, 4, 5];

fixture.componentRef.setInput('tabs', tabs);
fixture.detectChanges();

expect(getTabs().length).toBe(tabs.length);
});

it('When you click on a tab it changes the active tab', () => {
const [firstTab, secondTab] = getTabs().map(
tab => tab.nativeElement as HTMLButtonElement,
);

expect(firstTab).toEqual(component.tabsDirective.activeElement);

secondTab.click();
fixture.detectChanges();

expect(firstTab).not.toEqual(component.tabsDirective.activeElement);
expect(secondTab).toEqual(component.tabsDirective.activeElement);
});

it('When a tab is active, it has the class _active', () => {
const [firstTab, secondTab] = getTabs().map(
tab => tab.nativeElement as HTMLButtonElement,
);

expect(firstTab.classList.contains('_active')).toBeTruthy();
expect(secondTab.classList.contains('_active')).toBeFalsy();

secondTab.click();
fixture.detectChanges();

expect(firstTab.classList.contains('_active')).toBeFalsy();
expect(secondTab.classList.contains('_active')).toBeTruthy();
});
});
});

0 comments on commit 9925c6f

Please sign in to comment.