or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

accessibility.mdaccordion.mdcollections-data.mddialogs.mddrag-drop.mdindex.mdlistbox.mdmenus.mdobservers.mdoverlays.mdplatform-utilities.mdportals.mdscrolling.mdtesting.mdtext-fields.md
tile.json

testing.mddocs/

Testing

The Angular CDK Testing module provides a comprehensive component harness system for creating reliable, maintainable component tests. It abstracts away implementation details and provides a consistent API for interacting with components across different test environments.

Capabilities

Component Harness

Base class for creating component test harnesses that provide a high-level API for interacting with components.

/**
 * Base class for component harnesses
 */
abstract class ComponentHarness {
  /**
   * Get the host element for this harness
   * @returns Promise resolving to the host TestElement
   */
  host(): Promise<TestElement>;
  
  /**
   * Create a locator for a single sub-component
   * @param selector - Harness constructor or predicate
   * @returns Function that locates the component
   */
  locatorFor<T extends ComponentHarness>(
    selector: ComponentHarnessConstructor<T> | HarnessPredicate<T>
  ): AsyncFactoryFn<T>;
  
  /**
   * Create a locator for an optional sub-component
   * @param selector - Harness constructor or predicate
   * @returns Function that locates the component or returns null
   */
  locatorForOptional<T extends ComponentHarness>(
    selector: ComponentHarnessConstructor<T> | HarnessPredicate<T>
  ): AsyncFactoryFn<T | null>;
  
  /**
   * Create a locator for all matching sub-components
   * @param selector - Harness constructor or predicate
   * @returns Function that locates all matching components
   */
  locatorForAll<T extends ComponentHarness>(
    selector: ComponentHarnessConstructor<T> | HarnessPredicate<T>
  ): AsyncFactoryFn<T[]>;
  
  /**
   * Wait for asynchronous tasks outside Angular to complete
   * @returns Promise that resolves when tasks are complete
   */
  waitForTasksOutsideAngular(): Promise<void>;
  
  /**
   * Force Angular change detection and wait for async tasks
   * @returns Promise that resolves when stabilized
   */
  forceStabilize(): Promise<void>;
  
  /**
   * Create a child harness environment
   * @param selector - CSS selector for child element
   * @returns Promise of child harness environment
   */
  protected childEnvironment(selector: string): Promise<HarnessEnvironment<Element>>;
  
  /**
   * Get all child elements matching a selector
   * @param selector - CSS selector
   * @returns Promise of TestElement array
   */
  protected getAllChildElements(selector: string): Promise<TestElement[]>;
}

/**
 * Constructor type for component harnesses
 */
interface ComponentHarnessConstructor<T extends ComponentHarness> {
  new (locator: LocatorFnResult<T>): T;
  hostSelector: string;
}

/**
 * Function type for async factory functions
 */
interface AsyncFactoryFn<T> {
  (): Promise<T>;
}

Test Element

Interface representing an element in tests with methods for interaction.

/**
 * Interface for elements in component tests
 */
interface TestElement {
  /**
   * Blur the element
   * @returns Promise that resolves when blur is complete
   */
  blur(): Promise<void>;
  
  /**
   * Clear the element's value (for input elements)
   * @returns Promise that resolves when clear is complete
   */
  clear(): Promise<void>;
  
  /**
   * Click the element
   * @param relativeX - X coordinate relative to element
   * @param relativeY - Y coordinate relative to element
   * @returns Promise that resolves when click is complete
   */
  click(relativeX?: number, relativeY?: number): Promise<void>;
  
  /**
   * Focus the element
   * @returns Promise that resolves when focus is complete
   */
  focus(): Promise<void>;
  
  /**
   * Get a CSS property value
   * @param property - CSS property name
   * @returns Promise resolving to property value
   */
  getCssValue(property: string): Promise<string>;
  
  /**
   * Hover over the element
   * @returns Promise that resolves when hover is complete
   */
  hover(): Promise<void>;
  
  /**
   * Move mouse away from the element
   * @returns Promise that resolves when mouse move is complete
   */
  mouseAway(): Promise<void>;
  
  /**
   * Send keystrokes to the element
   * @param keys - Keys to send (can include special keys)
   * @returns Promise that resolves when keys are sent
   */
  sendKeys(...keys: (string | TestKey)[]): Promise<void>;
  
  /**
   * Get the element's text content
   * @returns Promise resolving to text content
   */
  text(): Promise<string>;
  
  /**
   * Get an attribute value
   * @param name - Attribute name
   * @returns Promise resolving to attribute value or null
   */
  getAttribute(name: string): Promise<string | null>;
  
