Skip to content

Commit de0dbf4

Browse files
pedrodominguespCSimoesJr
authored andcommitted
feat(tabs): adiciona reordenação visual das abas
Permite a reordenação das abas visíveis quando uma aba do dropdown é selecionada. Ao selecionar uma aba do dropdown, ela se torna visível, e a última aba visível anteriormente é movida para o dropdown. Isso proporciona uma melhor experiência de usuário ao gerenciar um grande número de abas. Fixes DTHFUI-8578
1 parent d234be4 commit de0dbf4

File tree

7 files changed

+250
-9
lines changed

7 files changed

+250
-9
lines changed

projects/ui/src/lib/components/po-tabs/po-tab/po-tab-base.component.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export abstract class PoTabBaseComponent {
2626
private _active?: boolean = false;
2727
private _disabled?: boolean = false;
2828
private _hide?: boolean = false;
29+
widthButton;
2930

3031
/**
3132
* @optional

projects/ui/src/lib/components/po-tabs/po-tab/po-tab.component.spec.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testin
22

33
import { PoTabsService } from '../po-tabs.service';
44
import { PoTabComponent } from './po-tab.component';
5+
import { SimpleChange, SimpleChanges } from '@angular/core';
56

67
describe('PoTabComponent:', () => {
78
let component: PoTabComponent;
@@ -48,7 +49,11 @@ describe('PoTabComponent:', () => {
4849
it('should trigger onChanges after 100ms delay', fakeAsync(() => {
4950
spyOn(tabsService, 'triggerOnChanges');
5051

51-
component.ngOnChanges();
52+
const changes: SimpleChanges = {
53+
active: new SimpleChange(null, true, true)
54+
};
55+
56+
component.ngOnChanges(changes);
5257
tick(101);
5358

5459
expect(tabsService.triggerOnChanges).toHaveBeenCalled();

projects/ui/src/lib/components/po-tabs/po-tab/po-tab.component.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,12 @@ export class PoTabComponent extends PoTabBaseComponent implements AfterContentIn
2222
this.setDisplayOnActive();
2323
}
2424

25-
ngOnChanges(): void {
25+
ngOnChanges(changes: SimpleChanges): void {
2626
setTimeout(() => {
2727
this.tabsService.triggerOnChanges();
28+
if (changes?.active?.currentValue) {
29+
this.tabsService.triggerActiveOnChanges(this);
30+
}
2831
}, 100);
2932
}
3033

projects/ui/src/lib/components/po-tabs/po-tabs.component.html

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,8 @@
3030
[p-label]="literals.moreTabs"
3131
[p-small]="small"
3232
[p-tabs]="overflowedTabs"
33-
(p-activated)="onTabActive($event)"
3433
(p-change-state)="onTabChangeState($event)"
35-
(p-click)="selectedTab($event)"
34+
(p-click)="onTabActiveByDropdown($event)"
3635
>
3736
</po-tab-dropdown>
3837
</div>

projects/ui/src/lib/components/po-tabs/po-tabs.component.spec.ts

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { By } from '@angular/platform-browser';
33

44
import { PoTabsComponent } from './po-tabs.component';
55
import { PoTabsService } from './po-tabs.service';
6+
import { PoTabComponent } from './po-tab/po-tab.component';
7+
import { EventEmitter, QueryList } from '@angular/core';
68

79
describe('PoTabsComponent:', () => {
810
let component: PoTabsComponent;
@@ -49,13 +51,28 @@ describe('PoTabsComponent:', () => {
4951
expect(component.handleKeyboardNavigationTab).toHaveBeenCalled();
5052
});
5153

54+
it('ngOnInit: should call `onTabActiveByDropdown` if tab is active in tabsDropdown', () => {
55+
const tabMock = jasmine.createSpyObj('PoTabComponent', ['id'], { id: 'tab-id' });
56+
57+
spyOn(component, 'onTabActiveByDropdown');
58+
59+
component.tabsDropdown = [tabMock];
60+
component.ngOnInit();
61+
62+
component['tabsService'].triggerActiveOnChanges(tabMock);
63+
64+
expect(component.onTabActiveByDropdown).toHaveBeenCalledWith(tabMock);
65+
});
66+
5267
it('ngOnInit: should unsubscribe', () => {
5368
spyOn(component['subscription'], 'unsubscribe');
5469
spyOn(component['subscriptionTabsService'], 'unsubscribe');
70+
spyOn(component['subscriptionTabActive'], 'unsubscribe');
5571
component.ngOnDestroy();
5672

5773
expect(component['subscription'].unsubscribe).toHaveBeenCalled();
5874
expect(component['subscriptionTabsService'].unsubscribe).toHaveBeenCalled();
75+
expect(component['subscriptionTabActive'].unsubscribe).toHaveBeenCalled();
5976
});
6077

6178
it('isVisibleTab: should return `false` if `visibleTabs` is greater than `maxNumberOfTabs`', () => {
@@ -498,6 +515,130 @@ describe('PoTabsComponent:', () => {
498515
component.setQuantityTabsButton(1);
499516
expect(component.quantityTabsButton).toBe(1);
500517
});
518+
519+
it('onTabActiveByDropdown: should correctly call methods and update styles when called', () => {
520+
const tabMock = jasmine.createSpyObj('PoTabComponent', ['id'], { id: 'tab-id' });
521+
component.defaultLastTabWidth = 100;
522+
523+
const nativeElementMock = { style: { width: '' }, getBoundingClientRect: () => ({ width: 100 }) };
524+
const queryListMock = jasmine.createSpyObj('QueryList', ['last'], { last: { nativeElement: nativeElementMock } });
525+
queryListMock.last = { nativeElement: nativeElementMock };
526+
component.tabButton = queryListMock;
527+
528+
component.tabsChildren = new QueryList<PoTabComponent>();
529+
component.tabsChildren.reset([tabMock]);
530+
531+
component.tabsDefault = [tabMock];
532+
component.tabsDropdown = [tabMock];
533+
534+
spyOn(component, 'handleKeyboardNavigationTab');
535+
536+
component.onTabActiveByDropdown(tabMock);
537+
538+
expect(component.tabButton.last.nativeElement.style.width).toBe('100px');
539+
expect(component.handleKeyboardNavigationTab).toHaveBeenCalled();
540+
expect(component.tabsDefault.includes(tabMock)).toBe(true);
541+
expect(component.tabsDropdown.length).toBe(1);
542+
});
543+
544+
it('reorderTabs: should reorder tabs if tabIndex is not -1', () => {
545+
const tabMock = jasmine.createSpyObj('PoTabComponent', ['id'], { id: 'tab4', widthButton: '130' });
546+
const tabsArrayMock = [
547+
jasmine.createSpyObj('PoTabComponent', ['id'], { id: 'tab1', widthButton: '100' }),
548+
jasmine.createSpyObj('PoTabComponent', ['id'], { id: 'tab2', widthButton: '110' }),
549+
jasmine.createSpyObj('PoTabComponent', ['id'], { id: 'tab3', widthButton: '120' }),
550+
tabMock
551+
];
552+
553+
const tabsChildrenMock = new QueryList<PoTabComponent>();
554+
tabsChildrenMock.reset(tabsArrayMock);
555+
Object.defineProperty(component, 'tabsChildren', { value: tabsChildrenMock });
556+
557+
component.quantityTabsButton = 3;
558+
559+
spyOn(component, <any>'changeTabPositionByDropdown').and.callFake((tab: PoTabComponent) => {
560+
const tabIndex = component.tabsChildren.toArray().indexOf(tab);
561+
if (tabIndex !== -1) {
562+
const reorderedTabs = component.tabsChildren.toArray();
563+
reorderedTabs.splice(tabIndex, 1);
564+
reorderedTabs.splice(component.quantityTabsButton - 1, 0, tab);
565+
component.tabsChildren.reset(reorderedTabs);
566+
}
567+
});
568+
569+
component.onTabActiveByDropdown(tabMock);
570+
571+
fixture.detectChanges();
572+
573+
const reorderedTabs = component.tabsChildren.toArray();
574+
575+
expect(reorderedTabs.map(tab => tab.id)).toEqual(['tab1', 'tab2', 'tab4', 'tab3']);
576+
expect(component.tabButton.last.nativeElement.style.width).toBe('130px');
577+
});
578+
579+
it('updateTabsState: should update tabs state correctly', () => {
580+
const nativeElementMock = {
581+
getBoundingClientRect: () => ({ width: 100 })
582+
};
583+
component.quantityTabsButton = 2;
584+
585+
const queryListMock = jasmine.createSpyObj('QueryList', [], { last: { nativeElement: nativeElementMock } });
586+
component.tabButton = queryListMock;
587+
588+
const tabsArrayMock = [
589+
jasmine.createSpyObj('PoTabComponent', ['id'], { id: 'tab1', widthButton: 0 }),
590+
jasmine.createSpyObj('PoTabComponent', ['id'], { id: 'tab2', widthButton: 100 }),
591+
jasmine.createSpyObj('PoTabComponent', ['id'], { id: 'tab3', widthButton: 0 })
592+
];
593+
594+
const tabsChildrenMock = new QueryList<PoTabComponent>();
595+
tabsChildrenMock.reset(tabsArrayMock);
596+
Object.defineProperty(component, 'tabsChildrenArray', { value: tabsArrayMock });
597+
component.quantityTabsButton = 2;
598+
599+
(component as any).updateTabsState();
600+
601+
expect(component.defaultLastTabWidth).toBe(100);
602+
expect(tabsArrayMock[1].widthButton).toBe(100);
603+
expect(component.tabsDefault).toEqual(tabsArrayMock.slice(0, 2));
604+
expect(component.tabsDropdown).toEqual(tabsArrayMock.slice(2));
605+
});
606+
607+
it('selectedTab: should correctly handle a tab in the dropdown', () => {
608+
const clickEventMock = new EventEmitter<any>();
609+
component.quantityTabsButton = 2;
610+
611+
const tabMock1 = { id: 'tab1', active: false, click: clickEventMock };
612+
const tabMock2 = { id: 'tab2', active: false, click: clickEventMock };
613+
const tabMockInDropdown = { id: 'tab3', active: false, click: clickEventMock };
614+
615+
component['tabsDefault'] = [tabMock1, tabMock2];
616+
component['tabsDropdown'] = [tabMockInDropdown];
617+
618+
spyOn(component, 'onTabActiveByDropdown');
619+
const changeDetectorSpy = spyOn(component['changeDetector'], 'detectChanges');
620+
621+
component.selectedTab(tabMockInDropdown);
622+
623+
expect(tabMockInDropdown.active).toBeTrue();
624+
expect(changeDetectorSpy).toHaveBeenCalled();
625+
clickEventMock.subscribe(tab => {
626+
expect(tab).toBe(tabMockInDropdown);
627+
});
628+
clickEventMock.emit(tabMockInDropdown);
629+
});
630+
631+
it('updateTabsState: should set defaultLastTabWidth to 0 if getBoundingClientRect().width is <= 0', () => {
632+
const nativeElementMock = {
633+
getBoundingClientRect: () => ({ width: 0 })
634+
};
635+
const queryListMock = jasmine.createSpyObj('QueryList', [], { last: { nativeElement: nativeElementMock } });
636+
component.tabButton = queryListMock;
637+
638+
(component as any).updateTabsState();
639+
640+
expect(component.defaultLastTabWidth).toBe(0);
641+
});
501642
});
502643

503644
describe('Templates:', () => {

0 commit comments

Comments
 (0)