diff --git a/src/material-experimental/config.bzl b/src/material-experimental/config.bzl index 3e4010824d92..8b44bc0b6608 100644 --- a/src/material-experimental/config.bzl +++ b/src/material-experimental/config.bzl @@ -33,6 +33,7 @@ entryPoints = [ "mdc-slider/testing", "mdc-snack-bar", "mdc-table", + "mdc-table/testing", "mdc-tabs", "menubar", "popover-edit", diff --git a/src/material-experimental/mdc-card/testing/BUILD.bazel b/src/material-experimental/mdc-card/testing/BUILD.bazel index 5850419e105d..650666b81d73 100644 --- a/src/material-experimental/mdc-card/testing/BUILD.bazel +++ b/src/material-experimental/mdc-card/testing/BUILD.bazel @@ -26,7 +26,6 @@ ng_test_library( ["**/*.spec.ts"], exclude = [ "**/*.e2e.spec.ts", - "shared.spec.ts", ], ), deps = [ diff --git a/src/material-experimental/mdc-table/testing/BUILD.bazel b/src/material-experimental/mdc-table/testing/BUILD.bazel new file mode 100644 index 000000000000..82ff5437e193 --- /dev/null +++ b/src/material-experimental/mdc-table/testing/BUILD.bazel @@ -0,0 +1,35 @@ +load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library") + +package(default_visibility = ["//visibility:public"]) + +ts_library( + name = "testing", + srcs = glob( + ["**/*.ts"], + exclude = ["**/*.spec.ts"], + ), + module_name = "@angular/material-experimental/mdc-table/testing", + deps = [ + "//src/cdk/testing", + ], +) + +filegroup( + name = "source-files", + srcs = glob(["**/*.ts"]), +) + +ng_test_library( + name = "unit_tests_lib", + srcs = glob(["**/*.spec.ts"]), + deps = [ + ":testing", + "//src/material-experimental/mdc-table", + "//src/material/table/testing:harness_tests_lib", + ], +) + +ng_web_test_suite( + name = "unit_tests", + deps = [":unit_tests_lib"], +) diff --git a/src/material-experimental/mdc-table/testing/cell-harness.ts b/src/material-experimental/mdc-table/testing/cell-harness.ts new file mode 100644 index 000000000000..84ca00d7bb7b --- /dev/null +++ b/src/material-experimental/mdc-table/testing/cell-harness.ts @@ -0,0 +1,94 @@ +/** + * @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, + ComponentHarnessConstructor, +} from '@angular/cdk/testing'; +import {CellHarnessFilters} from './table-harness-filters'; + +/** Harness for interacting with an MDC-based Angular Material table cell. */ +export class MatCellHarness extends ComponentHarness { + /** The selector for the host element of a `MatCellHarness` instance. */ + static hostSelector = '.mat-mdc-cell'; + + /** + * Gets a `HarnessPredicate` that can be used to search for a table cell with specific attributes. + * @param options Options for narrowing the search + * @return a `HarnessPredicate` configured with the given options. + */ + static with(options: CellHarnessFilters = {}): HarnessPredicate { + return getCellPredicate(MatCellHarness, options); + } + + /** Gets the cell's text. */ + async getText(): Promise { + return (await this.host()).text(); + } + + /** Gets the name of the column that the cell belongs to. */ + async getColumnName(): Promise { + const host = await this.host(); + const classAttribute = await host.getAttribute('class'); + + if (classAttribute) { + const prefix = 'mat-column-'; + const name = classAttribute.split(' ').map(c => c.trim()).find(c => c.startsWith(prefix)); + + if (name) { + return name.split(prefix)[1]; + } + } + + throw Error('Could not determine column name of cell.'); + } +} + +/** Harness for interacting with an MDC-based Angular Material table header cell. */ +export class MatHeaderCellHarness extends MatCellHarness { + /** The selector for the host element of a `MatHeaderCellHarness` instance. */ + static hostSelector = '.mat-mdc-header-cell'; + + /** + * Gets a `HarnessPredicate` that can be used to search for + * a table header cell with specific attributes. + * @param options Options for narrowing the search + * @return a `HarnessPredicate` configured with the given options. + */ + static with(options: CellHarnessFilters = {}): HarnessPredicate { + return getCellPredicate(MatHeaderCellHarness, options); + } +} + +/** Harness for interacting with an MDC-based Angular Material table footer cell. */ +export class MatFooterCellHarness extends MatCellHarness { + /** The selector for the host element of a `MatFooterCellHarness` instance. */ + static hostSelector = '.mat-mdc-footer-cell'; + + /** + * Gets a `HarnessPredicate` that can be used to search for + * a table footer cell with specific attributes. + * @param options Options for narrowing the search + * @return a `HarnessPredicate` configured with the given options. + */ + static with(options: CellHarnessFilters = {}): HarnessPredicate { + return getCellPredicate(MatFooterCellHarness, options); + } +} + + +function getCellPredicate( + type: ComponentHarnessConstructor, + options: CellHarnessFilters): HarnessPredicate { + return new HarnessPredicate(type, options) + .addOption('text', options.text, + (harness, text) => HarnessPredicate.stringMatches(harness.getText(), text)) + .addOption('columnName', options.columnName, + (harness, name) => HarnessPredicate.stringMatches(harness.getColumnName(), name)); +} diff --git a/src/material-experimental/mdc-table/testing/index.ts b/src/material-experimental/mdc-table/testing/index.ts new file mode 100644 index 000000000000..676ca90f1ffa --- /dev/null +++ b/src/material-experimental/mdc-table/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-experimental/mdc-table/testing/public-api.ts b/src/material-experimental/mdc-table/testing/public-api.ts new file mode 100644 index 000000000000..1a5ecd8070f2 --- /dev/null +++ b/src/material-experimental/mdc-table/testing/public-api.ts @@ -0,0 +1,12 @@ +/** + * @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 './table-harness'; +export * from './row-harness'; +export * from './cell-harness'; +export * from './table-harness-filters'; diff --git a/src/material-experimental/mdc-table/testing/row-harness.ts b/src/material-experimental/mdc-table/testing/row-harness.ts new file mode 100644 index 000000000000..c740604e8230 --- /dev/null +++ b/src/material-experimental/mdc-table/testing/row-harness.ts @@ -0,0 +1,129 @@ +/** + * @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 {RowHarnessFilters, CellHarnessFilters} from './table-harness-filters'; +import {MatCellHarness, MatHeaderCellHarness, MatFooterCellHarness} from './cell-harness'; + +/** Text extracted from a table row organized by columns. */ +export interface MatRowHarnessColumnsText { + [columnName: string]: string; +} + +/** Harness for interacting with an MDC-based Angular Material table row. */ +export class MatRowHarness extends ComponentHarness { + /** The selector for the host element of a `MatRowHarness` instance. */ + static hostSelector = '.mat-mdc-row'; + + /** + * Gets a `HarnessPredicate` that can be used to search for a table row with specific attributes. + * @param options Options for narrowing the search + * @return a `HarnessPredicate` configured with the given options. + */ + static with(options: RowHarnessFilters = {}): HarnessPredicate { + return new HarnessPredicate(MatRowHarness, options); + } + + /** Gets a list of `MatCellHarness` for all cells in the row. */ + async getCells(filter: CellHarnessFilters = {}): Promise { + return this.locatorForAll(MatCellHarness.with(filter))(); + } + + /** Gets the text of the cells in the row. */ + async getCellTextByIndex(filter: CellHarnessFilters = {}): Promise { + return getCellTextByIndex(this, filter); + } + + /** Gets the text inside the row organized by columns. */ + async getCellTextByColumnName(): Promise { + return getCellTextByColumnName(this); + } +} + +/** Harness for interacting with an MDC-based Angular Material table header row. */ +export class MatHeaderRowHarness extends ComponentHarness { + /** The selector for the host element of a `MatHeaderRowHarness` instance. */ + static hostSelector = '.mat-mdc-header-row'; + + /** + * Gets a `HarnessPredicate` that can be used to search for + * a table header row with specific attributes. + * @param options Options for narrowing the search + * @return a `HarnessPredicate` configured with the given options. + */ + static with(options: RowHarnessFilters = {}): HarnessPredicate { + return new HarnessPredicate(MatHeaderRowHarness, options); + } + + /** Gets a list of `MatHeaderCellHarness` for all cells in the row. */ + async getCells(filter: CellHarnessFilters = {}): Promise { + return this.locatorForAll(MatHeaderCellHarness.with(filter))(); + } + + /** Gets the text of the cells in the header row. */ + async getCellTextByIndex(filter: CellHarnessFilters = {}): Promise { + return getCellTextByIndex(this, filter); + } + + /** Gets the text inside the header row organized by columns. */ + async getCellTextByColumnName(): Promise { + return getCellTextByColumnName(this); + } +} + + +/** Harness for interacting with an MDC-based Angular Material table footer row. */ +export class MatFooterRowHarness extends ComponentHarness { + /** The selector for the host element of a `MatFooterRowHarness` instance. */ + static hostSelector = '.mat-mdc-footer-row'; + + /** + * Gets a `HarnessPredicate` that can be used to search for + * a table footer row cell with specific attributes. + * @param options Options for narrowing the search + * @return a `HarnessPredicate` configured with the given options. + */ + static with(options: RowHarnessFilters = {}): HarnessPredicate { + return new HarnessPredicate(MatFooterRowHarness, options); + } + + /** Gets a list of `MatFooterCellHarness` for all cells in the row. */ + async getCells(filter: CellHarnessFilters = {}): Promise { + return this.locatorForAll(MatFooterCellHarness.with(filter))(); + } + + /** Gets the text of the cells in the footer row. */ + async getCellTextByIndex(filter: CellHarnessFilters = {}): Promise { + return getCellTextByIndex(this, filter); + } + + /** Gets the text inside the footer row organized by columns. */ + async getCellTextByColumnName(): Promise { + return getCellTextByColumnName(this); + } +} + + +async function getCellTextByIndex(harness: { + getCells: (filter?: CellHarnessFilters) => Promise +}, filter: CellHarnessFilters): Promise { + const cells = await harness.getCells(filter); + return Promise.all(cells.map(cell => cell.getText())); +} + +async function getCellTextByColumnName(harness: { + getCells: () => Promise +}): Promise { + const output: MatRowHarnessColumnsText = {}; + const cells = await harness.getCells(); + const cellsData = await Promise.all(cells.map(cell => { + return Promise.all([cell.getColumnName(), cell.getText()]); + })); + cellsData.forEach(([columnName, text]) => output[columnName] = text); + return output; +} diff --git a/src/material-experimental/mdc-table/testing/table-harness-filters.ts b/src/material-experimental/mdc-table/testing/table-harness-filters.ts new file mode 100644 index 000000000000..4f8bc415d9a2 --- /dev/null +++ b/src/material-experimental/mdc-table/testing/table-harness-filters.ts @@ -0,0 +1,25 @@ +/** + * @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 cell harness instances. */ +export interface CellHarnessFilters extends BaseHarnessFilters { + /** Only find instances whose text matches the given value. */ + text?: string | RegExp; + + /** Only find instances whose column name matches the given value. */ + columnName?: string | RegExp; +} + +/** A set of criteria that can be used to filter a list of row harness instances. */ +export interface RowHarnessFilters extends BaseHarnessFilters { +} + +/** A set of criteria that can be used to filter a list of table harness instances. */ +export interface TableHarnessFilters extends BaseHarnessFilters { +} diff --git a/src/material-experimental/mdc-table/testing/table-harness.spec.ts b/src/material-experimental/mdc-table/testing/table-harness.spec.ts new file mode 100644 index 000000000000..c9a83a773e7d --- /dev/null +++ b/src/material-experimental/mdc-table/testing/table-harness.spec.ts @@ -0,0 +1,7 @@ +import {MatTableModule} from '@angular/material-experimental/mdc-table'; +import {runHarnessTests} from '@angular/material/table/testing/shared.spec'; +import {MatTableHarness} from './table-harness'; + +describe('MDC-based MatTableHarness', () => { + runHarnessTests(MatTableModule, MatTableHarness); +}); diff --git a/src/material-experimental/mdc-table/testing/table-harness.ts b/src/material-experimental/mdc-table/testing/table-harness.ts new file mode 100644 index 000000000000..2db3374a8af2 --- /dev/null +++ b/src/material-experimental/mdc-table/testing/table-harness.ts @@ -0,0 +1,110 @@ +/** + * @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 {TableHarnessFilters, RowHarnessFilters} from './table-harness-filters'; +import { + MatRowHarness, + MatHeaderRowHarness, + MatFooterRowHarness, + MatRowHarnessColumnsText, +} from './row-harness'; + +/** Text extracted from a table organized by columns. */ +export interface MatTableHarnessColumnsText { + [columnName: string]: { + text: string[]; + headerText: string[]; + footerText: string[]; + }; +} + +/** Harness for interacting with an MDC-based mat-table in tests. */ +export class MatTableHarness extends ComponentHarness { + /** The selector for the host element of a `MatTableHarness` instance. */ + static hostSelector = '.mat-mdc-table'; + + /** + * Gets a `HarnessPredicate` that can be used to search for a table with specific attributes. + * @param options Options for narrowing the search + * @return a `HarnessPredicate` configured with the given options. + */ + static with(options: TableHarnessFilters = {}): HarnessPredicate { + return new HarnessPredicate(MatTableHarness, options); + } + + /** Gets all of the header rows in a table. */ + async getHeaderRows(filter: RowHarnessFilters = {}): Promise { + return this.locatorForAll(MatHeaderRowHarness.with(filter))(); + } + + /** Gets all of the regular data rows in a table. */ + async getRows(filter: RowHarnessFilters = {}): Promise { + return this.locatorForAll(MatRowHarness.with(filter))(); + } + + /** Gets all of the footer rows in a table. */ + async getFooterRows(filter: RowHarnessFilters = {}): Promise { + return this.locatorForAll(MatFooterRowHarness.with(filter))(); + } + + /** Gets the text inside the entire table organized by rows. */ + async getCellTextByIndex(): Promise { + const rows = await this.getRows(); + return Promise.all(rows.map(row => row.getCellTextByIndex())); + } + + /** Gets the text inside the entire table organized by columns. */ + async getCellTextByColumnName(): Promise { + const [headerRows, footerRows, dataRows] = await Promise.all([ + this.getHeaderRows(), + this.getFooterRows(), + this.getRows() + ]); + + const text: MatTableHarnessColumnsText = {}; + const [headerData, footerData, rowsData] = await Promise.all([ + Promise.all(headerRows.map(row => row.getCellTextByColumnName())), + Promise.all(footerRows.map(row => row.getCellTextByColumnName())), + Promise.all(dataRows.map(row => row.getCellTextByColumnName())), + ]); + + rowsData.forEach(data => { + Object.keys(data).forEach(columnName => { + const cellText = data[columnName]; + + if (!text[columnName]) { + text[columnName] = { + headerText: getCellTextsByColumn(headerData, columnName), + footerText: getCellTextsByColumn(footerData, columnName), + text: [] + }; + } + + text[columnName].text.push(cellText); + }); + }); + + return text; + } +} + +/** Extracts the text of cells only under a particular column. */ +function getCellTextsByColumn(rowsData: MatRowHarnessColumnsText[], column: string): string[] { + const columnTexts: string[] = []; + + rowsData.forEach(data => { + Object.keys(data).forEach(columnName => { + if (columnName === column) { + columnTexts.push(data[columnName]); + } + }); + }); + + return columnTexts; +}