  /**
   * Check if element has a CSS class
   * @param name - Class name
   * @returns Promise resolving to true if class exists
   */
  hasClass(name: string): Promise<boolean>;
  
  /**
   * Get element dimensions and position
   * @returns Promise resolving to element dimensions
   */
  getDimensions(): Promise<ElementDimensions>;
  
  /**
   * Get a DOM property value
   * @param name - Property name
   * @returns Promise resolving to property value
   */
  getProperty(name: string): Promise<any>;
  
  /**
   * Set the value of an input element
   * @param value - Value to set
   * @returns Promise that resolves when value is set
   */
  setInputValue(value: string): Promise<void>;
  
  /**
   * Select options in a select element
   * @param optionIndexes - Indexes of options to select
   * @returns Promise that resolves when options are selected
   */
  selectOptions(...optionIndexes: number[]): Promise<void>;
  
  /**
   * Check if element matches a CSS selector
   * @param selector - CSS selector
   * @returns Promise resolving to true if matches
   */
  matchesSelector(selector: string): Promise<boolean>;
  
  /**
   * Check if element is focused
   * @returns Promise resolving to true if focused
   */
  isFocused(): Promise<boolean>;
  
  /**
   * Dispatch a custom event
   * @param name - Event name
   * @param data - Event data
   * @returns Promise that resolves when event is dispatched
   */
  dispatchEvent(name: string, data?: Record<string, EventData>): Promise<void>;
}

/**
 * Element dimensions and position information
 */
interface ElementDimensions {
  top: number;
  left: number;
  width: number;
  height: number;
}

/**
 * Event data type for custom events
 */
type EventData = string | number | boolean | undefined | null | EventData[] | {[key: string]: EventData};

Harness Environment

Abstract base class for harness execution environments.

/**
 * Base class for harness execution environments
 * @template E Element type for the environment
 */
abstract class HarnessEnvironment<E> {
  /**
   * Create a TestElement from a raw element
   * @param element - Raw element
   * @returns TestElement wrapper
   */
  protected abstract createTestElement(element: E): TestElement;
  
  /**
   * Create a child environment for an element
   * @param element - Element for child environment
   * @returns Child harness environment
   */
  protected abstract createEnvironment(element: E): HarnessEnvironment<E>;
  
  /**
   * Get all raw elements matching a selector
   * @param selector - CSS selector
   * @returns Promise of raw elements
   */
  protected abstract getAllRawElements(selector: string): Promise<E[]>;
  
  /**
   * Get the current task state
   * @returns Promise of task state information
   */
  protected abstract requestTaskState(): Promise<TaskState>;
  
  /**
   * Wait for asynchronous tasks outside Angular
   * @returns Promise that resolves when tasks complete
   */
  protected abstract waitForTasksOutsideAngular(): Promise<void>;
}

/**
 * Task state information
 */
interface TaskState {
  /** Whether there are pending microtasks */
  hasPendingMicrotasks: boolean;
  
  /** Whether there are pending macrotasks */
  hasPendingMacrotasks: boolean;
  
  /** Whether there are pending timeouts */
  hasPendingTimeouts: boolean;
  
  /** Whether there are pending intervals */
  hasPendingIntervals: boolean;
}

Harness Predicate

Builder for creating complex component selection criteria.

/**
 * Builder for harness predicates
 * @template T Harness type
 */
class HarnessPredicate<T extends ComponentHarness> {
  /**
   * Create a harness predicate
   * @param harnessType - Harness constructor
   * @param options - Initial predicate options
   */
  constructor(harnessType: ComponentHarnessConstructor<T>, options: BaseHarnessFilters);
  
  /**
   * Add a custom predicate condition
   * @param description - Description of the condition
   * @param predicate - Predicate function
   * @returns This predicate for chaining
   */
  add<K extends keyof T>(
    description: string, 
    predicate: AsyncPredicate<T>
  ): HarnessPredicate<T>;
  
  /**
   * Add an option-based predicate condition
   * @param name - Option name
   * @param option - Option value or query
   * @param predicate - Predicate function for the option
   * @returns This predicate for chaining
   */
  addOption<K extends keyof T>(
    name: string,
    option: HarnessQuery<T[K]> | null,
    predicate: AsyncOptionPredicate<T, T[K]>
  ): HarnessPredicate<T>;
  
  /**
   * Get description of this predicate
   * @returns Predicate description
   */
  getDescription(): string;
  
  /**
   * Evaluate this predicate against a harness
   * @param harness - Harness to evaluate
   * @returns Promise resolving to true if predicate matches
   */
  evaluate(harness: T): Promise<boolean>;
}

/**
 * Base filters for harness predicates
 */
interface BaseHarnessFilters {
  /** CSS selector that must match */
  selector?: string;
  
