Skip to content

Commit

Permalink
feat: simpler tools for form controls ngMocks.change and ngMocks.touch
Browse files Browse the repository at this point in the history
  • Loading branch information
satanTime committed Feb 27, 2021
1 parent f85f497 commit 753b975
Show file tree
Hide file tree
Showing 14 changed files with 486 additions and 115 deletions.
5 changes: 5 additions & 0 deletions docs/articles/api/ngMocks.md
Expand Up @@ -17,6 +17,11 @@ access desired elements and instances in fixtures.
* [`globalReplace()`](ngMocks/globalReplace.md)
* [`globalWipe()`](ngMocks/globalWipe.md)

## Simulating form control events

- [`change()`](ngMocks/change.md)
- [`touch()`](ngMocks/touch.md)

## Manipulating `ng-template`

- [`render()`](ngMocks/render.md)
Expand Down
34 changes: 34 additions & 0 deletions docs/articles/api/ngMocks/change.md
@@ -0,0 +1,34 @@
---
title: ngMocks.change
description: Documentation about ngMocks.change from ng-mocks library
---

`ngMocks.change` helps to **simulate external changes of a form control**.
Does not matter whether the declaration of the form control is a **mock** instance or a **real** one.

To simulate a change, we need a **debug element** the form control belongs to, and the desired value for the change.

Let's assume that we have the next template:

```html
<input data-testid="inputControl" [(ngModel)]="value" />
```

And, we want to **simulate a change** of the input which would set value to `123`.

Then solution may look like that:

```ts
// looking for debug element of the input
const el = ngMocks.find(['data-testid', 'inputControl']);

// simulating change
ngMocks.change(el, 123);

// asserting
expect(component.value).toEqual(123);
```

Profit!

It supports both `FormsModule` and `ReactiveFormsModule`.
32 changes: 32 additions & 0 deletions docs/articles/api/ngMocks/touch.md
@@ -0,0 +1,32 @@
---
title: ngMocks.touch
description: Documentation about ngMocks.touch from ng-mocks library
---

`ngMocks.touch` helps to **simulate external touches of a form control**.
Does not matter whether the declaration of the form control is a **mock** instance or a **real** one.

To simulate a touch, we need a **debug element** the form control belongs to.

Let's assume that we have the next template:

```html
<input data-testid="inputControl" [formControl]="myControl" />
```

And, we want to **simulate a touch** of the input.

Then solution may look like that:

```ts
// looking for debug element of the input
const el = ngMocks.find(['data-testid', 'inputControl']);

// simulating touch
ngMocks.touch(valueAccessorEl);

// asserting
expect(component.myControl.touched).toEqual(true);
```

Profit!
96 changes: 35 additions & 61 deletions docs/articles/extra/mock-form-controls.md
Expand Up @@ -6,7 +6,7 @@ sidebar_label: Mock form controls

`ng-mocks` respects `ControlValueAccessor` interface if [a directive](../api/MockDirective.md),
or [a component](../api/MockComponent.md) implements it.
Apart from that, `ng-mocks` provides helper functions to emit changes and touches.
Apart from that, `ng-mocks` provides helper functions to emit [changes](../api/ngMocks/change.md) and [touches](../api/ngMocks/touch.md).

it supports both `FormsModule` and `ReactiveFormsModule`:

Expand All @@ -28,22 +28,13 @@ it supports both `FormsModule` and `ReactiveFormsModule`:
- `registerOnValidatorChange`
- `validate`

## MockControlValueAccessor
## Related tools

A mock object of `ControlValueAccessor` additionally implements `MockControlValueAccessor` and provides:
- [`ngMocks.change()`](../api/ngMocks/change.md)
- [`ngMocks.touch()`](../api/ngMocks/touch.md)

- `__simulateChange(value: any)` - calls `onChanged` on the mock component bound to a `FormControl`
- `__simulateTouch()` - calls `onTouched` on the mock component bound to a `FormControl`

* [`isMockControlValueAccessor( instance )`](../api/helpers/isMockControlValueAccessor.md) - to verify `MockControlValueAccessor`

## MockValidator

A mock object of `Validator` or `AsyncValidator` additionally implements `MockValidator` and provides:

- `__simulateValidatorChange()` - calls `updateValueAndValidity` on the mock component bound to a `FormControl`

