diff --git a/src/cdk/testing/testing.md b/src/cdk/testing/testing.md index bde9d78f4fec..d84d944c3a88 100644 --- a/src/cdk/testing/testing.md +++ b/src/cdk/testing/testing.md @@ -36,15 +36,15 @@ Test authors are developers using component harnesses written by someone else to application. For example, this could be an app developer who uses a third-party menu component and needs to interact with the menu in a unit test. -#### `ComponentHarness` +#### Working with `ComponentHarness` classes -This is the abstract base class for all component harnesses. Every harness extends -`ComponentHarness`. All `ComponentHarness` subclasses have a static property, `hostSelector`, that +`ComponentHarness` is the abstract base class for all component harnesses. Every harness extends +this class. All `ComponentHarness` subclasses have a static property, `hostSelector`, that matches the harness class to instances of the component in the DOM. Beyond that, the API of any given harness is specific to its corresponding component; refer to the component's documentation to learn how to use a specific harness. -#### `TestbedHarnessEnvironment` and `ProtractorHarnessEnvironment` +#### Using `TestbedHarnessEnvironment` and `ProtractorHarnessEnvironment` These classes correspond to different implementations of the component harness system with bindings for specific test environments. Any given test must only import _one_ of these classes. Karma-based @@ -117,7 +117,7 @@ Since Protractor does not deal with fixtures, the API in this environment is sim `HarnessLoader` returned by the `loader()` method should be sufficient for loading all necessary `ComponentHarness` instances. -#### `HarnessLoader` +#### Creating harnesses with `HarnessLoader` Instances of this class correspond to a specific DOM element (the "root element" of the loader) and are used to create `ComponentHarness` instances for elements under this root element. @@ -134,8 +134,8 @@ are used to create `ComponentHarness` instances for elements under this root ele Calls to `getHarness` and `getAllHarnesses` can either take `ComponentHarness` subclass or a `HarnessPredicate`. `HarnessPredicate` applies additional restrictions to the search (e.g. searching for a button that has some particular text, etc). The -[details of `HarnessPredicate`](#harnesspredicate) are discussed in the -[API for component harness authors](#api-for-component-harness-authors); harness authors should +[details of `HarnessPredicate`](#filtering-harness-instances-with-harnesspredicate) are discussed in +the [API for component harness authors](#api-for-component-harness-authors); harness authors should provide convenience methods on their `ComponentHarness` subclass to facilitate creation of `HarnessPredicate` instances. However, if the harness author's API is not sufficient, they can be created manually. @@ -169,11 +169,378 @@ it('reads properties in parallel', async () => { ### API for component harness authors -TODO(mmalerba): Fill in docs for harness authors +Component harness authors are developers who maintain some reusable Angular component, and want to +create a test harness for it, that users of the component can use in their tests. For example, this +could be an author of a third party Angular component library or a developer who maintains a set of +common components for a large Angular application. -#### `HarnessPredicate` +#### Extending `ComponentHarness` -TODO(mmalerba): Fill in docs for `HarnessPredicate` +The abstract `ComponentHarness` class is the base class for all component harnesses. To create a +custom component harness, extend `ComponentHarness` and implement the static property +`hostSelector`. The `hostSelector` property identifies elements in the DOM that match this harness +subclass. In most cases the `hostSelector` should be the same as the `selector` of the corresponding +`Component` or `Directive`. For example, consider a simple popup component: + +```ts +@Component({ + selector: 'my-popup', + template: ` + +
+ ` +}) +class MyPopup { + @Input() triggerText: string; + + open = false; + + toggle() { + this.open = !this.open; + } +} +``` + +In this case, a minimal harness for the component would look like the following: + +```ts +class MyPopupHarness extends ComponentHarness { + static hostSelector = 'my-popup'; +} +``` + +While `ComponentHarness` subclasses require only the `hostSelector` property, most harnesses should +also implement a static `with` method to generate `HarnessPredicate` instances. The +[`HarnessPredicate`](#filtering-harness-instances-with-harnesspredicate) section below covers this +in more detail. + +#### Finding elements in the component's DOM + +Each instance of a `ComponentHarness` subclass represents a particular instance of the +corresponding component. You can access the component's host element via the `host` method from +the `ComponentHarness` base class. + +`ComponentHarness` additionally offers several methods for locating elements within the component's +DOM. These methods are `locatorFor`, `locatorForOptional`, and `locatorForAll`. +Note, though, that these methods do not directly find elements. Instead, they _create functions_ +that find elements. This approach safeguards against caching references to out-of-date elements. For +example, when an `ngIf` hides and then shows an element, the result is a new DOM element; using +functions ensures that tests always reference the current state of the DOM. + +| Method | Description | +| ------ | ----------- | +| `host(): Promise` | Returns a `Promise` for the host element of the corresponding component instance. | +| `locatorFor(selector: string): () => Promise` | Creates a function that returns a `Promise` for the first element matching the given selector when called. If no matching element is found, the `Promise` rejects. | +| `locatorForOptional(selector: string): () => Promise` | Creates a function that returns a `Promise` for the first element matching the given selector when called. If no matching element is found, the `Promise` is resolved with `null`. | +| `locatorForAll(selector: string): () => Promise` | Creates a function that returns a `Promise` for a list of all elements matching the given selector when called. | + +For example, the `MyPopupHarness` class discussed above could provide methods to get the trigger +and content elements as follows: + +```ts +class MyPopupHarness extends ComponentHarness { + static hostSelector = 'my-popup'; + + /** Gets the trigger element */ + getTriggerElement = this.locatorFor('button'); + + /** Gets the content element. */ + getContentElement = this.locatorForOptional('.my-popup-content'); +} +``` + +#### Working with `TestElement` instances + +The functions created with the locator methods described above all return `TestElement` instances. +`TestElement` offers a number of methods to interact with the underlying DOM: + +| Method | Description | +| ------ | ----------- | +| `blur(): Promise` | Blurs the element. | +| `clear(): Promise` | Clears the text in the element (intended for `` and `