diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b7ce24275..9479f89758 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. + +## [10.0.1](https://github.com/ike18t/ng-mocks/compare/v10.0.0...v10.0.1) (2020-07-12) + + +### Bug Fixes + +* building es5 only that supports es2015 ([d11ed5a](https://github.com/ike18t/ng-mocks/commit/d11ed5a)), closes [#158](https://github.com/ike18t/ng-mocks/issues/158) +* respect mocks in tokens with useValue ([ccccfc6](https://github.com/ike18t/ng-mocks/commit/ccccfc6)), closes [#151](https://github.com/ike18t/ng-mocks/issues/151) +* smart injection of NG_VALUE_ACCESSOR ([ad37bf0](https://github.com/ike18t/ng-mocks/commit/ad37bf0)), closes [#157](https://github.com/ike18t/ng-mocks/issues/157) + + + # [10.0.0](https://github.com/ike18t/ng-mocks/compare/v9.6.4...v10.0.0) (2020-07-05) diff --git a/README.md b/README.md index f053eafed7..264da0c1c2 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Tested on: - Angular 7 (Jasmine, Jest, es5, es2015) - Angular 8 (Jasmine, Jest, es5, es2015) - Angular 9 (Jasmine, Jest, Ivy, es5, es2015) -- Angular 10 (Jasmine, Jest, Ivy, es2015), es5 isn't supported, use `target: es2015` in `tsconfig.spec.json` +- Angular 10 (Jasmine, Jest, Ivy, es5, es2015) ## Why use this? diff --git a/e2e/a10es2015ivy/src/app/app.component.html b/e2e/a10es2015ivy/src/app/app.component.html deleted file mode 100644 index d6f5903e18..0000000000 --- a/e2e/a10es2015ivy/src/app/app.component.html +++ /dev/null @@ -1 +0,0 @@ -ng-mocks diff --git a/e2e/a10es2015ivy/src/app/app.component.scss b/e2e/a10es2015ivy/src/app/app.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/e2e/a10es2015ivy/src/app/app.component.ts b/e2e/a10es2015ivy/src/app/app.component.ts index 6cdc4580e9..9b6e0b1392 100644 --- a/e2e/a10es2015ivy/src/app/app.component.ts +++ b/e2e/a10es2015ivy/src/app/app.component.ts @@ -2,8 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'], + template: 'ng-mocks', changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppComponent {} diff --git a/e2e/a10es2015noivy/src/app/app.component.html b/e2e/a10es2015noivy/src/app/app.component.html deleted file mode 100644 index d6f5903e18..0000000000 --- a/e2e/a10es2015noivy/src/app/app.component.html +++ /dev/null @@ -1 +0,0 @@ -ng-mocks diff --git a/e2e/a10es2015noivy/src/app/app.component.scss b/e2e/a10es2015noivy/src/app/app.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/e2e/a10es2015noivy/src/app/app.component.ts b/e2e/a10es2015noivy/src/app/app.component.ts index 6cdc4580e9..9b6e0b1392 100644 --- a/e2e/a10es2015noivy/src/app/app.component.ts +++ b/e2e/a10es2015noivy/src/app/app.component.ts @@ -2,8 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'], + template: 'ng-mocks', changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppComponent {} diff --git a/e2e/a10es5ivy/src/app/app.component.html b/e2e/a10es5ivy/src/app/app.component.html deleted file mode 100644 index d6f5903e18..0000000000 --- a/e2e/a10es5ivy/src/app/app.component.html +++ /dev/null @@ -1 +0,0 @@ -ng-mocks diff --git a/e2e/a10es5ivy/src/app/app.component.scss b/e2e/a10es5ivy/src/app/app.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/e2e/a10es5ivy/src/app/app.component.ts b/e2e/a10es5ivy/src/app/app.component.ts index 6cdc4580e9..9b6e0b1392 100644 --- a/e2e/a10es5ivy/src/app/app.component.ts +++ b/e2e/a10es5ivy/src/app/app.component.ts @@ -2,8 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'], + template: 'ng-mocks', changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppComponent {} diff --git a/e2e/a10es5noivy/src/app/app.component.html b/e2e/a10es5noivy/src/app/app.component.html deleted file mode 100644 index d6f5903e18..0000000000 --- a/e2e/a10es5noivy/src/app/app.component.html +++ /dev/null @@ -1 +0,0 @@ -ng-mocks diff --git a/e2e/a10es5noivy/src/app/app.component.scss b/e2e/a10es5noivy/src/app/app.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/e2e/a10es5noivy/src/app/app.component.ts b/e2e/a10es5noivy/src/app/app.component.ts index 6cdc4580e9..9b6e0b1392 100644 --- a/e2e/a10es5noivy/src/app/app.component.ts +++ b/e2e/a10es5noivy/src/app/app.component.ts @@ -2,8 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'], + template: 'ng-mocks', changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppComponent {} diff --git a/e2e/a5es2015/src/app/app.component.css b/e2e/a5es2015/src/app/app.component.css deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/e2e/a5es2015/src/app/app.component.html b/e2e/a5es2015/src/app/app.component.html deleted file mode 100644 index d6f5903e18..0000000000 --- a/e2e/a5es2015/src/app/app.component.html +++ /dev/null @@ -1 +0,0 @@ -ng-mocks diff --git a/e2e/a5es2015/src/app/app.component.ts b/e2e/a5es2015/src/app/app.component.ts index 5c65edda89..9b6e0b1392 100644 --- a/e2e/a5es2015/src/app/app.component.ts +++ b/e2e/a5es2015/src/app/app.component.ts @@ -1,10 +1,8 @@ -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.css'], + template: 'ng-mocks', + changeDetection: ChangeDetectionStrategy.OnPush, }) -export class AppComponent { - title = 'app'; -} +export class AppComponent {} diff --git a/e2e/a5es5/src/app/app.component.css b/e2e/a5es5/src/app/app.component.css deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/e2e/a5es5/src/app/app.component.html b/e2e/a5es5/src/app/app.component.html deleted file mode 100644 index d6f5903e18..0000000000 --- a/e2e/a5es5/src/app/app.component.html +++ /dev/null @@ -1 +0,0 @@ -ng-mocks diff --git a/e2e/a5es5/src/app/app.component.ts b/e2e/a5es5/src/app/app.component.ts index 5c65edda89..9b6e0b1392 100644 --- a/e2e/a5es5/src/app/app.component.ts +++ b/e2e/a5es5/src/app/app.component.ts @@ -1,10 +1,8 @@ -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.css'], + template: 'ng-mocks', + changeDetection: ChangeDetectionStrategy.OnPush, }) -export class AppComponent { - title = 'app'; -} +export class AppComponent {} diff --git a/e2e/a6es2015/src/app/app.component.html b/e2e/a6es2015/src/app/app.component.html deleted file mode 100644 index d6f5903e18..0000000000 --- a/e2e/a6es2015/src/app/app.component.html +++ /dev/null @@ -1 +0,0 @@ -ng-mocks diff --git a/e2e/a6es2015/src/app/app.component.scss b/e2e/a6es2015/src/app/app.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/e2e/a6es2015/src/app/app.component.ts b/e2e/a6es2015/src/app/app.component.ts index 3e1b37a943..b268ce6c53 100644 --- a/e2e/a6es2015/src/app/app.component.ts +++ b/e2e/a6es2015/src/app/app.component.ts @@ -2,8 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'], + template: 'ng-mocks', changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppComponent {} diff --git a/e2e/a6es5/src/app/app.component.html b/e2e/a6es5/src/app/app.component.html deleted file mode 100644 index d6f5903e18..0000000000 --- a/e2e/a6es5/src/app/app.component.html +++ /dev/null @@ -1 +0,0 @@ -ng-mocks diff --git a/e2e/a6es5/src/app/app.component.scss b/e2e/a6es5/src/app/app.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/e2e/a6es5/src/app/app.component.ts b/e2e/a6es5/src/app/app.component.ts index 3e1b37a943..b268ce6c53 100644 --- a/e2e/a6es5/src/app/app.component.ts +++ b/e2e/a6es5/src/app/app.component.ts @@ -2,8 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'], + template: 'ng-mocks', changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppComponent {} diff --git a/e2e/a7es2015/src/app/app.component.html b/e2e/a7es2015/src/app/app.component.html deleted file mode 100644 index d6f5903e18..0000000000 --- a/e2e/a7es2015/src/app/app.component.html +++ /dev/null @@ -1 +0,0 @@ -ng-mocks diff --git a/e2e/a7es2015/src/app/app.component.scss b/e2e/a7es2015/src/app/app.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/e2e/a7es2015/src/app/app.component.ts b/e2e/a7es2015/src/app/app.component.ts index 3e1b37a943..b268ce6c53 100644 --- a/e2e/a7es2015/src/app/app.component.ts +++ b/e2e/a7es2015/src/app/app.component.ts @@ -2,8 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'], + template: 'ng-mocks', changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppComponent {} diff --git a/e2e/a7es5/src/app/app.component.html b/e2e/a7es5/src/app/app.component.html deleted file mode 100644 index d6f5903e18..0000000000 --- a/e2e/a7es5/src/app/app.component.html +++ /dev/null @@ -1 +0,0 @@ -ng-mocks diff --git a/e2e/a7es5/src/app/app.component.scss b/e2e/a7es5/src/app/app.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/e2e/a7es5/src/app/app.component.ts b/e2e/a7es5/src/app/app.component.ts index 3e1b37a943..b268ce6c53 100644 --- a/e2e/a7es5/src/app/app.component.ts +++ b/e2e/a7es5/src/app/app.component.ts @@ -2,8 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'], + template: 'ng-mocks', changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppComponent {} diff --git a/e2e/a8es2015/src/app/app.component.html b/e2e/a8es2015/src/app/app.component.html deleted file mode 100644 index d6f5903e18..0000000000 --- a/e2e/a8es2015/src/app/app.component.html +++ /dev/null @@ -1 +0,0 @@ -ng-mocks diff --git a/e2e/a8es2015/src/app/app.component.scss b/e2e/a8es2015/src/app/app.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/e2e/a8es2015/src/app/app.component.ts b/e2e/a8es2015/src/app/app.component.ts index 3e1b37a943..b268ce6c53 100644 --- a/e2e/a8es2015/src/app/app.component.ts +++ b/e2e/a8es2015/src/app/app.component.ts @@ -2,8 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'], + template: 'ng-mocks', changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppComponent {} diff --git a/e2e/a8es5/src/app/app.component.html b/e2e/a8es5/src/app/app.component.html deleted file mode 100644 index d6f5903e18..0000000000 --- a/e2e/a8es5/src/app/app.component.html +++ /dev/null @@ -1 +0,0 @@ -ng-mocks diff --git a/e2e/a8es5/src/app/app.component.scss b/e2e/a8es5/src/app/app.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/e2e/a8es5/src/app/app.component.ts b/e2e/a8es5/src/app/app.component.ts index 3e1b37a943..b268ce6c53 100644 --- a/e2e/a8es5/src/app/app.component.ts +++ b/e2e/a8es5/src/app/app.component.ts @@ -2,8 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'], + template: 'ng-mocks', changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppComponent {} diff --git a/e2e/a9es2015ivy/src/app/app.component.html b/e2e/a9es2015ivy/src/app/app.component.html deleted file mode 100644 index d6f5903e18..0000000000 --- a/e2e/a9es2015ivy/src/app/app.component.html +++ /dev/null @@ -1 +0,0 @@ -ng-mocks diff --git a/e2e/a9es2015ivy/src/app/app.component.scss b/e2e/a9es2015ivy/src/app/app.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/e2e/a9es2015ivy/src/app/app.component.ts b/e2e/a9es2015ivy/src/app/app.component.ts index 3e1b37a943..b268ce6c53 100644 --- a/e2e/a9es2015ivy/src/app/app.component.ts +++ b/e2e/a9es2015ivy/src/app/app.component.ts @@ -2,8 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'], + template: 'ng-mocks', changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppComponent {} diff --git a/e2e/a9es2015noivy/src/app/app.component.html b/e2e/a9es2015noivy/src/app/app.component.html deleted file mode 100644 index d6f5903e18..0000000000 --- a/e2e/a9es2015noivy/src/app/app.component.html +++ /dev/null @@ -1 +0,0 @@ -ng-mocks diff --git a/e2e/a9es2015noivy/src/app/app.component.scss b/e2e/a9es2015noivy/src/app/app.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/e2e/a9es2015noivy/src/app/app.component.ts b/e2e/a9es2015noivy/src/app/app.component.ts index 3e1b37a943..b268ce6c53 100644 --- a/e2e/a9es2015noivy/src/app/app.component.ts +++ b/e2e/a9es2015noivy/src/app/app.component.ts @@ -2,8 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'], + template: 'ng-mocks', changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppComponent {} diff --git a/e2e/a9es5ivy/src/app/app.component.html b/e2e/a9es5ivy/src/app/app.component.html deleted file mode 100644 index d6f5903e18..0000000000 --- a/e2e/a9es5ivy/src/app/app.component.html +++ /dev/null @@ -1 +0,0 @@ -ng-mocks diff --git a/e2e/a9es5ivy/src/app/app.component.scss b/e2e/a9es5ivy/src/app/app.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/e2e/a9es5ivy/src/app/app.component.ts b/e2e/a9es5ivy/src/app/app.component.ts index 3e1b37a943..b268ce6c53 100644 --- a/e2e/a9es5ivy/src/app/app.component.ts +++ b/e2e/a9es5ivy/src/app/app.component.ts @@ -2,8 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'], + template: 'ng-mocks', changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppComponent {} diff --git a/e2e/a9es5noivy/src/app/app.component.html b/e2e/a9es5noivy/src/app/app.component.html deleted file mode 100644 index d6f5903e18..0000000000 --- a/e2e/a9es5noivy/src/app/app.component.html +++ /dev/null @@ -1 +0,0 @@ -ng-mocks diff --git a/e2e/a9es5noivy/src/app/app.component.scss b/e2e/a9es5noivy/src/app/app.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/e2e/a9es5noivy/src/app/app.component.ts b/e2e/a9es5noivy/src/app/app.component.ts index 3e1b37a943..b268ce6c53 100644 --- a/e2e/a9es5noivy/src/app/app.component.ts +++ b/e2e/a9es5noivy/src/app/app.component.ts @@ -2,8 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'], + template: 'ng-mocks', changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppComponent {} diff --git a/lib/mock-builder/mock-builder.ts b/lib/mock-builder/mock-builder.ts index a1b2867c79..ff75720a84 100644 --- a/lib/mock-builder/mock-builder.ts +++ b/lib/mock-builder/mock-builder.ts @@ -107,7 +107,6 @@ export class MockBuilderPromise implements PromiseLike { } public build(): NgModule { - // tslint:disable-line:cyclomatic-complexity const backup = { builder: ngMocksUniverse.builder, cache: ngMocksUniverse.cache, diff --git a/lib/mock-component/mock-component.ts b/lib/mock-component/mock-component.ts index 89dfd76445..135c9942eb 100644 --- a/lib/mock-component/mock-component.ts +++ b/lib/mock-component/mock-component.ts @@ -4,13 +4,15 @@ import { ChangeDetectorRef, Component, forwardRef, + Optional, Query, + Self, TemplateRef, ViewChild, ViewContainerRef, } from '@angular/core'; import { getTestBed } from '@angular/core/testing'; -import { NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { NgControl, NG_VALIDATORS } from '@angular/forms'; import { AbstractType, flatten, getMockedNgDefOf, MockControlValueAccessor, MockOf, Type } from '../common'; import { decorateInputs, decorateOutputs, decorateQueries } from '../common/decorate'; @@ -110,20 +112,13 @@ export function MockComponent( template, }; - for (const providerDef of flatten(providers)) { - const provider = + for (const providerDef of flatten(providers || [])) { + const provide = providerDef && typeof providerDef === 'object' && providerDef.provide ? providerDef.provide : providerDef; - if (options.providers && provider === NG_VALUE_ACCESSOR) { + if (options.providers && provide === NG_VALIDATORS) { 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, + provide, useExisting: forwardRef(() => ComponentMock), }); } @@ -134,9 +129,13 @@ export function MockComponent( @Component(options) @MockOf(component, outputs) class ComponentMock extends MockControlValueAccessor implements AfterContentInit { - constructor(changeDetector: ChangeDetectorRef) { + constructor(changeDetector: ChangeDetectorRef, @Self() @Optional() ngControl?: NgControl) { super(); + if (ngControl && !ngControl.valueAccessor) { + ngControl.valueAccessor = this; + } + // Providing method to hide any @ContentChild based on its selector. (this as any).__hide = (contentChildSelector: string) => { const key = viewChildRefs.get(contentChildSelector); diff --git a/lib/mock-directive/mock-directive.ts b/lib/mock-directive/mock-directive.ts index cc0fe59433..b0e8659313 100644 --- a/lib/mock-directive/mock-directive.ts +++ b/lib/mock-directive/mock-directive.ts @@ -1,7 +1,16 @@ import { core } from '@angular/compiler'; -import { Directive, ElementRef, forwardRef, OnInit, Optional, TemplateRef, ViewContainerRef } from '@angular/core'; +import { + Directive, + ElementRef, + forwardRef, + OnInit, + Optional, + Self, + TemplateRef, + ViewContainerRef, +} from '@angular/core'; import { getTestBed } from '@angular/core/testing'; -import { NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { NgControl, NG_VALIDATORS } from '@angular/forms'; import { AbstractType, flatten, getMockedNgDefOf, MockControlValueAccessor, MockOf, Type } from '../common'; import { decorateInputs, decorateOutputs, decorateQueries } from '../common/decorate'; @@ -67,20 +76,13 @@ export function MockDirective(directive: Type): Type DirectiveMock), - }); - } - if (options.providers && provider === NG_VALIDATORS) { - options.providers.push({ - multi: true, - provide: NG_VALIDATORS, + provide, useExisting: forwardRef(() => DirectiveMock), }); } @@ -94,10 +96,15 @@ export function MockDirective(directive: Type): Type, - @Optional() viewContainer?: ViewContainerRef + @Optional() viewContainer?: ViewContainerRef, + @Self() @Optional() ngControl?: NgControl ) { super(); + if (ngControl && !ngControl.valueAccessor) { + ngControl.valueAccessor = this; + } + // Basically any directive on ng-template is treated as structural, even it doesn't control render process. // In our case we don't if we should render it or not and due to this we do nothing. (this as any).__element = element; diff --git a/lib/mock-module/mock-module.spec.ts b/lib/mock-module/mock-module.spec.ts index e27997362c..ef4a6ab890 100644 --- a/lib/mock-module/mock-module.spec.ts +++ b/lib/mock-module/mock-module.spec.ts @@ -4,8 +4,8 @@ import { APP_INITIALIZER, ApplicationModule, Component, InjectionToken, NgModule import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { BrowserModule, By } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { ngMocksUniverse } from 'ng-mocks/dist/lib/common/ng-mocks-universe'; +import { ngMocksUniverse } from '../common/ng-mocks-universe'; import { ngModuleResolver } from '../common/reflect'; import { MockComponent } from '../mock-component'; import { MockModule, MockProvider } from '../mock-module'; diff --git a/lib/mock-module/mock-module.ts b/lib/mock-module/mock-module.ts index cc89f9450f..e906f8c038 100644 --- a/lib/mock-module/mock-module.ts +++ b/lib/mock-module/mock-module.ts @@ -15,11 +15,11 @@ import { Type, } from '../common'; import { ngMocksUniverse } from '../common/ng-mocks-universe'; -import { ngModuleResolver } from '../common/reflect'; +import { jitReflector, ngModuleResolver } from '../common/reflect'; import { MockComponent } from '../mock-component'; import { MockDirective } from '../mock-directive'; import { MockPipe } from '../mock-pipe'; -import { MockService } from '../mock-service'; +import { MockService, mockServiceHelper } from '../mock-service'; export type MockedModule = T & Mock & {}; @@ -62,7 +62,6 @@ export function MockProvider(provider: any): Provider | undefined { export function MockModule(module: Type): Type; export function MockModule(module: NgModuleWithProviders): NgModuleWithProviders; export function MockModule(module: any): any { - // tslint:disable-line:cyclomatic-complexity let ngModule: Type; let ngModuleProviders: Provider[] | undefined; let mockModule: typeof ngModule | undefined; @@ -130,11 +129,31 @@ export function MockModule(module: any): any { if (mockModuleDef) { const parent = ngMocksUniverse.flags.has('skipMock') ? ngModule : Mock; - @NgModule(mockModuleDef) - @MockOf(ngModule) - class ModuleMock extends parent {} + // first we try to eval es2015 style and if it fails to use es5 transpilation in the catch block. + (window as any).ngMocksParent = parent; + try { + // tslint:disable-next-line:no-eval + eval(` + class mockModule extends window.ngMocksParent { + } + window.ngMocksResult = mockModule + `); + mockModule = (window as any).ngMocksResult; + } catch (e) { + class ClassEs5 extends parent {} + mockModule = ClassEs5; + } + (window as any).ngMocksParent = undefined; + + // the next step is to respect constructor parameters as the parent class. + if (mockModule) { + (mockModule as any).parameters = jitReflector.parameters(parent); + } + + // the last thing is to apply decorators. + NgModule(mockModuleDef)(mockModule as any); + MockOf(ngModule)(mockModule as any); - mockModule = ModuleMock; if (ngMocksUniverse.flags.has('cacheModule')) { ngMocksUniverse.cache.set(ngModule, mockModule); } @@ -161,7 +180,6 @@ export function MockModule(module: any): any { const NEVER_MOCK: Array> = [CommonModule, ApplicationModule]; -// tslint:disable-next-line:cyclomatic-complexity function MockNgModuleDef(ngModuleDef: NgModule, ngModule?: Type): [boolean, NgModule] { let changed = !ngMocksUniverse.flags.has('skipMock'); const mockedModuleDef: NgModule = {}; @@ -206,6 +224,17 @@ function MockNgModuleDef(ngModuleDef: NgModule, ngModule?: Type): [boolean, if (!mockedDef) { mockedDef = MockProvider(def); } + // if provider is a value, we need to go through the value and to replace all mocked instances. + if (provider !== def && mockedDef && mockedDef.useValue) { + const useValue = mockServiceHelper.replaceWithMocks(mockedDef.useValue); + mockedDef = + useValue === mockedDef.useValue + ? mockedDef + : { + ...mockedDef, + useValue, + }; + } if (!isNgInjectionToken(provider) || def !== mockedDef) { resolutions.set(provider, mockedDef); diff --git a/lib/mock-service/mock-service.spec.ts b/lib/mock-service/mock-service.spec.ts index 4285fe264d..158e50dc6d 100644 --- a/lib/mock-service/mock-service.spec.ts +++ b/lib/mock-service/mock-service.spec.ts @@ -2,9 +2,10 @@ import { InjectionToken } from '@angular/core'; +import { ngMocksUniverse } from '../common/ng-mocks-universe'; import { ngMocks } from '../mock-helper'; -import { MockService } from './mock-service'; +import { MockService, mockServiceHelper } from './mock-service'; class DeepParentClass { public deepParentMethodName = 'deepParentMethod'; @@ -276,4 +277,25 @@ describe('MockService', () => { expect(test.nameRead).toBe('fake4'); expect(test.nameSet).toBe('fake5'); }); + + it('respects original class in replaceWithMocks', () => { + class A {} + + class B {} + + class Test { + private member = A; + + public getMember() { + return this.member; + } + } + + ngMocksUniverse.cache.set(A, B); + + const instance = new Test(); + const updated = mockServiceHelper.replaceWithMocks(instance); + expect(updated).toEqual(jasmine.any(Test)); + expect(updated.getMember()).toBe(B); + }); }); diff --git a/lib/mock-service/mock-service.ts b/lib/mock-service/mock-service.ts index bbc4debd1b..e0ebec0e5d 100644 --- a/lib/mock-service/mock-service.ts +++ b/lib/mock-service/mock-service.ts @@ -1,3 +1,5 @@ +import { ngMocksUniverse } from '../common/ng-mocks-universe'; + export type MockedFunction = () => any; const isFunc = (value: any): boolean => { @@ -138,7 +140,6 @@ const mockServiceHelperPrototype = { } }, - // tslint:disable-next-line:cyclomatic-complexity mock: (instance: any, name: string, accessType?: 'get' | 'set'): T => { const def = Object.getOwnPropertyDescriptor(instance, name); if (def && def[accessType || 'value']) { @@ -188,6 +189,35 @@ const mockServiceHelperPrototype = { Object.defineProperty(instance, name, mockDef); return mock; }, + + replaceWithMocks(value: any): any { + if (ngMocksUniverse.cache.has(value)) { + return ngMocksUniverse.cache.get(value); + } + if (typeof value !== 'object') { + return value; + } + let mocked: any; + let updated = false; + if (Array.isArray(value)) { + mocked = []; + for (let key = 0; key < value.length; key += 1) { + mocked[key] = mockServiceHelper.replaceWithMocks(value[key]); + updated = updated || mocked[key] !== value[key]; + } + } else if (value) { + mocked = {}; + for (const key of Object.keys(value)) { + mocked[key] = mockServiceHelper.replaceWithMocks(value[key]); + updated = updated || mocked[key] !== value[key]; + } + } + if (updated) { + Object.setPrototypeOf(mocked, Object.getPrototypeOf(value)); + return mocked; + } + return value; + }, }; // We need a single pointer to the object among all environments. @@ -209,6 +239,7 @@ export const mockServiceHelper: { mock(instance: any, name: string, style?: 'get' | 'set'): T; mockFunction(mockName: string): MockedFunction; registerMockFunction(mockFunction: (mockName: string) => MockedFunction | undefined): void; + replaceWithMocks(value: any): any; } = ((window as any) || (global as any)).ngMocksMockServiceHelper; export function MockService(service?: boolean | number | string | null, mockNamePrefix?: string): undefined; diff --git a/package-lock.json b/package-lock.json index aa3eed58e2..341b3350ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "ng-mocks", - "version": "10.0.0", + "version": "10.0.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 33c355cd7a..0ef478c2bc 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,8 @@ { "name": "ng-mocks", - "version": "10.0.0", + "version": "10.0.1", "description": "Functions for creating angular mocks", "main": "dist/index.js", - "module": "dist/es5/index.js", - "es5": "dist/es5/index.js", - "es2015": "dist/index.js", "types": "dist/index.d.ts", "files": [ "CHANGELOG", @@ -14,7 +11,7 @@ "README" ], "scripts": { - "build": "npm run clean && tsc && tsc -t es5 --outDir dist/es5", + "build": "npm run clean && tsc", "build:all": "npm run lint && npm run build && npm run test", "i:a": "npm run i:a5 & npm run i:a6 & npm run i:a7 & npm run i:a8 & npm run i:a9 & npm run i:a10 & wait", "i:a5": "npm run i:a5es5 && npm run i:a5es2015", @@ -138,7 +135,7 @@ "test:a9es5noivy": "cd e2e/a9es5noivy && npm run test", "test:a9es2015ivy": "cd e2e/a9es2015ivy && npm run test", "test:a9es2015noivy": "cd e2e/a9es2015noivy && npm run test", - "test:a10": "npm run test:a10es2015", + "test:a10": "npm run test:a10es5 && npm run test:a10es2015", "test:a10es5": "npm run test:a10es5ivy && npm run test:a10es5noivy", "test:a10es2015": "npm run test:a10es2015ivy && npm run test:a10es2015noivy", "test:a10es5ivy": "cd e2e/a10es5ivy && npm run test", diff --git a/tests/issue-145/components.spec.ts b/tests/issue-145/components.spec.ts index 18ad2de5bc..dad6f1a87e 100644 --- a/tests/issue-145/components.spec.ts +++ b/tests/issue-145/components.spec.ts @@ -48,20 +48,19 @@ describe('issue-145', () => { ]); }); - it('ComponentValueAccessor', () => { + // this test was changed due to issue 157: https://github.com/ike18t/ng-mocks/issues/157 + it('should skip NG_VALUE_ACCESSOR in mocked component 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(), - }, - ]); + expect(providers as any).not.toEqual( + jasmine.arrayContaining([ + { + multi: true, + provide: NG_VALUE_ACCESSOR, + useExisting: jasmine.anything(), + }, + ]) + ); }); it('ComponentValidator', () => { diff --git a/tests/issue-145/directives.spec.ts b/tests/issue-145/directives.spec.ts index 2c57c6483b..d6f4f37544 100644 --- a/tests/issue-145/directives.spec.ts +++ b/tests/issue-145/directives.spec.ts @@ -45,20 +45,19 @@ describe('issue-145', () => { ]); }); + // this test was changed due to issue 157: https://github.com/ike18t/ng-mocks/issues/157 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(), - }, - ]); + expect(providers as any).not.toEqual( + jasmine.arrayContaining([ + { + multi: true, + provide: NG_VALUE_ACCESSOR, + useExisting: jasmine.anything(), + }, + ]) + ); }); it('DirectiveValidator', () => { diff --git a/tests/issue-151/app/app-routing.module.ts b/tests/issue-151/app/app-routing.module.ts new file mode 100644 index 0000000000..3d5f652adf --- /dev/null +++ b/tests/issue-151/app/app-routing.module.ts @@ -0,0 +1,23 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { HelloComponent } from './hello.component'; +import { HelloModule } from './hello.module'; + +const routes: Routes = [ + { + path: '', + pathMatch: 'full', + redirectTo: '/hello', + }, + { + component: HelloComponent, + path: 'hello', + }, +]; + +@NgModule({ + exports: [HelloModule, RouterModule], + imports: [HelloModule, RouterModule.forRoot(routes)], +}) +export class AppRoutingModule {} diff --git a/tests/issue-151/app/app.component.ts b/tests/issue-151/app/app.component.ts new file mode 100644 index 0000000000..9cdf3dd558 --- /dev/null +++ b/tests/issue-151/app/app.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + template: '', +}) +export class AppComponent {} diff --git a/tests/issue-151/app/app.module.ts b/tests/issue-151/app/app.module.ts new file mode 100644 index 0000000000..668f48ef5f --- /dev/null +++ b/tests/issue-151/app/app.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { AppRoutingModule } from './app-routing.module'; +import { AppComponent } from './app.component'; + +@NgModule({ + bootstrap: [AppComponent], + declarations: [AppComponent], + imports: [BrowserModule, AppRoutingModule], +}) +export class AppModule {} diff --git a/tests/issue-151/app/hello.component.ts b/tests/issue-151/app/hello.component.ts new file mode 100644 index 0000000000..80fc2a8497 --- /dev/null +++ b/tests/issue-151/app/hello.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-hello', + template: 'hello', +}) +export class HelloComponent {} diff --git a/tests/issue-151/app/hello.module.ts b/tests/issue-151/app/hello.module.ts new file mode 100644 index 0000000000..57da57cfb5 --- /dev/null +++ b/tests/issue-151/app/hello.module.ts @@ -0,0 +1,11 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; + +import { HelloComponent } from './hello.component'; + +@NgModule({ + declarations: [HelloComponent], + exports: [HelloComponent], + imports: [CommonModule], +}) +export class HelloModule {} diff --git a/tests/issue-151/test.spec.ts b/tests/issue-151/test.spec.ts new file mode 100644 index 0000000000..8749a81be1 --- /dev/null +++ b/tests/issue-151/test.spec.ts @@ -0,0 +1,33 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Router, RouterModule } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import { MockBuilder, MockRender } from 'ng-mocks'; + +import { AppComponent } from './app/app.component'; +import { AppModule } from './app/app.module'; + +describe('issue-151', () => { + let fixture: ComponentFixture; + + describe('mock AppRoutingModule', () => { + beforeEach(() => + TestBed.configureTestingModule( + MockBuilder(AppComponent, AppModule).keep(RouterModule).keep(RouterTestingModule).build() + ) + ); + + beforeEach(async () => { + fixture = MockRender(AppComponent); + const router = TestBed.get(Router); + if (fixture.ngZone) { + fixture.ngZone.run(() => router.initialNavigation()); + } + await fixture.whenStable(); + }); + + it('should create the app', () => { + // asserting that app-hello is mocked (no content) and detected by router. + expect(fixture.nativeElement.innerHTML).toContain(''); + }); + }); +}); diff --git a/tests/issue-157/test.spec.ts b/tests/issue-157/test.spec.ts new file mode 100644 index 0000000000..3278ffbd11 --- /dev/null +++ b/tests/issue-157/test.spec.ts @@ -0,0 +1,334 @@ +import { Component, Directive, forwardRef, Optional, Self } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { + ControlValueAccessor, + FormControl, + FormGroup, + NgControl, + NG_VALUE_ACCESSOR, + ReactiveFormsModule, +} from '@angular/forms'; +import { MockBuilder, MockRender } from 'ng-mocks'; + +@Component({ + selector: 'actual-empty', + template: '', +}) +export class ActualEmptyComponent {} + +@Component({ + selector: 'actual-injection', + template: '', +}) +export class ActualInjectionComponent implements ControlValueAccessor { + protected value: any = undefined; + + constructor(@Self() @Optional() ngControl: NgControl) { + if (ngControl) { + ngControl.valueAccessor = this; + } + } + + public registerOnChange(fn: any): void { + this.change = fn; + } + + public registerOnTouched(fn: any): void { + this.touch = fn; + } + + public writeValue(obj: any): void { + this.value = obj; + } + + protected change: any = () => undefined; + protected touch: any = () => undefined; +} + +@Component({ + providers: [ + { + multi: true, + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ActualTokenComponent), + }, + ], + selector: 'actual-token', + template: '', +}) +export class ActualTokenComponent implements ControlValueAccessor { + protected value: any = undefined; + + public registerOnChange(fn: any): void { + this.change = fn; + } + + public registerOnTouched(fn: any): void { + this.touch = fn; + } + + public writeValue(obj: any): void { + this.value = obj; + } + + protected change: any = () => undefined; + protected touch: any = () => undefined; +} + +@Directive({ + providers: [ + { + multi: true, + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ActualTokenDirective), + }, + ], + selector: '[actualToken]', +}) +export class ActualTokenDirective implements ControlValueAccessor { + protected value: any = undefined; + + public registerOnChange(fn: any): void { + this.change = fn; + } + + public registerOnTouched(fn: any): void { + this.touch = fn; + } + + public writeValue(obj: any): void { + this.value = obj; + } + + protected change: any = () => undefined; + protected touch: any = () => undefined; +} + +describe('issue-157:real', () => { + beforeEach(() => + TestBed.configureTestingModule({ + declarations: [ActualEmptyComponent, ActualInjectionComponent, ActualTokenComponent, ActualTokenDirective], + imports: [ReactiveFormsModule], + }).compileComponents() + ); + + it('does not throw on both declarations of valueAccessor', () => { + expect(() => + MockRender( + ` +
+ + + +
+ `, + { + form: new FormGroup({ + field: new FormControl(), + }), + } + ) + ).not.toThrow(); + }); + + it('does not throw on mix of NG_VALUE_ACCESSOR declarations', () => { + expect(() => + MockRender( + ` +
+ +
+ `, + { + form: new FormGroup({ + field: new FormControl(), + }), + } + ) + ).toThrow(); + }); + + it('does not throw when component does not provide NG_VALUE_ACCESSOR', () => { + expect(() => + MockRender( + ` +
+ +
+ `, + { + form: new FormGroup({ + field: new FormControl(), + }), + } + ) + ).not.toThrow(); + }); + + it('throws when NG_VALUE_ACCESSOR is not provided', () => { + expect(() => + MockRender( + ` +
+ +
+ `, + { + form: new FormGroup({ + field: new FormControl(), + }), + } + ) + ).toThrow(); + }); + + it('does not throw when NG_VALUE_ACCESSOR is provided on a normal tag', () => { + expect(() => + MockRender( + ` +
+
+
+ `, + { + form: new FormGroup({ + field: new FormControl(), + }), + } + ) + ).not.toThrow(); + }); + + it('throws when NG_VALUE_ACCESSOR is not provided on a normal tag', () => { + expect(() => + MockRender( + ` +
+
+
+ `, + { + form: new FormGroup({ + field: new FormControl(), + }), + } + ) + ).toThrow(); + }); +}); + +describe('issue-157:mock', () => { + beforeEach(() => + MockBuilder(ReactiveFormsModule) + .mock(ActualEmptyComponent) + .mock(ActualInjectionComponent) + .mock(ActualTokenComponent) + .mock(ActualTokenDirective) + ); + + it('does not throw on both declarations of valueAccessor', () => { + expect(() => + MockRender( + ` +
+ + + +
+ `, + { + form: new FormGroup({ + field: new FormControl(), + }), + } + ) + ).not.toThrow(); + }); + + // not original behavior + it('does not throw on mix of NG_VALUE_ACCESSOR declarations when both component and directive are mocked', () => { + expect(() => + MockRender( + ` +
+ +
+ `, + { + form: new FormGroup({ + field: new FormControl(), + }), + } + ) + ).not.toThrow(); + }); + + it('does not throw when component does not provide NG_VALUE_ACCESSOR', () => { + expect(() => + MockRender( + ` +
+ +
+ `, + { + form: new FormGroup({ + field: new FormControl(), + }), + } + ) + ).not.toThrow(); + }); + + // not original behavior + it('does not throw when NG_VALUE_ACCESSOR is not provided, but the related component is mocked', () => { + expect(() => + MockRender( + ` +
+ +
+ `, + { + form: new FormGroup({ + field: new FormControl(), + }), + } + ) + ).not.toThrow(); + }); + + it('does not throw when NG_VALUE_ACCESSOR is provided on a normal tag', () => { + expect(() => + MockRender( + ` +
+
+
+ `, + { + form: new FormGroup({ + field: new FormControl(), + }), + } + ) + ).not.toThrow(); + }); + + it('throws when NG_VALUE_ACCESSOR is not provided on a normal tag', () => { + expect(() => + MockRender( + ` +
+
+
+ `, + { + form: new FormGroup({ + field: new FormControl(), + }), + } + ) + ).toThrow(); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 8684cc6d2e..80b35857df 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "lib": ["dom", "es7"], "module": "commonjs", - "target": "es2015", + "target": "es5", "noImplicitAny": true, "moduleResolution": "node", "emitDecoratorMetadata": true, diff --git a/tslint.json b/tslint.json index d02364b1ec..fd4898efc9 100644 --- a/tslint.json +++ b/tslint.json @@ -5,6 +5,7 @@ "arrow-parens": false, "comment-format": false, "completed-docs": false, + "cyclomatic-complexity": false, "deprecation": false, "file-name-casing": false, "max-classes-per-file": false,