Skip to content

Commit

Permalink
Move to cdk harnesses (#1)
Browse files Browse the repository at this point in the history
* install pachages

* stage one

* replacing pageObject with cdk harness

* update readme
remove left overs
  • Loading branch information
eyalellenbogen authored Nov 2, 2020
1 parent 9682a59 commit 72c6c9e
Show file tree
Hide file tree
Showing 11 changed files with 547 additions and 1,889 deletions.
98 changes: 24 additions & 74 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# NG Test Tools

NG Test Tools is an opinionated library for Angular component tests based on Jasmine. It provides tools for bootstrapping tests and helpers for controlling the TestBed and the Page Objects.
NG Test Tools is an opinionated library for Angular component tests based on Jasmine. It provides tools for bootstrapping tests and helpers for controlling the TestBed and the Component Harnesses.

## The premise

Expand All @@ -10,7 +10,7 @@ The best way to test a component is to treat it as one single unit including its

To test a component's behavior we wrap it in a host component and drive it by modifying it's inputs. We do that by changing host properties that are bound to the component's inputs.

We analyze the behavior by spying on host callbacks that are bound to the component's outputs and monitor UI changes by watching the associated component PageObject.
We analyze the behavior by spying on host callbacks that are bound to the component's outputs and monitor UI changes by watching the associated ComponentHarness.

## The details

Expand All @@ -26,7 +26,7 @@ First, we need a host component to load our expander component.
<lib-expander [title]="titleText">
{{ content }}
</lib-expander>
`
`,
})
class HostComponent {
titleText: string;
Expand Down Expand Up @@ -57,7 +57,7 @@ describe('ExpanderComponent', () => {
const context = TestContext.create(HostComponent)
.withComponent(ExpanderComponent) // <-- adding the component
.build();

beforeEach(async () => {
await context.bootstrap();
});
Expand All @@ -77,7 +77,7 @@ describe('ExpanderComponent', () => {
.withMetaData({
imports: [SomeModule],
providers: [SomeProvider],
declarations: [HostComponent]
declarations: [HostComponent],
})
.build();

Expand Down Expand Up @@ -105,14 +105,11 @@ const context = TestContext.create(HostComponent)
We can also call `bootstrapStable` if our component triggers some zone tasks in its initialization code.

```typescript
const context = TestContext.create(HostComponent)
.withComponent(ExpanderComponent)
.build();

beforeEach(async () => {
await context.bootstrapStable();
});
const context = TestContext.create(HostComponent).withComponent(ExpanderComponent).build();

beforeEach(async () => {
await context.bootstrapStable();
});
```

Our context is set up and we are ready to write some tests!
Expand All @@ -121,7 +118,7 @@ Our context is set up and we are ready to write some tests!

- `create(hostComponent: Type<THost>)` - creates a context for the host component provided
- `withComponent(component: Type<TComponent>)` - adds access to the component instance
- `withPageObject<T extends PageObject>(pageObject: T)` - instantiates a PageObject with the type provided and adds access to it
- `withHarness<T extends ComponentHarness>(harness: T)` - instantiates a ComponentHarness with the type provided and adds access to it
- `withMetaData(metadata: TestModuleMetadata)` - overrides the default module metadata used for the test
- `useStableZone()` - waits for any async tasks triggered by component initiation to complete
- `runBeforeCompile(func: ()=>void)` - allows to run code in a `beforeEach` statement before calling `TestBed.compileComponents()`
Expand All @@ -138,85 +135,38 @@ The context we created above contains a few properties and utility methods for c
- `element` - holds the reference to the HTML element holding the component
- `fixture` - holds the reference to the TestFixture that was created by the TestBed
- `host` - holds the host instance created after compilation
- `pageObject` - holds the reference for the PageObject instance that was created by the library (will be undefined if we didn't use `withPageObject`)
- `harness` - holds the reference for the ComponentHarness instance that was created by the library (will be undefined if we didn't use `withHarness`)

#### Methods

- `detectChanges` - a shortcut to `fixture.detectChanges`
- `resetComponentReference` - queries the `debugElement` for the component instance and repopulates it and the PageObject (if applicable).
- `setHostProp(propObject, callDetectChanges)` - a helper function to modify host properties and an option to call `detectChanges` as the 2nd parameter.

### Working with PageObjects

A PageObject is a representation of component elements through code in an object oriented way. To set up a PageObject we create a class and extend the `PageObject` class.

```typescript
class ExpanderPageObject extends PageObject {}
```
### Working with Harnesses

Extending `PageObject` gives us access to its protected methods such as `getElement` and `getElements`. These methods help us reference elements in the DOM.
A ComponentHarness is a representation of component elements through code in an object oriented way. To set up a ComponentHarness we create a class and extend the CDK's `ComponentHarness` class.

```typescript
class ExpanderPageObject extends PageObject {
get header() {
return this.getElement('.expander__header');
}
get expandedPanel() {
return this.getElement('.expander__panel');
}
class ExpanderHarness extends ComponentHarness {
statis hostSelector = '.app-expander';
public getTitleElement = this.loaderFor('.app-expander__title');
}
```

We can use nested PageObjects to represent a structure.
For more information about using Harnesses see the [Angular CDK Harnesses](https://material.angular.io/cdk/test-harnesses/overview).

```typescript
class ExpanderPageObject extends PageObject {
get header() {
return this.getElement('.expander__header', ExpanderHeader); // <-- referencing another PageObject
}
get expandedPanel() {
return this.getElement('.expander__panel');
}
}

class ExpanderHeader {
get title() {
return this.getElement('.expander__title');
}
get closeButton() {
return this.getElement('.expander__close-button');
}
}
```

Before using this PageObject we created, we first need to tell our TestContext about it.
In order to use our Harness we first need to tell our TestContext about it.

```typescript
const context = TestContext.create(HostComponent)
.withComponent(ExpanderComponent)
.withPageObject(ExpanderPageObject)
.bootstrap();
const context = TestContext.create(HostComponent).withComponent(ExpanderComponent).withHarness(ExpanderHarness).bootstrap();
```

Then, in our tests, we can use this PageObject to navigate to the element we are monitoring.
Then, in our tests, we can use this Harness to navigate to the element we are monitoring.

```typescript
it('should show the correct title', () => {
expect(ctx.pageObject.header.title).toBe(ctx.host.titleText);
it('should show the correct title', async () => {
const title = await ctx.harness.getTitleElement();
const titleText = await title.text();
expect(titleText).toBe(ctx.host.titleText);
});
```

#### The PageObject public API

- `__nativeElement` - access to the HTMLElement node associated with this PageObject
- `classList` - shortcute reference to `nativeElement.classList`
- `clientRect` - shortcut reference to `nativeElement.getBoundingClientRect()`
- `innerText` - shortcut reference to `nativeElement.innerText`
- `innerHTML` - shortcut reference to `nativeElement.innerHTML`

##### Methods

- `click()` - shortcut reference to `nativeElement.click()`
- `dispatchEvent(event: Event)` - shortcut reference to `nativeElement.dispatchEvent(event)`
- `focus()` - shortcut reference to `nativeElement.focus()`
- `getAttribute(key: string)` - shortcute reference to `nativeElement.getAttribute(key)`
Loading

0 comments on commit 72c6c9e

Please sign in to comment.