  /** Ancestor selector that must exist */
  ancestor?: string;
}

/**
 * Async predicate function type
 */
interface AsyncPredicate<T> {
  (harness: T): Promise<boolean>;
}

/**
 * Async option predicate function type
 */
interface AsyncOptionPredicate<T, O> {
  (harness: T, option: O): Promise<boolean>;
}

/**
 * Harness query type
 */
type HarnessQuery<T> = string | RegExp | ((value: T) => boolean);

Test Environment Implementations

Specific implementations for different test environments.

/**
 * Harness environment for Angular TestBed
 */
class TestbedHarnessEnvironment extends HarnessEnvironment<Element> {
  /**
   * Create a harness loader for a component fixture
   * @param fixture - Component fixture
   * @returns Harness loader for the fixture
   */
  static loader(fixture: ComponentFixture<unknown>): HarnessLoader;
  
  /**
   * Get a harness for the fixture's component
   * @param fixture - Component fixture
   * @param harnessType - Harness constructor
   * @returns Promise of component harness
   */
  static harnessForFixture<T extends ComponentHarness>(
    fixture: ComponentFixture<unknown>,
    harnessType: ComponentHarnessConstructor<T>
  ): Promise<T>;
  
  protected createTestElement(element: Element): TestElement;
  protected createEnvironment(element: Element): HarnessEnvironment<Element>;
  protected getAllRawElements(selector: string): Promise<Element[]>;
  protected requestTaskState(): Promise<TaskState>;
  protected waitForTasksOutsideAngular(): Promise<void>;
}

/**
 * Harness loader interface
 */
interface HarnessLoader {
  /**
   * Get a harness for a component
   * @param harnessType - Harness constructor or predicate  
   * @returns Promise of component harness
   */
  getHarness<T extends ComponentHarness>(
    harnessType: ComponentHarnessConstructor<T> | HarnessPredicate<T>
  ): Promise<T>;
  
  /**
   * Get a harness for a component or null if not found
   * @param harnessType - Harness constructor or predicate
   * @returns Promise of component harness or null
   */
  getHarnessOrNull<T extends ComponentHarness>(
    harnessType: ComponentHarnessConstructor<T> | HarnessPredicate<T>
  ): Promise<T | null>;
  
  /**
   * Get all harnesses for components
   * @param harnessType - Harness constructor or predicate
   * @returns Promise of component harness array
   */
  getAllHarnesses<T extends ComponentHarness>(
    harnessType: ComponentHarnessConstructor<T> | HarnessPredicate<T>
  ): Promise<T[]>;
  
  /**
   * Get child loader for a selector
   * @param selector - CSS selector
   * @returns Promise of child harness loader
   */
  getChildLoader(selector: string): Promise<HarnessLoader>;
  
  /**
   * Get all child loaders for a selector
   * @param selector - CSS selector  
   * @returns Promise of child harness loader array
   */
  getAllChildLoaders(selector: string): Promise<HarnessLoader[]>;
}

Testing Utilities

Utility functions for testing scenarios.

/**
 * Execute multiple async functions in parallel
 * @param values - Array of functions that return promises
 * @returns Promise that resolves to array of results
 */
function parallel<T>(values: (() => T | Promise<T>)[]): Promise<T[]>;

/**
 * Disable automatic change detection for manual control
 * @returns Function to re-enable automatic change detection
 */
function manualChangeDetection(): () => void;

/**
 * Special test keys for keyboard interactions
 */
enum TestKey {
  ALT = 'alt',
  BACKSPACE = 'backspace',
  CONTROL = 'control',
  DELETE = 'delete',
  DOWN_ARROW = 'downarrow',
  END = 'end',
  ENTER = 'enter',
  ESCAPE = 'escape',
  F1 = 'f1',
  F2 = 'f2',
  F3 = 'f3',
  F4 = 'f4',
  F5 = 'f5',
  F6 = 'f6',
  F7 = 'f7',
  F8 = 'f8',
  F9 = 'f9',
  F10 = 'f10',
  F11 = 'f11',
  F12 = 'f12',
  HOME = 'home',
  INSERT = 'insert',
  LEFT_ARROW = 'leftarrow',
  META = 'meta',
  PAGE_DOWN = 'pagedown',
  PAGE_UP = 'pageup',
  RIGHT_ARROW = 'rightarrow',
  SHIFT = 'shift',
  SPACE = 'space',
  TAB = 'tab',
  UP_ARROW = 'uparrow'
}

Module

/**
 * Angular module for testing utilities
 */
@NgModule({})
class TestingModule {}

Usage Patterns

Creating a Component Harness

import { ComponentHarness } from '@angular/cdk/testing';
import { TestKey } from '@angular/cdk/testing';