* [`isMockValidator( instance )`](../api/helpers/isMockValidator.md) - to verify `MockValidator`
* [`isMockControlValueAccessor()`](../api/helpers/isMockControlValueAccessor.md)
* [`isMockValidator()`](../api/helpers/isMockValidator.md)

## Advanced example

Expand All @@ -55,42 +46,34 @@ Please, pay attention to comments in the code.

```ts
describe('MockReactiveForms', () => {
// That is our spy on writeValue calls.
// With auto spy this code is not needed.
const writeValue = jasmine.createSpy('writeValue');
// in case of jest
// const writeValue = jest.fn();

// Because of early calls of writeValue, we need to install
// the spy in the ctor call.
beforeEach(() =>
MockInstance(DependencyComponent, () => ({
writeValue,
})),
);

beforeEach(() => {
return MockBuilder(TestedComponent)
.mock(DependencyComponent)
.keep(ReactiveFormsModule);
});

it('sends the correct value to the mock form component', () => {
// That is our spy on writeValue calls.
// With auto spy this code is not needed.
const writeValue = jasmine.createSpy('writeValue');
// in case of jest
// const writeValue = jest.fn();

// Because of early calls of writeValue, we need to install
// the spy via MockInstance before the render.
MockInstance(DependencyComponent, 'writeValue', writeValue);

const fixture = MockRender(TestedComponent);
const component = fixture.point.componentInstance;

// Let's find the mock form component.
const mockControl = ngMocks.find(DependencyComponent)
.componentInstance;

// During initialization it should be called
// with null.
expect(writeValue).toHaveBeenCalledWith(null);

// Let's simulate its change, like a user does it.
if (isMockControlValueAccessor(mockControl)) {
mockControl.__simulateChange('foo');
}
// Let's find the form control element
// and simulate its change, like a user does it.
const mockControlEl = ngMocks.find(DependencyComponent);
ngMocks.change(mockControlEl, 'foo');
expect(component.formControl.value).toBe('foo');

// Let's check that change on existing formControl
Expand All @@ -108,44 +91,35 @@ A usage example of mock FormControl with ngModel in Angular tests

```ts
describe('MockForms', () => {
// That is our spy on writeValue calls.
// With auto spy this code is not needed.
const writeValue = jasmine.createSpy('writeValue');
// in case of jest
// const writeValue = jest.fn();

// Because of early calls of writeValue, we need to install
// the spy in the ctor call.
beforeEach(() =>
MockInstance(DependencyComponent, () => ({
writeValue,
})),
);

beforeEach(() => {
return MockBuilder(TestedComponent)
.mock(DependencyComponent)
.keep(FormsModule);
});

it('sends the correct value to the mock form component', async () => {
// That is our spy on writeValue calls.
// With auto spy this code is not needed.
const writeValue = jasmine.createSpy('writeValue');
// in case of jest
// const writeValue = jest.fn();

// Because of early calls of writeValue, we need to install
// the spy via MockInstance before the render.
MockInstance(DependencyComponent, 'writeValue', writeValue);

const fixture = MockRender(TestedComponent);
const component = fixture.point.componentInstance;

// Let's find the mock form component.
const mockControl = ngMocks.find(DependencyComponent)
.componentInstance;

// During initialization it should be called
// with null.
expect(writeValue).toHaveBeenCalledWith(null);

// Let's simulate its change, like a user does it.
if (isMockControlValueAccessor(mockControl)) {
mockControl.__simulateChange('foo');
fixture.detectChanges();
await fixture.whenStable();
}
// Let's find the form control element
// and simulate its change, like a user does it.
const mockControlEl = ngMocks.find(DependencyComponent);
ngMocks.change(mockControlEl, 'foo');
await fixture.whenStable();
expect(component.value).toBe('foo');

// Let's check that change on existing value
Expand Down
2 changes: 2 additions & 0 deletions docs/sidebars.js
Expand Up @@ -36,6 +36,8 @@ module.exports = {
'api/ngMocks/globalMock',
'api/ngMocks/globalReplace',
'api/ngMocks/globalWipe',
'api/ngMocks/change',
'api/ngMocks/touch',
'api/ngMocks/render',
'api/ngMocks/hide',
'api/ngMocks/input',
Expand Down
46 changes: 18 additions & 28 deletions examples/MockForms/test.spec.ts
Expand Up @@ -7,7 +7,6 @@ import {
NG_VALUE_ACCESSOR,
} from '@angular/forms';
import {
isMockControlValueAccessor,
MockBuilder,
MockInstance,
MockRender,
Expand Down Expand Up @@ -45,47 +44,38 @@ class TestedComponent {
}

describe('MockForms', () => {
// That is our spy on writeValue calls.
// With auto spy this code is not needed.
const writeValue =
typeof jest === 'undefined'
? jasmine.createSpy('writeValue')
: jest.fn();
// in case of jest
// const writeValue = jest.fn();

// Because of early calls of writeValue, we need to install
// the spy in the ctor call.
beforeEach(() =>
MockInstance(DependencyComponent, () => ({
writeValue,
})),
);

beforeEach(() => {
return MockBuilder(TestedComponent)
.mock(DependencyComponent)
.keep(FormsModule);
});

it('sends the correct value to the mock form component', async () => {
// That is our spy on writeValue calls.
// With auto spy this code is not needed.
const writeValue =
typeof jest === 'undefined'
? jasmine.createSpy('writeValue')
: jest.fn();
// in case of jest
// const writeValue = jest.fn();

// Because of early calls of writeValue, we need to install
// the spy via MockInstance before the render.
MockInstance(DependencyComponent, 'writeValue', writeValue);

const fixture = MockRender(TestedComponent);
const component = fixture.point.componentInstance;

// Let's find the mock form component.
const mockControl = ngMocks.find(DependencyComponent)
.componentInstance;

// During initialization it should be called
// with null.
expect(writeValue).toHaveBeenCalledWith(null);

// Let's simulate its change, like a user does it.
if (isMockControlValueAccessor(mockControl)) {
mockControl.__simulateChange('foo');
fixture.detectChanges();
await fixture.whenStable();
}
// Let's find the form control element
// and simulate its change, like a user does it.
const mockControlEl = ngMocks.find(DependencyComponent);
ngMocks.change(mockControlEl, 'foo');
await fixture.whenStable();
expect(component.value).toBe('foo');

// Let's check that change on existing value
Expand Down
43 changes: 17 additions & 26 deletions examples/MockReactiveForms/test.spec.ts
Expand Up @@ -8,7 +8,6 @@ import {
ReactiveFormsModule,
} from '@angular/forms';
import {
isMockControlValueAccessor,
MockBuilder,
MockInstance,
MockRender,
Expand Down Expand Up @@ -41,45 +40,37 @@ class TestedComponent {
}

describe('MockReactiveForms', () => {
// That is our spy on writeValue calls.
// With auto spy this code is not needed.
const writeValue =
typeof jest === 'undefined'
? jasmine.createSpy('writeValue')
: jest.fn();
// in case of jest
// const writeValue = jest.fn();

// Because of early calls of writeValue, we need to install
// the spy in the ctor call.
beforeEach(() =>
MockInstance(DependencyComponent, () => ({
writeValue,
})),
);

beforeEach(() => {
return MockBuilder(TestedComponent)
.mock(DependencyComponent)
.keep(ReactiveFormsModule);
});

it('sends the correct value to the mock form component', () => {
// That is our spy on writeValue calls.
// With auto spy this code is not needed.
const writeValue =
typeof jest === 'undefined'
? jasmine.createSpy('writeValue')
: jest.fn();
// in case of jest
// const writeValue = jest.fn();

// Because of early calls of writeValue, we need to install
// the spy via MockInstance before the render.
MockInstance(DependencyComponent, 'writeValue', writeValue);

const fixture = MockRender(TestedComponent);
const component = fixture.point.componentInstance;

// Let's find the mock form component.
const mockControl = ngMocks.find(DependencyComponent)
.componentInstance;

// During initialization it should be called
// with null.
expect(writeValue).toHaveBeenCalledWith(null);

// Let's simulate its change, like a user does it.
if (isMockControlValueAccessor(mockControl)) {
mockControl.__simulateChange('foo');
}
// Let's find the form control element
// and simulate its change, like a user does it.
const mockControlEl = ngMocks.find(DependencyComponent);
ngMocks.change(mockControlEl, 'foo');
expect(component.formControl.value).toBe('foo');

// Let's check that change on existing formControl
Expand Down

0 comments on commit 753b975

Please sign in to comment.