sass-references/angular-material/material/paginator/paginator.spec.ts

681 lines
24 KiB
TypeScript
Raw Permalink Normal View History

2024-12-06 10:42:08 +08:00
import {dispatchMouseEvent} from '@angular/cdk/testing/private';
import {ChangeDetectorRef, Component, Provider, Type, ViewChild, inject} from '@angular/core';
import {ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing';
import {ThemePalette} from '@angular/material/core';
import {MatSelect} from '@angular/material/select';
import {By} from '@angular/platform-browser';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {
MatPaginator,
MatPaginatorIntl,
MatPaginatorModule,
MatPaginatorSelectConfig,
} from './index';
import {MAT_PAGINATOR_DEFAULT_OPTIONS, MatPaginatorDefaultOptions} from './paginator';
describe('MatPaginator', () => {
function createComponent<T>(type: Type<T>, providers: Provider[] = []): ComponentFixture<T> {
TestBed.configureTestingModule({
imports: [MatPaginatorModule, NoopAnimationsModule],
providers: [MatPaginatorIntl, ...providers],
declarations: [type],
});
const fixture = TestBed.createComponent(type);
fixture.detectChanges();
return fixture;
}
describe('with the default internationalization provider', () => {
describe('showing the right range text', () => {
it('should show second page of list of 100, each page contains 10 items', () => {
const fixture = createComponent(MatPaginatorApp);
const component = fixture.componentInstance;
const rangeElement = fixture.nativeElement.querySelector('.mat-mdc-paginator-range-label');
component.length = 100;
component.pageSize = 10;
component.pageIndex = 1;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(rangeElement.textContent!.trim()).toBe('11 20 of 100');
});
it('should show third page of list of 200, each page contains 20 items', () => {
const fixture = createComponent(MatPaginatorApp);
const component = fixture.componentInstance;
const rangeElement = fixture.nativeElement.querySelector('.mat-mdc-paginator-range-label');
component.length = 200;
component.pageSize = 20;
component.pageIndex = 2;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(rangeElement.textContent!.trim()).toBe('41 60 of 200');
});
it('should show first page of list of 0, each page contains 5 items', () => {
const fixture = createComponent(MatPaginatorApp);
const component = fixture.componentInstance;
const rangeElement = fixture.nativeElement.querySelector('.mat-mdc-paginator-range-label');
component.length = 0;
component.pageSize = 5;
component.pageIndex = 2;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(rangeElement.textContent!.trim()).toBe('0 of 0');
});
it('should show third page of list of 12, each page contains 5 items', () => {
const fixture = createComponent(MatPaginatorApp);
const component = fixture.componentInstance;
const rangeElement = fixture.nativeElement.querySelector('.mat-mdc-paginator-range-label');
component.length = 12;
component.pageSize = 5;
component.pageIndex = 2;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(rangeElement.textContent!.trim()).toBe('11 12 of 12');
});
it('should show third page of list of 10, each page contains 5 items', () => {
const fixture = createComponent(MatPaginatorApp);
const component = fixture.componentInstance;
const rangeElement = fixture.nativeElement.querySelector('.mat-mdc-paginator-range-label');
component.length = 10;
component.pageSize = 5;
component.pageIndex = 2;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(rangeElement.textContent!.trim()).toBe('11 15 of 10');
});
it('should show third page of list of -5, each page contains 5 items', () => {
const fixture = createComponent(MatPaginatorApp);
const component = fixture.componentInstance;
const rangeElement = fixture.nativeElement.querySelector('.mat-mdc-paginator-range-label');
component.length = -5;
component.pageSize = 5;
component.pageIndex = 2;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(rangeElement.textContent!.trim()).toBe('11 15 of 0');
});
});
it('should show right aria-labels for select and buttons', () => {
const fixture = createComponent(MatPaginatorApp);
expect(getPreviousButton(fixture).getAttribute('aria-label')).toBe('Previous page');
expect(getNextButton(fixture).getAttribute('aria-label')).toBe('Next page');
const select = fixture.nativeElement.querySelector('.mat-mdc-select');
const selectLabelIds = select.getAttribute('aria-labelledby')?.split(/\s/g) as string[];
const selectLabelTexts = selectLabelIds?.map(labelId => {
return fixture.nativeElement.querySelector(`#${labelId}`)?.textContent?.trim();
});
expect(selectLabelTexts).toContain('Items per page:');
});
it('should re-render when the i18n labels change', () => {
const fixture = createComponent(MatPaginatorApp);
const label = fixture.nativeElement.querySelector('.mat-mdc-paginator-page-size-label');
const intl = TestBed.inject(MatPaginatorIntl);
intl.itemsPerPageLabel = '1337 items per page';
intl.changes.next();
fixture.detectChanges();
expect(label.textContent!.trim()).toBe('1337 items per page');
});
});
describe('when navigating with the next and previous buttons', () => {
it('should be able to go to the next page', () => {
const fixture = createComponent(MatPaginatorApp);
const component = fixture.componentInstance;
const paginator = component.paginator;
expect(paginator.pageIndex).toBe(0);
dispatchMouseEvent(getNextButton(fixture), 'click');
expect(paginator.pageIndex).toBe(1);
expect(component.pageEvent).toHaveBeenCalledWith(
jasmine.objectContaining({
previousPageIndex: 0,
pageIndex: 1,
}),
);
});
it('should be able to go to the previous page', () => {
const fixture = createComponent(MatPaginatorApp);
const component = fixture.componentInstance;
const paginator = component.paginator;
paginator.pageIndex = 1;
fixture.detectChanges();
expect(paginator.pageIndex).toBe(1);
dispatchMouseEvent(getPreviousButton(fixture), 'click');
expect(paginator.pageIndex).toBe(0);
expect(component.pageEvent).toHaveBeenCalledWith(
jasmine.objectContaining({
previousPageIndex: 1,
pageIndex: 0,
}),
);
});
});
it('should be able to show the first/last buttons', () => {
const fixture = createComponent(MatPaginatorApp);
expect(getFirstButton(fixture)).withContext('Expected first button to not exist.').toBeNull();
expect(getLastButton(fixture)).withContext('Expected last button to not exist.').toBeNull();
fixture.componentInstance.showFirstLastButtons = true;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(getFirstButton(fixture))
.withContext('Expected first button to be rendered.')
.toBeTruthy();
expect(getLastButton(fixture)).withContext('Expected last button to be rendered.').toBeTruthy();
});
it('should mark itself as initialized', fakeAsync(() => {
const fixture = createComponent(MatPaginatorApp);
const component = fixture.componentInstance;
const paginator = component.paginator;
let isMarkedInitialized = false;
paginator.initialized.subscribe(() => (isMarkedInitialized = true));
tick();
expect(isMarkedInitialized).toBeTruthy();
}));
it('should not allow a negative pageSize', () => {
const fixture = createComponent(MatPaginatorApp);
const component = fixture.componentInstance;
const paginator = component.paginator;
paginator.pageSize = -1337;
fixture.changeDetectorRef.markForCheck();
expect(paginator.pageSize).toBeGreaterThanOrEqual(0);
});
it('should not allow a negative pageIndex', () => {
const fixture = createComponent(MatPaginatorApp);
const component = fixture.componentInstance;
const paginator = component.paginator;
paginator.pageIndex = -42;
expect(paginator.pageIndex).toBeGreaterThanOrEqual(0);
});
it('should be able to set the color of the form field', () => {
const fixture = createComponent(MatPaginatorApp);
const component = fixture.componentInstance;
const formField: HTMLElement = fixture.nativeElement.querySelector('.mat-mdc-form-field');
component.color = 'accent';
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(formField.classList).not.toContain('mat-warn');
expect(formField.classList).toContain('mat-accent');
component.color = 'warn';
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(formField.classList).toContain('mat-warn');
expect(formField.classList).not.toContain('mat-accent');
});
it('should be able to pass options to the underlying mat-select', () => {
const fixture = createComponent(MatPaginatorApp);
fixture.detectChanges();
const select: MatSelect = fixture.debugElement.query(By.directive(MatSelect)).componentInstance;
expect(select.disableOptionCentering).toBe(false);
expect(select.panelClass).toBeFalsy();
fixture.componentInstance.selectConfig = {
disableOptionCentering: true,
panelClass: 'custom-class',
};
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(select.disableOptionCentering).toBe(true);
expect(select.panelClass).toBe('custom-class');
});
describe('when showing the first and last button', () => {
let fixture: ComponentFixture<MatPaginatorApp>;
let component: MatPaginatorApp;
let paginator: MatPaginator;
beforeEach(() => {
fixture = createComponent(MatPaginatorApp);
component = fixture.componentInstance;
paginator = component.paginator;
component.showFirstLastButtons = true;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
});
it('should show right aria-labels for first/last buttons', () => {
expect(getFirstButton(fixture).getAttribute('aria-label')).toBe('First page');
expect(getLastButton(fixture).getAttribute('aria-label')).toBe('Last page');
});
it('should be able to go to the last page via the last page button', () => {
expect(paginator.pageIndex).toBe(0);
dispatchMouseEvent(getLastButton(fixture), 'click');
expect(paginator.pageIndex).toBe(9);
expect(component.pageEvent).toHaveBeenCalledWith(
jasmine.objectContaining({
previousPageIndex: 0,
pageIndex: 9,
}),
);
});
it('should be able to go to the first page via the first page button', () => {
paginator.pageIndex = 3;
fixture.detectChanges();
expect(paginator.pageIndex).toBe(3);
dispatchMouseEvent(getFirstButton(fixture), 'click');
expect(paginator.pageIndex).toBe(0);
expect(component.pageEvent).toHaveBeenCalledWith(
jasmine.objectContaining({
previousPageIndex: 3,
pageIndex: 0,
}),
);
});
it('should disable navigating to the next page if at last page', () => {
component.goToLastPage();
fixture.detectChanges();
expect(paginator.pageIndex).toBe(9);
expect(paginator.hasNextPage()).toBe(false);
component.pageEvent.calls.reset();
dispatchMouseEvent(getNextButton(fixture), 'click');
expect(component.pageEvent).not.toHaveBeenCalled();
expect(paginator.pageIndex).toBe(9);
});
it('should disable navigating to the previous page if at first page', () => {
expect(paginator.pageIndex).toBe(0);
expect(paginator.hasPreviousPage()).toBe(false);
component.pageEvent.calls.reset();
dispatchMouseEvent(getPreviousButton(fixture), 'click');
expect(component.pageEvent).not.toHaveBeenCalled();
expect(paginator.pageIndex).toBe(0);
});
});
it('should mark for check when inputs are changed directly', () => {
const fixture = createComponent(MatPaginatorApp);
const component = fixture.componentInstance;
const paginator = component.paginator;
const rangeElement = fixture.nativeElement.querySelector('.mat-mdc-paginator-range-label');
expect(rangeElement.innerText.trim()).toBe('1 10 of 100');
paginator.length = 99;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(rangeElement.innerText.trim()).toBe('1 10 of 99');
paginator.pageSize = 6;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(rangeElement.innerText.trim()).toBe('1 6 of 99');
paginator.pageIndex = 1;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(rangeElement.innerText.trim()).toBe('7 12 of 99');
// Having one option and the same page size should remove the select menu
expect(fixture.nativeElement.querySelector('.mat-mdc-select')).not.toBeNull();
paginator.pageSize = 10;
paginator.pageSizeOptions = [10];
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(fixture.nativeElement.querySelector('.mat-mdc-select')).toBeNull();
});
it('should default the page size options to the page size if no options provided', () => {
const fixture = createComponent(MatPaginatorWithoutOptionsApp);
fixture.detectChanges();
expect(fixture.componentInstance.paginator._displayedPageSizeOptions).toEqual([10]);
});
it('should default the page size to the first page size option if not provided', () => {
const fixture = createComponent(MatPaginatorWithoutPageSizeApp);
fixture.detectChanges();
expect(fixture.componentInstance.paginator.pageSize).toEqual(10);
});
it('should show a sorted list of page size options including the current page size', () => {
const fixture = createComponent(MatPaginatorApp);
const component = fixture.componentInstance;
const paginator = component.paginator;
expect(paginator._displayedPageSizeOptions).toEqual([5, 10, 25, 100]);
component.pageSize = 30;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(paginator.pageSizeOptions).toEqual([5, 10, 25, 100]);
expect(paginator._displayedPageSizeOptions).toEqual([5, 10, 25, 30, 100]);
component.pageSizeOptions = [100, 25, 10, 5];
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(paginator._displayedPageSizeOptions).toEqual([5, 10, 25, 30, 100]);
});
it('should be able to change the page size while keeping the first item present', () => {
const fixture = createComponent(MatPaginatorApp);
const component = fixture.componentInstance;
const paginator = component.paginator;
// Start on the third page of a list of 100 with a page size of 10.
component.pageIndex = 4;
component.pageSize = 10;
component.length = 100;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
// The first item of the page should be item with index 40
expect(paginator.pageIndex * paginator.pageSize).toBe(40);
// The first item on the page is now 25. Change the page size to 25 so that we should now be
// on the second page where the top item is index 25.
component.pageEvent.calls.reset();
paginator._changePageSize(25);
expect(component.pageEvent).toHaveBeenCalledWith(
jasmine.objectContaining({
pageIndex: 1,
pageSize: 25,
}),
);
// The first item on the page is still 25. Change the page size to 8 so that we should now be
// on the fourth page where the top item is index 24.
component.pageEvent.calls.reset();
paginator._changePageSize(8);
expect(component.pageEvent).toHaveBeenCalledWith(
jasmine.objectContaining({
pageIndex: 3,
pageSize: 8,
}),
);
// The first item on the page is 24. Change the page size to 16 so that we should now be
// on the first page where the top item is index 0.
component.pageEvent.calls.reset();
paginator._changePageSize(25);
expect(component.pageEvent).toHaveBeenCalledWith(
jasmine.objectContaining({
pageIndex: 0,
pageSize: 25,
}),
);
});
it('should keep track of the right number of pages', () => {
const fixture = createComponent(MatPaginatorApp);
const component = fixture.componentInstance;
const paginator = component.paginator;
component.pageSize = 10;
component.length = 100;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(paginator.getNumberOfPages()).toBe(10);
component.pageSize = 10;
component.length = 0;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(paginator.getNumberOfPages()).toBe(0);
component.pageSize = 10;
component.length = 10;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(paginator.getNumberOfPages()).toBe(1);
});
it('should show a select only if there are multiple options', () => {
const fixture = createComponent(MatPaginatorApp);
const component = fixture.componentInstance;
const paginator = component.paginator;
expect(paginator._displayedPageSizeOptions).toEqual([5, 10, 25, 100]);
expect(fixture.nativeElement.querySelector('.mat-mdc-select')).not.toBeNull();
// Remove options so that the paginator only uses the current page size (10) as an option.
// Should no longer show the select component since there is only one option.
component.pageSizeOptions = [];
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(fixture.nativeElement.querySelector('.mat-mdc-select')).toBeNull();
});
it('should handle the number inputs being passed in as strings', () => {
const fixture = createComponent(MatPaginatorWithStringValues);
fixture.detectChanges();
const withStringPaginator = fixture.componentInstance.paginator;
expect(withStringPaginator.pageIndex).toEqual(0);
expect(withStringPaginator.length).toEqual(100);
expect(withStringPaginator.pageSize).toEqual(10);
expect(withStringPaginator.pageSizeOptions).toEqual([5, 10, 25, 100]);
});
it('should be able to hide the page size select', () => {
const fixture = createComponent(MatPaginatorApp);
const element = fixture.nativeElement;
expect(element.querySelector('.mat-mdc-paginator-page-size'))
.withContext('Expected select to be rendered.')
.toBeTruthy();
fixture.componentInstance.hidePageSize = true;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(element.querySelector('.mat-mdc-paginator-page-size'))
.withContext('Expected select to be removed.')
.toBeNull();
});
it('should be able to disable all the controls in the paginator via the binding', () => {
const fixture = createComponent(MatPaginatorApp);
const select: MatSelect = fixture.debugElement.query(
By.directive(MatSelect),
)!.componentInstance;
fixture.componentInstance.pageIndex = 1;
fixture.componentInstance.showFirstLastButtons = true;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(select.disabled).toBe(false);
expect(getPreviousButton(fixture).hasAttribute('disabled')).toBe(false);
expect(getNextButton(fixture).hasAttribute('disabled')).toBe(false);
expect(getFirstButton(fixture).hasAttribute('disabled')).toBe(false);
expect(getLastButton(fixture).hasAttribute('disabled')).toBe(false);
fixture.componentInstance.disabled = true;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(select.disabled).toBe(true);
expect(getPreviousButton(fixture).hasAttribute('aria-disabled')).toBe(true);
expect(getNextButton(fixture).hasAttribute('aria-disabled')).toBe(true);
expect(getFirstButton(fixture).hasAttribute('aria-disabled')).toBe(true);
expect(getLastButton(fixture).hasAttribute('aria-disabled')).toBe(true);
});
it('should be able to configure the default options via a provider', () => {
const fixture = createComponent(MatPaginatorWithoutInputsApp, [
{
provide: MAT_PAGINATOR_DEFAULT_OPTIONS,
useValue: {
pageSize: 7,
pageSizeOptions: [7, 14, 21],
hidePageSize: true,
showFirstLastButtons: true,
} as MatPaginatorDefaultOptions,
},
]);
const paginator = fixture.componentInstance.paginator;
expect(paginator.pageSize).toBe(7);
expect(paginator.pageSizeOptions).toEqual([7, 14, 21]);
expect(paginator.hidePageSize).toBe(true);
expect(paginator.showFirstLastButtons).toBe(true);
});
it('should set `role="group"` on the host element', () => {
const fixture = createComponent(MatPaginatorApp);
const hostElement = fixture.nativeElement.querySelector('mat-paginator');
expect(hostElement.getAttribute('role')).toBe('group');
});
it('should handle the page size options input being passed in as readonly array', () => {
const fixture = createComponent(MatPaginatorWithReadonlyOptions);
fixture.detectChanges();
expect(fixture.componentInstance.paginator._displayedPageSizeOptions).toEqual([5, 10, 25, 100]);
});
});
function getPreviousButton(fixture: ComponentFixture<any>) {
return fixture.nativeElement.querySelector('.mat-mdc-paginator-navigation-previous');
}
function getNextButton(fixture: ComponentFixture<any>) {
return fixture.nativeElement.querySelector('.mat-mdc-paginator-navigation-next');
}
function getFirstButton(fixture: ComponentFixture<any>) {
return fixture.nativeElement.querySelector('.mat-mdc-paginator-navigation-first');
}
function getLastButton(fixture: ComponentFixture<any>) {
return fixture.nativeElement.querySelector('.mat-mdc-paginator-navigation-last');
}
@Component({
template: `
<mat-paginator [pageIndex]="pageIndex"
[pageSize]="pageSize"
[pageSizeOptions]="pageSizeOptions"
[hidePageSize]="hidePageSize"
[selectConfig]="selectConfig"
[showFirstLastButtons]="showFirstLastButtons"
[length]="length"
[color]="color"
[disabled]="disabled"
(page)="pageEvent($event)">
</mat-paginator>
`,
standalone: false,
})
class MatPaginatorApp {
pageIndex = 0;
pageSize = 10;
pageSizeOptions = [5, 10, 25, 100];
hidePageSize = false;
showFirstLastButtons = false;
length = 100;
disabled: boolean;
pageEvent = jasmine.createSpy('page event');
color: ThemePalette;
selectConfig: MatPaginatorSelectConfig = {};
@ViewChild(MatPaginator) paginator: MatPaginator;
private readonly _changeDetectorRef = inject(ChangeDetectorRef);
goToLastPage() {
this.pageIndex = Math.ceil(this.length / this.pageSize) - 1;
this._changeDetectorRef.markForCheck();
}
}
@Component({
template: `
<mat-paginator></mat-paginator>
`,
standalone: false,
})
class MatPaginatorWithoutInputsApp {
@ViewChild(MatPaginator) paginator: MatPaginator;
}
@Component({
template: `
<mat-paginator [pageSizeOptions]="[10, 20, 30]"></mat-paginator>
`,
standalone: false,
})
class MatPaginatorWithoutPageSizeApp {
@ViewChild(MatPaginator) paginator: MatPaginator;
}
@Component({
template: `
<mat-paginator [pageSize]="10"></mat-paginator>
`,
standalone: false,
})
class MatPaginatorWithoutOptionsApp {
@ViewChild(MatPaginator) paginator: MatPaginator;
}
@Component({
template: `
<mat-paginator pageIndex="0"
pageSize="10"
[pageSizeOptions]="['5', '10', '25', '100']"
length="100">
</mat-paginator>
`,
standalone: false,
})
class MatPaginatorWithStringValues {
@ViewChild(MatPaginator) paginator: MatPaginator;
}
@Component({
template: `
<mat-paginator [pageSizeOptions]="pageSizeOptions">
</mat-paginator>
`,
standalone: false,
})
class MatPaginatorWithReadonlyOptions {
@ViewChild(MatPaginator) paginator: MatPaginator;
pageSizeOptions: readonly number[] = [5, 10, 25, 100];
}