/**
 * Harness for interacting with a button component
 */
export class ButtonHarness extends ComponentHarness {
  static hostSelector = 'app-button';

  /**
   * Get the button text
   * @returns Promise resolving to button text
   */
  async getText(): Promise<string> {
    const host = await this.host();
    return host.text();
  }

  /**
   * Click the button
   * @returns Promise that resolves when click is complete
   */
  async click(): Promise<void> {
    const host = await this.host();
    return host.click();
  }

  /**
   * Check if button is disabled
   * @returns Promise resolving to true if disabled
   */
  async isDisabled(): Promise<boolean> {
    const host = await this.host();
    return host.hasClass('disabled');
  }

  /**
   * Get button type
   * @returns Promise resolving to button type
   */
  async getType(): Promise<string | null> {
    const host = await this.host();
    return host.getAttribute('type');
  }

  /**
   * Focus the button and press enter
   * @returns Promise that resolves when action is complete
   */
  async focusAndPressEnter(): Promise<void> {
    const host = await this.host();
    await host.focus();
    await host.sendKeys(TestKey.ENTER);
  }
}

Using Harness in Tests

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { ButtonHarness } from './button.harness';
import { ButtonComponent } from './button.component';

describe('ButtonComponent', () => {
  let component: ButtonComponent;
  let fixture: ComponentFixture<ButtonComponent>;
  let loader: HarnessLoader;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [ButtonComponent]
    }).compileComponents();

    fixture = TestBed.createComponent(ButtonComponent);
    component = fixture.componentInstance;
    loader = TestbedHarnessEnvironment.loader(fixture);
  });

  it('should display button text', async () => {
    component.text = 'Click me';
    fixture.detectChanges();

    const button = await loader.getHarness(ButtonHarness);
    const text = await button.getText();
    
    expect(text).toBe('Click me');
  });

  it('should handle click events', async () => {
    spyOn(component, 'onClick');
    
    const button = await loader.getHarness(ButtonHarness);
    await button.click();
    
    expect(component.onClick).toHaveBeenCalled();
  });

  it('should be disabled when disabled property is true', async () => {
    component.disabled = true;
    fixture.detectChanges();

    const button = await loader.getHarness(ButtonHarness);
    const isDisabled = await button.isDisabled();
    
    expect(isDisabled).toBe(true);
  });
});

Complex Harness with Sub-Components

export interface FormHarnessFilters extends BaseHarnessFilters {
  title?: string;
}

/**
 * Harness for a form component
 */
export class FormHarness extends ComponentHarness {
  static hostSelector = 'app-form';

  private _title = this.locatorFor('h2');
  private _submitButton = this.locatorFor(ButtonHarness.with({ text: 'Submit' }));
  private _inputs = this.locatorForAll('input');

  /**
   * Predicate for filtering form harnesses
   */
  static with(options: FormHarnessFilters): HarnessPredicate<FormHarness> {
    return new HarnessPredicate(FormHarness, options)
      .addOption('title', options.title, async (harness, title) => {
        const titleElement = await harness._title();
        const titleText = await titleElement.text();
        return titleText === title;
      });
  }

  /**
   * Get form title
   */
  async getTitle(): Promise<string> {
    const title = await this._title();
    return title.text();
  }

  /**
   * Submit the form
   */
  async submit(): Promise<void> {
    const button = await this._submitButton();
    return button.click();
  }

  /**
   * Fill form field
   */
  async fillField(name: string, value: string): Promise<void> {
    const inputs = await this._inputs();
    for (const input of inputs) {
      const nameAttr = await input.getAttribute('name');
      if (nameAttr === name) {
        await input.setInputValue(value);
        return;
      }
    }
    throw new Error(`Field '${name}' not found`);
  }

  /**
   * Get all form data
   */
  async getFormData(): Promise<Record<string, string>> {
    const inputs = await this._inputs();
    const data: Record<string, string> = {};
    
    for (const input of inputs) {
      const name = await input.getAttribute('name');
      const value = await input.getProperty('value');
      if (name) {
        data[name] = value;
      }
    }
    
    return data;
  }
}

Parallel Testing

import { parallel } from '@angular/cdk/testing';

it('should handle multiple interactions simultaneously', async () => {
  const button1 = await loader.getHarness(ButtonHarness.with({ text: 'Button 1' }));
  const button2 = await loader.getHarness(ButtonHarness.with({ text: 'Button 2' }));
  const button3 = await loader.getHarness(ButtonHarness.with({ text: 'Button 3' }));

  // Click all buttons in parallel
  await parallel(() => [
    button1.click(),
    button2.click(),
    button3.click()
  ]);

  // Verify all buttons were clicked
  // ... assertions
});