Skip to content

Commit

Permalink
fix: adding NG_VALUE_ACCESSOR only when necessary
Browse files Browse the repository at this point in the history
closes #145
  • Loading branch information
satanTime committed Jun 23, 2020
1 parent e536bb6 commit 7f54464
Show file tree
Hide file tree
Showing 8 changed files with 238 additions and 22 deletions.
10 changes: 9 additions & 1 deletion examples/MockReactiveForms/dependency.component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { Component } from '@angular/core';
import { Component, forwardRef } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';

@Component({
providers: [
{
multi: true,
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DependencyComponent),
},
],
selector: 'dependency-component-selector',
template: `dependency`,
})
Expand Down
16 changes: 13 additions & 3 deletions lib/common/Mock.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { EventEmitter } from '@angular/core';
import { ControlValueAccessor } from '@angular/forms';
import { AbstractControl, ControlValueAccessor, ValidationErrors, Validator } from '@angular/forms';

import { mockServiceHelper } from '../mock-service';

Expand Down Expand Up @@ -54,7 +54,7 @@ export class Mock {
}
}

export class MockControlValueAccessor extends Mock implements ControlValueAccessor {
export class MockControlValueAccessor extends Mock implements ControlValueAccessor, Validator {
get __ngMocksMockControlValueAccessor(): boolean {
return true;
}
Expand All @@ -63,6 +63,8 @@ export class MockControlValueAccessor extends Mock implements ControlValueAccess

__simulateTouch = () => {}; // tslint:disable-line:variable-name

__simulateValidatorChange = () => {}; // tslint:disable-line:variable-name

registerOnChange(fn: (value: any) => void): void {
this.__simulateChange = fn;
}
Expand All @@ -71,5 +73,13 @@ export class MockControlValueAccessor extends Mock implements ControlValueAccess
this.__simulateTouch = fn;
}

writeValue = (value: any) => {};
registerOnValidatorChange(fn: () => void): void {
this.__simulateValidatorChange = fn;
}

setDisabledState = (isDisabled: boolean): void => {};

validate = (control: AbstractControl): ValidationErrors | null => null;

writeValue = (obj: any) => {};
}
30 changes: 22 additions & 8 deletions lib/mock-component/mock-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import {
ViewContainerRef,
} from '@angular/core';
import { getTestBed } from '@angular/core/testing';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms';

import { AbstractType, getMockedNgDefOf, MockControlValueAccessor, MockOf, Type } from '../common';
import { AbstractType, flatten, getMockedNgDefOf, MockControlValueAccessor, MockOf, Type } from '../common';
import { decorateInputs, decorateOutputs, decorateQueries } from '../common/decorate';
import { ngMocksUniverse } from '../common/ng-mocks-universe';
import { directiveResolver } from '../common/reflect';
Expand Down Expand Up @@ -63,7 +63,7 @@ export function MockComponent<TComponent>(
throw new Error('ng-mocks is not in JIT mode and cannot resolve declarations');
}
}
const { exportAs, inputs, outputs, queries, selector } = meta;
const { exportAs, inputs, outputs, queries, selector, providers } = meta;

let template = `<ng-content></ng-content>`;
const viewChildRefs = new Map<string, string>();
Expand Down Expand Up @@ -101,11 +101,6 @@ export function MockComponent<TComponent>(
const options: Component = {
exportAs,
providers: [
{
multi: true,
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ComponentMock),
},
{
provide: component,
useExisting: forwardRef(() => ComponentMock),
Expand All @@ -115,6 +110,25 @@ export function MockComponent<TComponent>(
template,
};

for (const providerDef of flatten(providers)) {
const provider =
providerDef && typeof providerDef === 'object' && providerDef.provide ? providerDef.provide : providerDef;
if (options.providers && provider === NG_VALUE_ACCESSOR) {
options.providers.push({
multi: true,
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ComponentMock),
});
}
if (options.providers && provider === NG_VALIDATORS) {
options.providers.push({
multi: true,
provide: NG_VALIDATORS,
useExisting: forwardRef(() => ComponentMock),
});
}
}

const config = ngMocksUniverse.config.get(component);

@MockOf(component, outputs)
Expand Down
10 changes: 9 additions & 1 deletion lib/mock-component/test-components/empty-component.component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { Component } from '@angular/core';
import { Component, forwardRef } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';

@Component({
providers: [
{
multi: true,
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => EmptyComponent),
},
],
selector: 'empty-component',
template: 'some template',
})
Expand Down
30 changes: 22 additions & 8 deletions lib/mock-directive/mock-directive.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { core } from '@angular/compiler';
import { Directive, ElementRef, forwardRef, OnInit, Optional, TemplateRef, ViewContainerRef } from '@angular/core';
import { getTestBed } from '@angular/core/testing';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms';

import { AbstractType, getMockedNgDefOf, MockControlValueAccessor, MockOf, Type } from '../common';
import { AbstractType, flatten, getMockedNgDefOf, MockControlValueAccessor, MockOf, Type } from '../common';
import { decorateInputs, decorateOutputs, decorateQueries } from '../common/decorate';
import { ngMocksUniverse } from '../common/ng-mocks-universe';
import { directiveResolver } from '../common/reflect';
Expand Down Expand Up @@ -54,16 +54,11 @@ export function MockDirective<TDirective>(directive: Type<TDirective>): Type<Moc
throw new Error('ng-mocks is not in JIT mode and cannot resolve declarations');
}
}
const { selector, exportAs, inputs, outputs, queries } = meta;
const { selector, exportAs, inputs, outputs, queries, providers } = meta;

