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(type: Type, providers: Provider[] = []): ComponentFixture { 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; 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) { return fixture.nativeElement.querySelector('.mat-mdc-paginator-navigation-previous'); } function getNextButton(fixture: ComponentFixture) { return fixture.nativeElement.querySelector('.mat-mdc-paginator-navigation-next'); } function getFirstButton(fixture: ComponentFixture) { return fixture.nativeElement.querySelector('.mat-mdc-paginator-navigation-first'); } function getLastButton(fixture: ComponentFixture) { return fixture.nativeElement.querySelector('.mat-mdc-paginator-navigation-last'); } @Component({ template: ` `, 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: ` `, standalone: false, }) class MatPaginatorWithoutInputsApp { @ViewChild(MatPaginator) paginator: MatPaginator; } @Component({ template: ` `, standalone: false, }) class MatPaginatorWithoutPageSizeApp { @ViewChild(MatPaginator) paginator: MatPaginator; } @Component({ template: ` `, standalone: false, }) class MatPaginatorWithoutOptionsApp { @ViewChild(MatPaginator) paginator: MatPaginator; } @Component({ template: ` `, standalone: false, }) class MatPaginatorWithStringValues { @ViewChild(MatPaginator) paginator: MatPaginator; } @Component({ template: ` `, standalone: false, }) class MatPaginatorWithReadonlyOptions { @ViewChild(MatPaginator) paginator: MatPaginator; pageSizeOptions: readonly number[] = [5, 10, 25, 100]; }