diff --git a/src/material/config.bzl b/src/material/config.bzl index d266e25bf9a7..f01b943322df 100644 --- a/src/material/config.bzl +++ b/src/material/config.bzl @@ -30,6 +30,7 @@ entryPoints = [ "menu", "menu/testing", "paginator", + "paginator/testing", "progress-bar", "progress-bar/testing", "progress-spinner", diff --git a/src/material/paginator/paginator.html b/src/material/paginator/paginator.html index 64d800e157ae..4276c8067ebb 100644 --- a/src/material/paginator/paginator.html +++ b/src/material/paginator/paginator.html @@ -20,7 +20,9 @@ -
{{pageSize}}
+
{{pageSize}}
diff --git a/src/material/paginator/testing/BUILD.bazel b/src/material/paginator/testing/BUILD.bazel new file mode 100644 index 000000000000..e848d5a0929f --- /dev/null +++ b/src/material/paginator/testing/BUILD.bazel @@ -0,0 +1,53 @@ +package(default_visibility = ["//visibility:public"]) + +load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library") + +ts_library( + name = "testing", + srcs = glob( + ["**/*.ts"], + exclude = ["**/*.spec.ts"], + ), + module_name = "@angular/material/paginator/testing", + deps = [ + "//src/cdk/coercion", + "//src/cdk/testing", + "//src/material/select/testing", + ], +) + +filegroup( + name = "source-files", + srcs = glob(["**/*.ts"]), +) + +ng_test_library( + name = "harness_tests_lib", + srcs = ["shared.spec.ts"], + deps = [ + ":testing", + "//src/cdk/testing", + "//src/cdk/testing/private", + "//src/cdk/testing/testbed", + "//src/material/paginator", + "@npm//@angular/platform-browser", + ], +) + +ng_test_library( + name = "unit_tests_lib", + srcs = glob( + ["**/*.spec.ts"], + exclude = ["shared.spec.ts"], + ), + deps = [ + ":harness_tests_lib", + ":testing", + "//src/material/paginator", + ], +) + +ng_web_test_suite( + name = "unit_tests", + deps = [":unit_tests_lib"], +) diff --git a/src/material/paginator/testing/index.ts b/src/material/paginator/testing/index.ts new file mode 100644 index 000000000000..676ca90f1ffa --- /dev/null +++ b/src/material/paginator/testing/index.ts @@ -0,0 +1,9 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +export * from './public-api'; diff --git a/src/material/paginator/testing/paginator-harness-filters.ts b/src/material/paginator/testing/paginator-harness-filters.ts new file mode 100644 index 000000000000..6d5ad9dcdb7a --- /dev/null +++ b/src/material/paginator/testing/paginator-harness-filters.ts @@ -0,0 +1,13 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {BaseHarnessFilters} from '@angular/cdk/testing'; + +/** A set of criteria that can be used to filter a list of `MatPaginatorHarness` instances. */ +export interface PaginatorHarnessFilters extends BaseHarnessFilters { +} diff --git a/src/material/paginator/testing/paginator-harness.spec.ts b/src/material/paginator/testing/paginator-harness.spec.ts new file mode 100644 index 000000000000..f35004bd9896 --- /dev/null +++ b/src/material/paginator/testing/paginator-harness.spec.ts @@ -0,0 +1,7 @@ +import {MatPaginatorModule} from '@angular/material/paginator'; +import {runHarnessTests} from './shared.spec'; +import {MatPaginatorHarness} from './paginator-harness'; + +describe('Non-MDC-based MatPaginatorHarness', () => { + runHarnessTests(MatPaginatorModule, MatPaginatorHarness); +}); diff --git a/src/material/paginator/testing/paginator-harness.ts b/src/material/paginator/testing/paginator-harness.ts new file mode 100644 index 000000000000..ff68876b8848 --- /dev/null +++ b/src/material/paginator/testing/paginator-harness.ts @@ -0,0 +1,103 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {ComponentHarness, HarnessPredicate} from '@angular/cdk/testing'; +import {MatSelectHarness} from '@angular/material/select/testing'; +import {coerceNumberProperty} from '@angular/cdk/coercion'; +import {PaginatorHarnessFilters} from './paginator-harness-filters'; + + +/** Harness for interacting with a standard mat-paginator in tests. */ +export class MatPaginatorHarness extends ComponentHarness { + /** Selector used to find paginator instances. */ + static hostSelector = '.mat-paginator'; + private _nextButton = this.locatorFor('.mat-paginator-navigation-next'); + private _previousButton = this.locatorFor('.mat-paginator-navigation-previous'); + private _firstPageButton = this.locatorForOptional('.mat-paginator-navigation-first'); + private _lastPageButton = this.locatorForOptional('.mat-paginator-navigation-last'); + private _select = this.locatorForOptional(MatSelectHarness.with({ + ancestor: '.mat-paginator-page-size' + })); + private _pageSizeFallback = this.locatorFor('.mat-paginator-page-size-value'); + private _rangeLabel = this.locatorFor('.mat-paginator-range-label'); + + /** + * Gets a `HarnessPredicate` that can be used to search for a `MatPaginatorHarness` that meets + * certain criteria. + * @param options Options for filtering which paginator instances are considered a match. + * @return a `HarnessPredicate` configured with the given options. + */ + static with(options: PaginatorHarnessFilters = {}): HarnessPredicate { + return new HarnessPredicate(MatPaginatorHarness, options); + } + + /** Goes to the next page in the paginator. */ + async goToNextPage(): Promise { + return (await this._nextButton()).click(); + } + + /** Goes to the previous page in the paginator. */ + async goToPreviousPage(): Promise { + return (await this._previousButton()).click(); + } + + /** Goes to the first page in the paginator. */ + async goToFirstPage(): Promise { + const button = await this._firstPageButton(); + + // The first page button isn't enabled by default so we need to check for it. + if (!button) { + throw Error('Could not find first page button inside paginator. ' + + 'Make sure that `showFirstLastButtons` is enabled.'); + } + + return button.click(); + } + + /** Goes to the last page in the paginator. */ + async goToLastPage(): Promise { + const button = await this._lastPageButton(); + + // The last page button isn't enabled by default so we need to check for it. + if (!button) { + throw Error('Could not find last page button inside paginator. ' + + 'Make sure that `showFirstLastButtons` is enabled.'); + } + + return button.click(); + } + + /** + * Sets the page size of the paginator. + * @param size Page size that should be select. + */ + async setPageSize(size: number): Promise { + const select = await this._select(); + + // The select is only available if the `pageSizeOptions` are + // set to an array with more than one item. + if (!select) { + throw Error('Cannot find page size selector in paginator. ' + + 'Make sure that the `pageSizeOptions` have been configured.'); + } + + return select.clickOptions({text: `${size}`}); + } + + /** Gets the page size of the paginator. */ + async getPageSize(): Promise { + const select = await this._select(); + const value = select ? select.getValueText() : (await this._pageSizeFallback()).text(); + return coerceNumberProperty(await value); + } + + /** Gets the text of the range labe of the paginator. */ + async getRangeLabel(): Promise { + return (await this._rangeLabel()).text(); + } +} diff --git a/src/material/paginator/testing/public-api.ts b/src/material/paginator/testing/public-api.ts new file mode 100644 index 000000000000..04f54e17331a --- /dev/null +++ b/src/material/paginator/testing/public-api.ts @@ -0,0 +1,10 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +export * from './paginator-harness'; +export * from './paginator-harness-filters'; diff --git a/src/material/paginator/testing/shared.spec.ts b/src/material/paginator/testing/shared.spec.ts new file mode 100644 index 000000000000..b4118160e29c --- /dev/null +++ b/src/material/paginator/testing/shared.spec.ts @@ -0,0 +1,145 @@ +import {HarnessLoader} from '@angular/cdk/testing'; +import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; +import {Component, ViewChild} from '@angular/core'; +import {NoopAnimationsModule} from '@angular/platform-browser/animations'; +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {MatPaginatorModule, PageEvent, MatPaginator} from '@angular/material/paginator'; +import {MatPaginatorHarness} from './paginator-harness'; +import {expectAsyncError} from '@angular/cdk/testing/private'; + +/** Shared tests to run on both the original and MDC-based paginator. */ +export function runHarnessTests( + paginatorModule: typeof MatPaginatorModule, paginatorHarness: typeof MatPaginatorHarness) { + let fixture: ComponentFixture; + let loader: HarnessLoader; + let instance: PaginatorHarnessTest; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [paginatorModule, NoopAnimationsModule], + declarations: [PaginatorHarnessTest], + }).compileComponents(); + + fixture = TestBed.createComponent(PaginatorHarnessTest); + fixture.detectChanges(); + loader = TestbedHarnessEnvironment.loader(fixture); + instance = fixture.componentInstance; + }); + + it('should load all paginator harnesses', async () => { + const paginators = await loader.getAllHarnesses(paginatorHarness); + expect(paginators.length).toBe(1); + }); + + it('should be able to go to the next page', async () => { + const paginator = await loader.getHarness(paginatorHarness); + + expect(instance.pageIndex).toBe(0); + await paginator.goToNextPage(); + expect(instance.pageIndex).toBe(1); + }); + + it('should be able to go to the previous page', async () => { + const paginator = await loader.getHarness(paginatorHarness); + + instance.pageIndex = 5; + fixture.detectChanges(); + + await paginator.goToPreviousPage(); + expect(instance.pageIndex).toBe(4); + }); + + it('should be able to go to the first page', async () => { + const paginator = await loader.getHarness(paginatorHarness); + + instance.pageIndex = 5; + fixture.detectChanges(); + + await paginator.goToFirstPage(); + expect(instance.pageIndex).toBe(0); + }); + + it('should be able to go to the last page', async () => { + const paginator = await loader.getHarness(paginatorHarness); + + expect(instance.pageIndex).toBe(0); + await paginator.goToLastPage(); + expect(instance.pageIndex).toBe(49); + }); + + it('should be able to set the page size', async () => { + const paginator = await loader.getHarness(paginatorHarness); + + expect(instance.pageSize).toBe(10); + await paginator.setPageSize(25); + expect(instance.pageSize).toBe(25); + }); + + it('should be able to get the page size', async () => { + const paginator = await loader.getHarness(paginatorHarness); + expect(await paginator.getPageSize()).toBe(10); + }); + + it('should be able to get the range label', async () => { + const paginator = await loader.getHarness(paginatorHarness); + expect(await paginator.getRangeLabel()).toBe('1 – 10 of 500'); + }); + + it('should throw an error if the first page button is not available', async () => { + const paginator = await loader.getHarness(paginatorHarness); + + instance.showFirstLastButtons = false; + fixture.detectChanges(); + + await expectAsyncError(() => paginator.goToFirstPage(), + /Error: Could not find first page button inside paginator/); + }); + + it('should throw an error if the last page button is not available', async () => { + const paginator = await loader.getHarness(paginatorHarness); + + instance.showFirstLastButtons = false; + fixture.detectChanges(); + + await expectAsyncError(() => paginator.goToLastPage(), + /Error: Could not find last page button inside paginator/); + }); + + it('should throw an error if the page size selector is not available', async () => { + const paginator = await loader.getHarness(paginatorHarness); + + instance.pageSizeOptions = []; + fixture.detectChanges(); + + await expectAsyncError(() => paginator.setPageSize(10), + /Error: Cannot find page size selector in paginator/); + }); +} + +@Component({ + template: ` + + + ` +}) +class PaginatorHarnessTest { + @ViewChild(MatPaginator) paginator: MatPaginator; + length = 500; + pageSize = 10; + pageIndex = 0; + pageSizeOptions = [5, 10, 25]; + showFirstLastButtons = true; + + handlePageEvent(event: PageEvent) { + this.length = event.length; + this.pageSize = event.pageSize; + this.pageIndex = event.pageIndex; + } +} + diff --git a/test/karma-system-config.js b/test/karma-system-config.js index d8359d3344e4..7c61dc446ec6 100644 --- a/test/karma-system-config.js +++ b/test/karma-system-config.js @@ -136,6 +136,8 @@ System.config({ '@angular/material/menu/testing': 'dist/packages/material/menu/testing/index.js', '@angular/material/menu/testing/shared.spec': 'dist/packages/material/menu/testing/shared.spec.js', '@angular/material/paginator': 'dist/packages/material/paginator/index.js', + '@angular/material/paginator/testing': 'dist/packages/material/paginator/testing/index.js', + '@angular/material/paginator/testing/shared.spec': 'dist/packages/material/paginator/testing/shared.spec.js', '@angular/material/progress-bar': 'dist/packages/material/progress-bar/index.js', '@angular/material/progress-bar/testing': 'dist/packages/material/progress-bar/testing/index.js', '@angular/material/progress-bar/testing/shared.spec': 'dist/packages/material/progress-bar/testing/shared.spec.js', diff --git a/tools/public_api_guard/material/paginator/testing.d.ts b/tools/public_api_guard/material/paginator/testing.d.ts new file mode 100644 index 000000000000..3700a97bb817 --- /dev/null +++ b/tools/public_api_guard/material/paginator/testing.d.ts @@ -0,0 +1,14 @@ +export declare class MatPaginatorHarness extends ComponentHarness { + getPageSize(): Promise; + getRangeLabel(): Promise; + goToFirstPage(): Promise; + goToLastPage(): Promise; + goToNextPage(): Promise; + goToPreviousPage(): Promise; + setPageSize(size: number): Promise; + static hostSelector: string; + static with(options?: PaginatorHarnessFilters): HarnessPredicate; +} + +export interface PaginatorHarnessFilters extends BaseHarnessFilters { +}