const options: Directive = {
exportAs,
providers: [
{
multi: true,
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DirectiveMock),
},
{
provide: directive,
useExisting: forwardRef(() => DirectiveMock),
Expand All @@ -72,6 +67,25 @@ export function MockDirective<TDirective>(directive: Type<TDirective>): Type<Moc
selector,
};

for (const providerDef of flatten(providers)) {
const provider =
providerDef && typeof providerDef === 'object' && providerDef.provide ? providerDef.provide : providerDef;
if (options.providers && provider === NG_VALUE_ACCESSOR) {
options.providers.push({
multi: true,
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DirectiveMock),
});
}
if (options.providers && provider === NG_VALIDATORS) {
options.providers.push({
multi: true,
provide: NG_VALIDATORS,
useExisting: forwardRef(() => DirectiveMock),
});
}
}

const config = ngMocksUniverse.config.get(directive);

@MockOf(directive, outputs)
Expand Down
82 changes: 82 additions & 0 deletions tests/issue-145/components.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { Component } from '@angular/core';
import { NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms';
import { MockComponent } from 'ng-mocks';
import { directiveResolver } from 'ng-mocks/dist/lib/common/reflect';

@Component({
selector: 'component',
template: '',
})
export class ComponentDefault {}

@Component({
providers: [
{
multi: true,
provide: NG_VALUE_ACCESSOR,
useExisting: ComponentValueAccessor,
},
],
selector: 'component',
template: '',
})
export class ComponentValueAccessor {}

@Component({
providers: [
{
multi: true,
provide: NG_VALIDATORS,
useExisting: ComponentValidator,
},
],
selector: 'component',
template: '',
})
export class ComponentValidator {}

// providers should be added to components only in case if they were specified in the original component.
describe('issue-145', () => {
it('ComponentDefault', () => {
const mock = MockComponent(ComponentDefault);
const { providers } = directiveResolver.resolve(mock);
expect(providers).toEqual([
{
provide: ComponentDefault,
useExisting: jasmine.anything(),
},
]);
});

it('ComponentValueAccessor', () => {
const mock = MockComponent(ComponentValueAccessor);
const { providers } = directiveResolver.resolve(mock);
expect(providers).toEqual([
{
provide: ComponentValueAccessor,
useExisting: jasmine.anything(),
},
{
multi: true,
provide: NG_VALUE_ACCESSOR,
useExisting: jasmine.anything(),
},
]);
});

it('ComponentValidator', () => {
const mock = MockComponent(ComponentValidator);
const { providers } = directiveResolver.resolve(mock);
expect(providers).toEqual([
{
provide: ComponentValidator,
useExisting: jasmine.anything(),
},
{
multi: true,
provide: NG_VALIDATORS,
useExisting: jasmine.anything(),
},
]);
});
});
79 changes: 79 additions & 0 deletions tests/issue-145/directives.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Directive } from '@angular/core';
import { NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms';
import { MockDirective } from 'ng-mocks';
import { directiveResolver } from 'ng-mocks/dist/lib/common/reflect';

@Directive({
selector: 'directive',
})
export class DirectiveDefault {}

@Directive({
providers: [
{
multi: true,
provide: NG_VALUE_ACCESSOR,
useExisting: DirectiveValueAccessor,
},
],
selector: 'directive',
})
export class DirectiveValueAccessor {}

@Directive({
providers: [
{
multi: true,
provide: NG_VALIDATORS,
useExisting: DirectiveValidator,
},
],
selector: 'directive',
})
export class DirectiveValidator {}

// providers should be added to directives only in case if they were specified in the original directive.
describe('issue-145', () => {
it('DirectiveDefault', () => {
const mock = MockDirective(DirectiveDefault);
const { providers } = directiveResolver.resolve(mock);
expect(providers).toEqual([
{
provide: DirectiveDefault,
useExisting: jasmine.anything(),
},
]);
});

it('DirectiveValueAccessor', () => {
const mock = MockDirective(DirectiveValueAccessor);
const { providers } = directiveResolver.resolve(mock);
expect(providers).toEqual([
{
provide: DirectiveValueAccessor,
useExisting: jasmine.anything(),
},
{
multi: true,
provide: NG_VALUE_ACCESSOR,
useExisting: jasmine.anything(),
},
]);
});

it('DirectiveValidator', () => {
const mock = MockDirective(DirectiveValidator);
const { providers } = directiveResolver.resolve(mock);
expect(providers).toEqual([
{
provide: DirectiveValidator,
useExisting: jasmine.anything(),
},
{
multi: true,
provide: NG_VALIDATORS,
useExisting: jasmine.anything(),
},
]);
});
});
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"strictNullChecks": true,
"baseUrl": ".",
"paths": {
"ng-mocks": ["index"]
"ng-mocks": ["index"],
"ng-mocks/dist/*": ["./*"]
},
"skipLibCheck": true
},
Expand Down

0 comments on commit 7f54464

Please sign in to comment.