Comprehensive testing utilities and harnesses for all Angular Material components, enabling reliable component testing with Angular Testing Library and Jasmine/Jest.
Component test harnesses for automated testing of Material components.
/**
* Base class for component test harnesses
*/
abstract class ComponentHarness {
protected constructor(elementRef: TestElement);
protected locatorFor<T extends ComponentHarness>(selector: string): AsyncFactoryFn<T>;
protected locatorForOptional<T extends ComponentHarness>(selector: string): AsyncFactoryFn<T | null>;
protected locatorForAll<T extends ComponentHarness>(selector: string): AsyncFactoryFn<T[]>;
protected async waitForTasksOutsideAngular(): Promise<void>;
protected async forceStabilize(): Promise<void>;
async host(): Promise<TestElement>;
}
/**
* Button harness for testing button components
*/
class MatButtonHarness extends ComponentHarness {
static hostSelector = '.mat-mdc-button';
static with(options: ButtonHarnessFilters): HarnessPredicate<MatButtonHarness>;
async click(): Promise<void>;
async isDisabled(): Promise<boolean>;
async getText(): Promise<string>;
async focus(): Promise<void>;
async blur(): Promise<void>;
async isFocused(): Promise<boolean>;
async getVariant(): Promise<'text' | 'raised' | 'stroked' | 'flat'>;
}
/**
* Input harness for testing input components
*/
class MatInputHarness extends ComponentHarness {
static hostSelector = '.mat-mdc-input-element';
static with(options: InputHarnessFilters): HarnessPredicate<MatInputHarness>;
async getValue(): Promise<string>;
async setValue(newValue: string): Promise<void>;
async isDisabled(): Promise<boolean>;
async isRequired(): Promise<boolean>;
async isReadonly(): Promise<boolean>;
async getType(): Promise<string>;
async getName(): Promise<string>;
async getId(): Promise<string>;
async getPlaceholder(): Promise<string>;
async focus(): Promise<void>;
async blur(): Promise<void>;
async isFocused(): Promise<boolean>;
async sendKeys(...keys: (string | TestKey)[]): Promise<void>;
}
/**
* Form field harness for testing form field containers
*/
class MatFormFieldHarness extends ComponentHarness {
static hostSelector = '.mat-mdc-form-field';
static with(options: FormFieldHarnessFilters): HarnessPredicate<MatFormFieldHarness>;
async getControl(): Promise<MatInputHarness | MatSelectHarness | MatDatepickerInputHarness | null>;
async getLabel(): Promise<string | null>;
async hasLabel(): Promise<boolean>;
async getErrors(): Promise<string[]>;
async getHints(): Promise<string[]>;
async isRequired(): Promise<boolean>;
async isDisabled(): Promise<boolean>;
async isValid(): Promise<boolean>;
async hasErrors(): Promise<boolean>;
async getAppearance(): Promise<'fill' | 'outline'>;
async getThemeColor(): Promise<'primary' | 'accent' | 'warn'>;
async isLabelFloating(): Promise<boolean>;
}
/**
* Select harness for testing select components
*/
class MatSelectHarness extends ComponentHarness {
static hostSelector = '.mat-mdc-select';
static with(options: SelectHarnessFilters): HarnessPredicate<MatSelectHarness>;
async open(): Promise<void>;
async close(): Promise<void>;
async isOpen(): Promise<boolean>;
async getOptions(filter?: OptionHarnessFilters): Promise<MatOptionHarness[]>;
async clickOptions(filter?: OptionHarnessFilters): Promise<void>;
async getValue(): Promise<string>;
async getValueText(): Promise<string>;
async isDisabled(): Promise<boolean>;
async isRequired(): Promise<boolean>;
async isMultiple(): Promise<boolean>;
async focus(): Promise<void>;
async blur(): Promise<void>;
async isFocused(): Promise<boolean>;
}
/**
* Option harness for testing option components
*/
class MatOptionHarness extends ComponentHarness {
static hostSelector = '.mat-mdc-option';
static with(options: OptionHarnessFilters): HarnessPredicate<MatOptionHarness>;
async click(): Promise<void>;
async getText(): Promise<string>;
async getValue(): Promise<string>;
async isSelected(): Promise<boolean>;
async isActive(): Promise<boolean>;
async isDisabled(): Promise<boolean>;
async isMultiple(): Promise<boolean>;
}
/**
* Checkbox harness for testing checkbox components
*/
class MatCheckboxHarness extends ComponentHarness {
static hostSelector = '.mat-mdc-checkbox';
static with(options: CheckboxHarnessFilters): HarnessPredicate<MatCheckboxHarness>;
async isChecked(): Promise<boolean>;
async isIndeterminate(): Promise<boolean>;
async isDisabled(): Promise<boolean>;
async isRequired(): Promise<boolean>;
async isValid(): Promise<boolean>;
async getName(): Promise<string | null>;
async getValue(): Promise<string | null>;
async getAriaLabel(): Promise<string | null>;
async getAriaLabelledby(): Promise<string | null>;
async getLabelText(): Promise<string>;
async check(): Promise<void>;
async uncheck(): Promise<void>;
async toggle(): Promise<void>;
async focus(): Promise<void>;
async blur(): Promise<void>;
async isFocused(): Promise<boolean>;
}
/**
* Dialog harness for testing dialog components
*/
class MatDialogHarness extends ComponentHarness {
static hostSelector = '.mat-mdc-dialog-container';
static with(options: DialogHarnessFilters): HarnessPredicate<MatDialogHarness>;
async getId(): Promise<string | null>;
async getRole(): Promise<DialogRole | null>;
async getAriaLabel(): Promise<string | null>;
async getAriaLabelledby(): Promise<string | null>;
async getAriaDescribedby(): Promise<string | null>;
async close(): Promise<void>;
}
/**
* Snack bar harness for testing snack bar components
*/
class MatSnackBarHarness extends ComponentHarness {
static hostSelector = '.mat-mdc-snack-bar-container';
static with(options: SnackBarHarnessFilters): HarnessPredicate<MatSnackBarHarness>;
async getMessage(): Promise<string>;
async getActionDescription(): Promise<string>;
async hasAction(): Promise<boolean>;
async dismissWithAction(): Promise<void>;
async dismiss(): Promise<void>;
async isDismissed(): Promise<boolean>;
}
/**
* Table harness for testing table components
*/
class MatTableHarness extends ComponentHarness {
static hostSelector = '.mat-mdc-table';
static with(options: TableHarnessFilters): HarnessPredicate<MatTableHarness>;
async getHeaderRows(filter?: RowHarnessFilters): Promise<MatHeaderRowHarness[]>;
async getRows(filter?: RowHarnessFilters): Promise<MatRowHarness[]>;
async getFooterRows(filter?: RowHarnessFilters): Promise<MatFooterRowHarness[]>;
async getCellTextByIndex(filter: RowHarnessFilters, columnIndex: number): Promise<string[]>;
async getCellTextByColumnName(filter: RowHarnessFilters, columnName: string): Promise<string[]>;
}
/**
* Tabs harness for testing tab components
*/
class MatTabGroupHarness extends ComponentHarness {
static hostSelector = '.mat-mdc-tab-group';
static with(options: TabGroupHarnessFilters): HarnessPredicate<MatTabGroupHarness>;
async getTabs(filter?: TabHarnessFilters): Promise<MatTabHarness[]>;
async getSelectedTab(): Promise<MatTabHarness | null>;
async selectTab(filter: TabHarnessFilters): Promise<void>;
}
/**
* Tab harness for testing individual tabs
*/
class MatTabHarness extends ComponentHarness {
static hostSelector = '.mat-mdc-tab';
static with(options: TabHarnessFilters): HarnessPredicate<MatTabHarness>;
async getLabel(): Promise<string>;
async getAriaLabel(): Promise<string | null>;
async getAriaLabelledby(): Promise<string | null>;
async isSelected(): Promise<boolean>;
async isDisabled(): Promise<boolean>;
async select(): Promise<void>;
async getTextContent(): Promise<string>;
}
/**
* Menu harness for testing menu components
*/
class MatMenuHarness extends ComponentHarness {
static hostSelector = '.mat-mdc-menu-panel';
static with(options: MenuHarnessFilters): HarnessPredicate<MatMenuHarness>;
async isOpen(): Promise<boolean>;
async getTriggerText(): Promise<string>;
async focus(): Promise<void>;
async blur(): Promise<void>;
async isFocused(): Promise<boolean>;
async open(): Promise<void>;
async close(): Promise<void>;
async getItems(filters?: MenuItemHarnessFilters): Promise<MatMenuItemHarness[]>;
async clickItem(itemFilter: MenuItemHarnessFilters, ...subItemFilters: MenuItemHarnessFilters[]): Promise<void>;
}
/**
* Datepicker harness for testing datepicker components
*/
class MatDatepickerInputHarness extends ComponentHarness {
static hostSelector = '.mat-datepicker-input';
static with(options: DatepickerInputHarnessFilters): HarnessPredicate<MatDatepickerInputHarness>;
async getValue(): Promise<string>;
async setValue(newValue: string): Promise<void>;
async openCalendar(): Promise<void>;
async closeCalendar(): Promise<void>;
async isCalendarOpen(): Promise<boolean>;
async isDisabled(): Promise<boolean>;
async isRequired(): Promise<boolean>;
async getMin(): Promise<string | null>;
async getMax(): Promise<string | null>;
}Type definitions for filtering test harnesses.
/**
* Base harness filters
*/
interface BaseHarnessFilters {
selector?: string;
ancestor?: string;
}
/**
* Button harness filters
*/
interface ButtonHarnessFilters extends BaseHarnessFilters {
text?: string | RegExp;
variant?: 'text' | 'raised' | 'stroked' | 'flat';
disabled?: boolean;
}
/**
* Input harness filters
*/
interface InputHarnessFilters extends BaseHarnessFilters {
value?: string | RegExp;
placeholder?: string | RegExp;
type?: string;
disabled?: boolean;
}
/**
* Form field harness filters
*/
interface FormFieldHarnessFilters extends BaseHarnessFilters {
floatingLabelText?: string | RegExp;
hasErrors?: boolean;
}
/**
* Select harness filters
*/
interface SelectHarnessFilters extends BaseHarnessFilters {
disabled?: boolean;
required?: boolean;
multiple?: boolean;
}
/**
* Option harness filters
*/
interface OptionHarnessFilters extends BaseHarnessFilters {
text?: string | RegExp;
isSelected?: boolean;
disabled?: boolean;
}
/**
* Checkbox harness filters
*/
interface CheckboxHarnessFilters extends BaseHarnessFilters {
label?: string | RegExp;
name?: string;
checked?: boolean;
disabled?: boolean;
}
/**
* Dialog harness filters
*/
interface DialogHarnessFilters extends BaseHarnessFilters {
// No additional filters for dialog
}
/**
* Snack bar harness filters
*/
interface SnackBarHarnessFilters extends BaseHarnessFilters {
// No additional filters for snack bar
}
/**
* Table harness filters
*/
interface TableHarnessFilters extends BaseHarnessFilters {
// No additional filters for table
}
/**
* Row harness filters
*/
interface RowHarnessFilters extends BaseHarnessFilters {
// No additional filters for row
}
/**
* Tab group harness filters
*/
interface TabGroupHarnessFilters extends BaseHarnessFilters {
selectedTabLabel?: string | RegExp;
}
/**
* Tab harness filters
*/
interface TabHarnessFilters extends BaseHarnessFilters {
label?: string | RegExp;
selected?: boolean;
disabled?: boolean;
}
/**
* Menu harness filters
*/
interface MenuHarnessFilters extends BaseHarnessFilters {
triggerText?: string | RegExp;
}
/**
* Menu item harness filters
*/
interface MenuItemHarnessFilters extends BaseHarnessFilters {
text?: string | RegExp;
hasSubmenu?: boolean;
}
/**
* Datepicker input harness filters
*/
interface DatepickerInputHarnessFilters extends BaseHarnessFilters {
value?: string | RegExp;
placeholder?: string | RegExp;
}Utility constants and functions for testing with dates and other common scenarios.
/**
* Month constants for easier date testing
*/
const JAN = 0;
const FEB = 1;
const MAR = 2;
const APR = 3;
const MAY = 4;
const JUN = 5;
const JUL = 6;
const AUG = 7;
const SEP = 8;
const OCT = 9;
const NOV = 10;
const DEC = 11;
/**
* Test key constants
*/
enum TestKey {
BACKSPACE,
TAB,
ENTER,
SHIFT,
CONTROL,
ALT,
PAUSE,
ESCAPE,
SPACE,
PAGE_UP,
PAGE_DOWN,
END,
HOME,
LEFT_ARROW,
UP_ARROW,
RIGHT_ARROW,
DOWN_ARROW,
INSERT,
DELETE,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12,
META
}
/**
* Test element interface
*/
interface TestElement {
blur(): Promise<void>;
clear(): Promise<void>;
click(): Promise<void>;
focus(): Promise<void>;
getCssValue(property: string): Promise<string>;
hover(): Promise<void>;
mouseAway(): Promise<void>;
sendKeys(...keys: (string | TestKey)[]): Promise<void>;
text(): Promise<string>;
getAttribute(name: string): Promise<string | null>;
hasClass(name: string): Promise<boolean>;
getDimensions(): Promise<ElementDimensions>;
getProperty(name: string): Promise<any>;
setInputValue(value: string): Promise<void>;
selectOptions(...optionIndexes: number[]): Promise<void>;
matchesSelector(selector: string): Promise<boolean>;
isFocused(): Promise<boolean>;
dispatchEvent(name: string, data?: Record<string, EventInit>): Promise<void>;
}
/**
* Element dimensions interface
*/
interface ElementDimensions {
top: number;
left: number;
width: number;
height: number;
}
/**
* Harness predicate for filtering
*/
class HarnessPredicate<T extends ComponentHarness> {
static stringMatches(s: string | Promise<string>, pattern: string | RegExp): Promise<boolean>;
add(description: string, predicate: AsyncPredicate<T>): this;
addOption<O>(name: string, option: O | undefined, predicate: AsyncOptionPredicate<T, O>): this;
filter(harnesses: T[]): Promise<T[]>;
evaluate(harness: T): Promise<boolean>;
getDescription(): string;
getSelector(): string;
}
/**
* Async predicate function type
*/
type AsyncPredicate<T> = (item: T) => Promise<boolean>;
/**
* Async option predicate function type
*/
type AsyncOptionPredicate<T, O> = (item: T, option: O) => Promise<boolean>;
/**
* Async factory function type
*/
type AsyncFactoryFn<T> = () => Promise<T>;Usage Examples:
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatButtonHarness } from '@angular/material/button/testing';
import { MatInputHarness } from '@angular/material/input/testing';
import { MatFormFieldHarness } from '@angular/material/form-field/testing';
import { MatSelectHarness } from '@angular/material/select/testing';
import { MatCheckboxHarness } from '@angular/material/checkbox/testing';
import { MatDialogHarness } from '@angular/material/dialog/testing';
import { JAN, FEB, MAR } from '@angular/material/testing';
describe('MyComponent', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
let loader: HarnessLoader;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [MyComponent]
}).compileComponents();
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
loader = TestbedHarnessEnvironment.loader(fixture);
});
it('should interact with button', async () => {
const button = await loader.getHarness(MatButtonHarness.with({text: 'Click me'}));
expect(await button.isDisabled()).toBe(false);
expect(await button.getText()).toBe('Click me');
await button.click();
expect(component.clicked).toBe(true);
});
it('should fill out form', async () => {
// Get form field harnesses
const nameField = await loader.getHarness(
MatFormFieldHarness.with({floatingLabelText: 'Name'})
);
const emailField = await loader.getHarness(
MatFormFieldHarness.with({floatingLabelText: 'Email'})
);
// Get input controls
const nameInput = await nameField.getControl() as MatInputHarness;
const emailInput = await emailField.getControl() as MatInputHarness;
// Fill out form
await nameInput.setValue('John Doe');
await emailInput.setValue('john@example.com');
expect(await nameInput.getValue()).toBe('John Doe');
expect(await emailInput.getValue()).toBe('john@example.com');
// Check validation
expect(await nameField.hasErrors()).toBe(false);
expect(await emailField.hasErrors()).toBe(false);
});
it('should select from dropdown', async () => {
const select = await loader.getHarness(MatSelectHarness);
expect(await select.isOpen()).toBe(false);
await select.open();
expect(await select.isOpen()).toBe(true);
const options = await select.getOptions();
expect(options.length).toBe(3);
await select.clickOptions({text: 'Option 2'});
expect(await select.getValueText()).toBe('Option 2');
});
it('should check checkbox', async () => {
const checkbox = await loader.getHarness(
MatCheckboxHarness.with({label: 'Accept terms'})
);
expect(await checkbox.isChecked()).toBe(false);
await checkbox.check();
expect(await checkbox.isChecked()).toBe(true);
await checkbox.uncheck();
expect(await checkbox.isChecked()).toBe(false);
});
it('should test dialog', async () => {
// Trigger dialog open
const openButton = await loader.getHarness(
MatButtonHarness.with({text: 'Open Dialog'})
);
await openButton.click();
// Get dialog harness
const dialog = await loader.getHarness(MatDialogHarness);
expect(await dialog.getId()).toBe('my-dialog');
expect(await dialog.getRole()).toBe('dialog');
// Interact with dialog content
const closeButton = await loader.getHarness(
MatButtonHarness.with({text: 'Close'})
);
await closeButton.click();
});
it('should test date operations', () => {
// Use month constants for clearer date testing
const testDate = new Date(2023, JAN, 15); // January 15, 2023
const anotherDate = new Date(2023, FEB, 28); // February 28, 2023
component.setStartDate(testDate);
component.setEndDate(anotherDate);
expect(component.getDateRange()).toEqual({
start: testDate,
end: anotherDate
});
});
it('should test async operations', async () => {
const loadButton = await loader.getHarness(
MatButtonHarness.with({text: 'Load Data'})
);
await loadButton.click();
// Wait for async operation to complete
await fixture.whenStable();
expect(component.isLoading).toBe(false);
expect(component.data.length).toBeGreaterThan(0);
});
});