diff --git a/.lintstagedrc.js b/.lintstagedrc.js index faa1852f7f..9e7ecbe38a 100644 --- a/.lintstagedrc.js +++ b/.lintstagedrc.js @@ -27,6 +27,7 @@ module.exports = { const filesForLint = filenames .map(file => path.relative(cwd, file)) .filter(file => !file.match(/\.spec\.ts$/i)) + .filter(file => !file.match(/\.fixtures\.ts$/i)) .filter(file => !file.match(/^e2e\//i)) .filter(file => !file.match(/^examples\//i)) .filter(file => !file.match(/^tests\//i)) diff --git a/.prettierignore b/.prettierignore index 2c8af31099..01e3818bc4 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,6 +4,7 @@ .browserslistrc .dockerignore .editorconfig +.env .gitignore .gitkeep .idea/ diff --git a/README.md b/README.md index 20c5522ac0..8a9f3a6adb 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ +[![chat on gitter](https://badges.gitter.im/ng-mocks/community.svg)](https://gitter.im/ng-mocks/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![npm version](https://badge.fury.io/js/ng-mocks.svg)](https://badge.fury.io/js/ng-mocks) -[![Build Status](https://travis-ci.org/ike18t/ng-mocks.svg?branch=master)](https://travis-ci.org/ike18t/ng-mocks) -[![Coverage Status](https://coveralls.io/repos/github/ike18t/ng-mocks/badge.svg?branch=master)](https://coveralls.io/github/ike18t/ng-mocks?branch=master) -[![Gitter](https://badges.gitter.im/ng-mocks/community.svg)](https://gitter.im/ng-mocks/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![build status](https://travis-ci.org/ike18t/ng-mocks.svg?branch=master)](https://travis-ci.org/ike18t/ng-mocks) +[![coverage status](https://coveralls.io/repos/github/ike18t/ng-mocks/badge.svg?branch=master)](https://coveralls.io/github/ike18t/ng-mocks?branch=master) # ngMocks - ease of mocking annoying dependencies in Angular unit tests @@ -45,6 +45,7 @@ I'm open to contributions. - [a directive](#how-to-mock-a-directive) - [a pipe](#how-to-mock-a-pipe) - [a service](#how-to-mock-a-service) + - [a provider](#how-to-mock-a-provider) - [a module](#how-to-mock-a-module) - [a form control](#how-to-mock-classic-and-reactive-form-components) @@ -103,9 +104,11 @@ and/or mock child components** and dependencies via a few lines of code with hel [`MockComponent`](#how-to-mock-a-component), [`MockDirective`](#how-to-mock-a-directive), [`MockPipe`](#how-to-mock-a-pipe), -[`MockService`](#how-to-mock-a-service), -[`MockModule`](#how-to-mock-a-module) and -[`MockBuilder`](#mockbuilder). +[`MockProvider`](#how-to-mock-a-provider), +[`MockModule`](#how-to-mock-a-module), +or with pro tools such as +[`MockBuilder`](#mockbuilder) with +[`MockRender`](#mockrender). Let's imagine that in our Angular application we have a base component and its template looks like that: @@ -249,7 +252,7 @@ beforeEach(() => ); ``` -Profit. Subscribe, like, share! +Profit. Subscribe, like, share! [Back to top](#content). Below more detailed documentation begins, please bear with us. @@ -260,6 +263,10 @@ Below more detailed documentation begins, please bear with us. This section provides vast **information how to mock dependencies in angular** with real examples and detailed explanations of all aspects might be useful in writing fully isolated unit tests. +[Back to top](#content). + +--- + ### How to mock a component There is a `MockComponent` function. It covers almost all needs for mocking behavior. @@ -336,7 +343,7 @@ describe('Test', () => {

The source file is here: -[examples/MockComponent/test.spec.ts](https://github.com/ike18t/ng-mocks/blob/master/examples/MockComponent/test.spec.ts).
+[MockComponent](https://github.com/ike18t/ng-mocks/blob/master/examples/MockComponent/test.spec.ts).
Prefix it with `fdescribe` or `fit` on [codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/MockComponent/test.spec.ts) to play with. @@ -450,6 +457,8 @@ describe('MockComponent', () => {

+
[Back to top](#content). + --- ### How to mock a directive @@ -526,7 +535,7 @@ describe('Test', () => {

The source file is here: -[examples/MockDirective-Attribute/test.spec.ts](https://github.com/ike18t/ng-mocks/blob/master/examples/MockDirective-Attribute/test.spec.ts).
+[MockDirective-Attribute](https://github.com/ike18t/ng-mocks/blob/master/examples/MockDirective-Attribute/test.spec.ts).
Prefix it with `fdescribe` or `fit` on [codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/MockDirective-Attribute/test.spec.ts) to play with. @@ -599,7 +608,7 @@ It's important to render a structural directive with the right context first, if you want to assert on its nested elements. The source file is here: -[examples/MockDirective-Structural/test.spec.ts](https://github.com/ike18t/ng-mocks/blob/master/examples/MockDirective-Structural/test.spec.ts).
+[MockDirective-Structural](https://github.com/ike18t/ng-mocks/blob/master/examples/MockDirective-Structural/test.spec.ts).
Prefix it with `fdescribe` or `fit` on [codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/MockDirective-Structural/test.spec.ts) to play with. @@ -646,6 +655,8 @@ describe('MockDirective', () => {

+
[Back to top](#content). + --- ### How to mock a pipe @@ -716,7 +727,7 @@ describe('Test', () => {

The source file is here: -[examples/MockPipe/test.spec.ts](https://github.com/ike18t/ng-mocks/blob/master/examples/MockPipe/test.spec.ts).
+[MockPipe](https://github.com/ike18t/ng-mocks/blob/master/examples/MockPipe/test.spec.ts).
Prefix it with `fdescribe` or `fit` on [codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/MockPipe/test.spec.ts) to play with. @@ -742,6 +753,8 @@ describe('MockPipe', () => {

+
[Back to top](#content). + --- ### How to mock a service @@ -785,6 +798,20 @@ const instance = MockService({ instance.nested.func = () => 'My Custom Behavior'; ``` +[Back to top](#content). + +--- + +### How to mock a provider + +`MockProvider` might be useful If you want to stub a service or a token in providers. + +- `MockProvider(MyService)` - creates a `useFactory` provider with `MockService(MyService)` under the hood. +- `MockProvider(MY_TOKEN_1)` - creates a `useValue` provider that returns `undefined`. +- `MockProvider(MyService, {mocked: true})` - creates a `useValue` provider that returns the passed value. +- `MockProvider(MY_TOKEN_1, 'fake')` - creates a `useValue` provider that returns the passed value. +- `MockProviders(MyService1, MY_TOKEN_1, ...)` - returns an array of mocked services and tokens. + Now let's pretend that in an Angular application `TargetComponent` depends on a service of `DependencyService`, and it should be mocked in favor of avoiding overhead. @@ -809,16 +836,13 @@ describe('Test', () => { }); ``` -To **mock a service** simply pass `DependencyService` into `MockService`: +To **mock a service** simply pass it into `MockProvider`: ```typescript TestBed.configureTestingModule({ declarations: [TargetComponent], providers: [ - { - provide: DependencyService, - useValue: MockService(DependencyService), // <- profit - }, + MockProvider(DependencyService), // <- profit ], }); ``` @@ -839,6 +863,41 @@ describe('Test', () => { }); ``` +
Click to see an example of mocking providers in Angular tests +

+ +The source file is here: +[MockProvider](https://github.com/ike18t/ng-mocks/blob/master/examples/MockProvider/test.spec.ts).
+Prefix it with `fdescribe` or `fit` on +[codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/MockProvider/test.spec.ts) +to play with. + +```typescript +describe('MockProvider', () => { + beforeEach(() => + TestBed.configureTestingModule({ + declarations: [TargetComponent], + providers: [ + MockProvider(DependencyService), + MockProvider(DEPENDENCY_TOKEN, 'mocked token'), + ], + }).compileComponents() + ); + + it('mocks providers', () => { + const fixture = TestBed.createComponent(TargetComponent); + fixture.detectChanges(); + expect(fixture.nativeElement.innerHTML).not.toContain('target'); + expect(fixture.nativeElement.innerHTML).toContain('mocked token'); + }); +}); +``` + +

+
+ +
[Back to top](#content). + --- ### How to mock a module @@ -930,7 +989,7 @@ beforeEach(() => MockBuilder(TargetComponent, TargetModule));

The source file is here: -[examples/MockModule/test.spec.ts](https://github.com/ike18t/ng-mocks/blob/master/examples/MockModule/test.spec.ts).
+[MockModule](https://github.com/ike18t/ng-mocks/blob/master/examples/MockModule/test.spec.ts).
Prefix it with `fdescribe` or `fit` on [codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/MockModule/test.spec.ts) to play with. @@ -953,6 +1012,8 @@ describe('MockModule', () => {

+
[Back to top](#content). + --- ### How to mock classic and reactive form components @@ -969,7 +1030,7 @@ A mocked instance of `ControlValueAccessor` provides:

The source file is here: -[examples/MockReactiveForms/test.spec.ts](https://github.com/ike18t/ng-mocks/blob/master/examples/MockReactiveForms/test.spec.ts).
+[MockReactiveForms](https://github.com/ike18t/ng-mocks/blob/master/examples/MockReactiveForms/test.spec.ts).
Prefix it with `fdescribe` or `fit` on [codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/MockReactiveForms/test.spec.ts) to play with. @@ -1014,7 +1075,7 @@ describe('MockReactiveForms', () => {

The source file is here: -[examples/MockForms/test.spec.ts](https://github.com/ike18t/ng-mocks/blob/master/examples/MockForms/test.spec.ts).
+[MockForms](https://github.com/ike18t/ng-mocks/blob/master/examples/MockForms/test.spec.ts).
Prefix it with `fdescribe` or `fit` on [codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/MockForms/test.spec.ts) to play with. @@ -1059,12 +1120,14 @@ describe('MockForms', () => {

+
[Back to top](#content). + --- ## Extensive example of mocking in Angular tests The source file is here: -[examples/MAIN/test.spec.ts](https://github.com/ike18t/ng-mocks/blob/master/examples/MAIN/test.spec.ts).
+[MAIN](https://github.com/ike18t/ng-mocks/blob/master/examples/MAIN/test.spec.ts).
Prefix it with `fdescribe` or `fit` on [codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/MAIN/test.spec.ts) to play with. @@ -1256,6 +1319,8 @@ Our tests: - [examples from the doc](https://github.com/ike18t/ng-mocks/tree/master/examples) - [current e2e tests](https://github.com/ike18t/ng-mocks/tree/master/tests) +[Back to top](#content). + --- ## Functions for easy mocking and rendering @@ -1268,6 +1333,10 @@ with minimum coding. - [`MockInstance`](#mockinstance) - edits anything on an early stage - [`ngMocks`](#ngmocks) - facilitates work with fixtures +[Back to top](#content). + +--- + ### MockBuilder `MockBuilder` is the simplest way to mock everything. @@ -1291,7 +1360,7 @@ and has a rich toolkit that supports:

The source file is here: -[examples/MockBuilder/test.spec.ts](https://github.com/ike18t/ng-mocks/blob/master/examples/MockBuilder/test.spec.ts).
+[MockBuilder](https://github.com/ike18t/ng-mocks/blob/master/examples/MockBuilder/test.spec.ts).
Prefix it with `fdescribe` or `fit` on [codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/MockBuilder/test.spec.ts) to play with. @@ -1574,6 +1643,8 @@ beforeEach(() => ); ``` +[Back to top](#content). + --- ### MockRender @@ -1581,17 +1652,23 @@ beforeEach(() => `MockRender` is a simple tool that helps with **shallow rendering in Angular tests** when we want to assert `Inputs`, `Outputs`, `ChildContent` and custom templates. -**Please note** +**Please note**, that `MockRender(MyComponent)` is not assignable to +`ComponentFixture`. You should use either -> that `MockRender(MyComponent)` is not assignable -> to `ComponentFixture`. -> -> You should use either -> `MockedComponentFixture` or -> `ComponentFixture>`. +```typescript +MockedComponentFixture +``` + +or + +```typescript +ComponentFixture< + DefaultRenderComponent > -> It happens because `MockRender` generates an additional component -> to render the desired thing and its interface differs. +``` + +It happens because `MockRender` generates an additional component to +render the desired thing and its interface differs. It returns `MockedComponentFixture` type. The difference is an additional `point` property. The best thing about it is that `fixture.point.componentInstance` is typed to the component's class instead of `any`. @@ -1627,7 +1704,7 @@ the render. There is **an example how to render a custom template in an Angular tests** below. The source file is here: -[examples/MockRender/test.spec.ts](https://github.com/ike18t/ng-mocks/blob/master/examples/MockRender/test.spec.ts).
+[MockRender](https://github.com/ike18t/ng-mocks/blob/master/examples/MockRender/test.spec.ts).
Prefix it with `fdescribe` or `fit` on [codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/MockRender/test.spec.ts) to play with. @@ -1701,6 +1778,8 @@ describe('MockRender', () => { }); ``` +[Back to top](#content). + --- ### MockInstance @@ -1739,7 +1818,7 @@ After a test you can reset changes to avoid their influence in other tests via a

The source file is here: -[examples/MockInstance/test.spec.ts](https://github.com/ike18t/ng-mocks/blob/master/examples/MockInstance/test.spec.ts).
+[MockInstance](https://github.com/ike18t/ng-mocks/blob/master/examples/MockInstance/test.spec.ts).
Prefix it with `fdescribe` or `fit` on [codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/MockInstance/test.spec.ts) to play with. @@ -1786,6 +1865,8 @@ describe('MockInstance', () => {

+
[Back to top](#content). + --- ### ngMocks @@ -1923,6 +2004,8 @@ ngMocks.stub(instance, {

+
[Back to top](#content). + --- ### Helper functions @@ -1992,6 +2075,8 @@ This function verifies tokens. - `isNgInjectionToken(TOKEN)` - checks whether `TOKEN` is a token +[Back to top](#content). + --- ### Usage with 3rd-party libraries @@ -2037,6 +2122,8 @@ const createComponent = createComponentFactory({ Profit. Subscribe, like, share! +[Back to top](#content). + --- ### Making Angular tests faster @@ -2139,6 +2226,8 @@ describe('beforeEach:manual-spy', () => {

+
[Back to top](#content). + --- ### Auto Spy @@ -2159,6 +2248,8 @@ In case of jest add it to `src/setupJest.ts`. import 'ng-mocks/dist/jest'; ``` +[Back to top](#content). + --- ## How to test an Angular application @@ -2168,11 +2259,17 @@ covering almost all possible cases. Should you not find an example you are interested in? Just contact us. +[Back to top](#content). + +--- + ### How to test a component Please check [the extensive example](#extensive-example-of-mocking-in-angular-tests), it covers all aspects of **testing components in angular applications**. +[Back to top](#content). + --- ### How to test a provider of a component @@ -2198,11 +2295,13 @@ const service = fixture.point.injector.get(TargetService); Profit. Now we can assert behavior of the service. A source file of this test is here: -[examples/TestProviderInComponent/test.spec.ts](https://github.com/ike18t/ng-mocks/blob/master/examples/TestProviderInComponent/test.spec.ts).
+[TestProviderInComponent](https://github.com/ike18t/ng-mocks/blob/master/examples/TestProviderInComponent/test.spec.ts).
Prefix it with `fdescribe` or `fit` on [codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/TestProviderInComponent/test.spec.ts) to play with. +[Back to top](#content). + --- ### How to test an attribute directive @@ -2236,11 +2335,13 @@ const instance = ngMocks.get(fixture.point, TargetDirective); ``` A source file of this test is here: -[examples/TestAttributeDirective/test.spec.ts](https://github.com/ike18t/ng-mocks/blob/master/examples/TestAttributeDirective/test.spec.ts).
+[TestAttributeDirective](https://github.com/ike18t/ng-mocks/blob/master/examples/TestAttributeDirective/test.spec.ts).
Prefix it with `fdescribe` or `fit` on [codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/TestAttributeDirective/test.spec.ts) to play with. +[Back to top](#content). + --- ### How to test a provider of a directive @@ -2267,11 +2368,13 @@ const service = fixture.point.injector.get(TargetService); ``` A source file of this test is here: -[examples/TestProviderInDirective/test.spec.ts](https://github.com/ike18t/ng-mocks/blob/master/examples/TestProviderInDirective/test.spec.ts).
+[TestProviderInDirective](https://github.com/ike18t/ng-mocks/blob/master/examples/TestProviderInDirective/test.spec.ts).
Prefix it with `fdescribe` or `fit` on [codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/TestProviderInDirective/test.spec.ts) to play with. +[Back to top](#content). + --- ### How to test a structural directive @@ -2322,11 +2425,13 @@ expect(fixture.nativeElement.innerHTML).toContain('content'); ``` A source file of this test is here: -[examples/TestStructuralDirective/test.spec.ts](https://github.com/ike18t/ng-mocks/blob/master/examples/TestStructuralDirective/test.spec.ts).
+[TestStructuralDirective](https://github.com/ike18t/ng-mocks/blob/master/examples/TestStructuralDirective/test.spec.ts).
Prefix it with `fdescribe` or `fit` on [codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/TestStructuralDirective/test.spec.ts) to play with. +[Back to top](#content). + --- ### How to test a structural directive with a context @@ -2363,11 +2468,13 @@ expect(fixture.nativeElement.innerHTML).not.toContain('1: world'); ``` A source file of this test is here: -[examples/TestStructuralDirectiveWithContext/test.spec.ts](https://github.com/ike18t/ng-mocks/blob/master/examples/TestStructuralDirectiveWithContext/test.spec.ts).
+[TestStructuralDirectiveWithContext](https://github.com/ike18t/ng-mocks/blob/master/examples/TestStructuralDirectiveWithContext/test.spec.ts).
Prefix it with `fdescribe` or `fit` on [codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/TestStructuralDirectiveWithContext/test.spec.ts) to play with. +[Back to top](#content). + --- ### How to test a pipe @@ -2394,11 +2501,13 @@ expect(fixture.nativeElement.innerHTML).toEqual('1, 2, 3'); ``` A source file of this test is here: -[examples/TestPipe/test.spec.ts](https://github.com/ike18t/ng-mocks/blob/master/examples/TestPipe/test.spec.ts).
+[TestPipe](https://github.com/ike18t/ng-mocks/blob/master/examples/TestPipe/test.spec.ts).
Prefix it with `fdescribe` or `fit` on [codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/TestPipe/test.spec.ts) to play with. +[Back to top](#content). + --- ### How to test a provider @@ -2452,41 +2561,43 @@ Despite the way providers are created: `useClass`, `useValue` etc. Their tests are quite similar. A source file of a test without dependencies is here: -[examples/TestProvider/test.spec.ts](https://github.com/ike18t/ng-mocks/blob/master/examples/TestProvider/test.spec.ts).
+[TestProvider](https://github.com/ike18t/ng-mocks/blob/master/examples/TestProvider/test.spec.ts).
Prefix it with `fdescribe` or `fit` on [codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/TestProvider/test.spec.ts) to play with. A source file of a test with dependencies is here: -[examples/TestProviderWithDependencies/test.spec.ts](https://github.com/ike18t/ng-mocks/blob/master/examples/TestProviderWithDependencies/test.spec.ts).
+[TestProviderWithDependencies](https://github.com/ike18t/ng-mocks/blob/master/examples/TestProviderWithDependencies/test.spec.ts).
Prefix it with `fdescribe` or `fit` on [codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/TestProviderWithDependencies/test.spec.ts) to play with. A source file of a test with `useClass` is here: -[examples/TestProviderWithUseClass/test.spec.ts](https://github.com/ike18t/ng-mocks/blob/master/examples/TestProviderWithUseClass/test.spec.ts).
+[TestProviderWithUseClass](https://github.com/ike18t/ng-mocks/blob/master/examples/TestProviderWithUseClass/test.spec.ts).
Prefix it with `fdescribe` or `fit` on [codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/TestProviderWithUseClass/test.spec.ts) to play with. A source file of a test with `useValue` is here: -[examples/TestProviderWithUseValue/test.spec.ts](https://github.com/ike18t/ng-mocks/blob/master/examples/TestProviderWithUseValue/test.spec.ts).
+[TestProviderWithUseValue](https://github.com/ike18t/ng-mocks/blob/master/examples/TestProviderWithUseValue/test.spec.ts).
Prefix it with `fdescribe` or `fit` on [codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/examples/TestProviderWithUseValue/test.spec.ts) to play with. A source file of a test with `useExisting` is here: -[examples/TestProviderWithUseExisting/test.spec.ts](https://github.com/ike18t/ng-mocks/blob/master/examples/TestProviderWithUseExisting/test.spec.ts).
+[TestProviderWithUseExisting](https://github.com/ike18t/ng-mocks/blob/master/examples/TestProviderWithUseExisting/test.spec.ts).
Prefix it with `fdescribe` or `fit` on [codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/TestProviderWithUseExisting/test.spec.ts) to play with. A source file of a test with `useFactory` is here: -[examples/TestProviderWithUseFactory/test.spec.ts](https://github.com/ike18t/ng-mocks/blob/master/examples/TestProviderWithUseFactory/test.spec.ts).
+[TestProviderWithUseFactory](https://github.com/ike18t/ng-mocks/blob/master/examples/TestProviderWithUseFactory/test.spec.ts).
Prefix it with `fdescribe` or `fit` on [codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/examples/TestProviderWithUseFactory/test.spec.ts) to play with. +[Back to top](#content). + --- ### How to test a token @@ -2521,11 +2632,13 @@ expect(token).toEqual(jasmine.any(ServiceExisting)); ``` A source file of this test is here: -[examples/TestToken/test.spec.ts](https://github.com/ike18t/ng-mocks/blob/master/examples/TestToken/test.spec.ts).
+[TestToken](https://github.com/ike18t/ng-mocks/blob/master/examples/TestToken/test.spec.ts).
Prefix it with `fdescribe` or `fit` on [codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/TestToken/test.spec.ts) to play with. +[Back to top](#content). + --- ### How to test a multi token @@ -2542,11 +2655,13 @@ expect(values.length).toEqual(4); ``` A source file of this test is here: -[examples/TestMultiToken/test.spec.ts](https://github.com/ike18t/ng-mocks/blob/master/examples/TestMultiToken/test.spec.ts).
+[TestMultiToken](https://github.com/ike18t/ng-mocks/blob/master/examples/TestMultiToken/test.spec.ts).
Prefix it with `fdescribe` or `fit` on [codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/TestMultiToken/test.spec.ts) to play with. +[Back to top](#content). + --- ### How to test a route @@ -2643,11 +2758,13 @@ expect(() => ngMocks.find(fixture, Target1Component)).not.toThrow(); ``` A source file of these tests is here: -[examples/TestRoute/test.spec.ts](https://github.com/ike18t/ng-mocks/blob/master/examples/TestRoute/test.spec.ts).
+[TestRoute](https://github.com/ike18t/ng-mocks/blob/master/examples/TestRoute/test.spec.ts).
Prefix it with `fdescribe` or `fit` on [codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/TestRoute/test.spec.ts) to play with. +[Back to top](#content). + --- ### How to test a routing guard @@ -2686,11 +2803,13 @@ expect(() => ngMocks.find(fixture, LoginComponent)).not.toThrow(); ``` A source file of this test is here: -[examples/TestRoutingGuard/test.spec.ts](https://github.com/ike18t/ng-mocks/blob/master/examples/TestRoutingGuard/test.spec.ts).
+[TestRoutingGuard](https://github.com/ike18t/ng-mocks/blob/master/examples/TestRoutingGuard/test.spec.ts).
Prefix it with `fdescribe` or `fit` on [codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/TestRoutingGuard/test.spec.ts) to play with. +[Back to top](#content). + --- ### How to test a routing resolver @@ -2756,11 +2875,13 @@ expect(route.snapshot.data).toEqual({ ``` A source file of this test is here: -[examples/TestRoutingResolver/test.spec.ts](https://github.com/ike18t/ng-mocks/blob/master/examples/TestRoutingResolver/test.spec.ts).
+[TestRoutingResolver](https://github.com/ike18t/ng-mocks/blob/master/examples/TestRoutingResolver/test.spec.ts).
Prefix it with `fdescribe` or `fit` on [codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/TestRoutingResolver/test.spec.ts) to play with. +[Back to top](#content). + --- ### How to test a http request @@ -2804,11 +2925,13 @@ expect(actual).toEqual([false, true, false]); ``` A source file of this test is here: -[examples/TestHttpRequest/test.spec.ts](https://github.com/ike18t/ng-mocks/blob/master/examples/TestHttpRequest/test.spec.ts).
+[TestHttpRequest](https://github.com/ike18t/ng-mocks/blob/master/examples/TestHttpRequest/test.spec.ts).
Prefix it with `fdescribe` or `fit` on [codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/TestHttpRequest/test.spec.ts) to play with. +[Back to top](#content). + --- ### How to test a http interceptor @@ -2861,7 +2984,9 @@ expect(req.request.headers.get('My-Custom')).toEqual( ``` A source file of this test is here: -[examples/TestHttpInterceptor/test.spec.ts](https://github.com/ike18t/ng-mocks/blob/master/examples/TestHttpInterceptor/test.spec.ts).
+[TestHttpInterceptor](https://github.com/ike18t/ng-mocks/blob/master/examples/TestHttpInterceptor/test.spec.ts).
Prefix it with `fdescribe` or `fit` on [codesandbox.io](https://codesandbox.io/s/github/satanTime/ng-mocks-cs?file=/src/examples/TestHttpInterceptor/test.spec.ts) to play with. + +[Back to top](#content). diff --git a/examples/MockProvider/test.spec.ts b/examples/MockProvider/test.spec.ts new file mode 100644 index 0000000000..49a50cf556 --- /dev/null +++ b/examples/MockProvider/test.spec.ts @@ -0,0 +1,40 @@ +import { Component, Inject, Injectable, InjectionToken } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { MockProvider } from 'ng-mocks'; + +const DEPENDENCY_TOKEN = new InjectionToken('TARGET_TOKEN'); + +@Injectable() +class DependencyService { + public name = 'target'; +} + +@Component({ + selector: 'target', + template: `{{ service.name }} {{ token }}`, +}) +class TargetComponent { + public readonly service: DependencyService; + public readonly token: string; + + constructor(service: DependencyService, @Inject(DEPENDENCY_TOKEN) token: string) { + this.service = service; + this.token = token; + } +} + +describe('MockProvider', () => { + beforeEach(() => + TestBed.configureTestingModule({ + declarations: [TargetComponent], + providers: [MockProvider(DependencyService), MockProvider(DEPENDENCY_TOKEN, 'mocked token')], + }).compileComponents() + ); + + it('mocks providers', () => { + const fixture = TestBed.createComponent(TargetComponent); + fixture.detectChanges(); + expect(fixture.nativeElement.innerHTML).not.toContain('target'); + expect(fixture.nativeElement.innerHTML).toContain('mocked token'); + }); +}); diff --git a/index.ts b/index.ts index ad84bf8002..ad07570a02 100644 --- a/index.ts +++ b/index.ts @@ -1,11 +1,43 @@ -export * from './lib/common'; -export * from './lib/mock-builder'; -export * from './lib/mock-component'; -export * from './lib/mock-declaration'; -export * from './lib/mock-directive'; -export * from './lib/mock-helper'; -export * from './lib/mock-instance'; -export * from './lib/mock-module'; -export * from './lib/mock-pipe'; -export * from './lib/mock-render'; -export * from './lib/mock-service'; +export { getTestBedInjection, getInjection } from './lib/common/core.helpers'; +export * from './lib/common/core.tokens'; +export * from './lib/common/core.types'; +export * from './lib/common/func.get-mocked-ng-def-of'; +export * from './lib/common/func.get-source-of-mock'; +export * from './lib/common/func.is-mock-of'; +export * from './lib/common/func.is-mocked-ng-def-of'; +export * from './lib/common/func.is-ng-def'; +export * from './lib/common/func.is-ng-injection-token'; +export * from './lib/common/func.is-ng-module-def-with-providers'; +export * from './lib/common/func.is-ng-type'; +export { Mock } from './lib/common/mock'; +export * from './lib/common/mock-control-value-accessor'; + +export * from './lib/mock-builder/mock-builder'; +export * from './lib/mock-builder/types'; + +export * from './lib/mock-component/mock-component'; +export * from './lib/mock-component/types'; + +export * from './lib/mock-declaration/mock-declaration'; + +export * from './lib/mock-directive/mock-directive'; +export * from './lib/mock-directive/types'; + +export * from './lib/mock-helper/mock-helper'; + +export * from './lib/mock-instance/mock-instance'; + +export * from './lib/mock-module/mock-module'; +export * from './lib/mock-module/types'; + +export * from './lib/mock-pipe/mock-pipe'; +export * from './lib/mock-pipe/types'; + +export * from './lib/mock-provider/mock-provider'; + +export * from './lib/mock-render/mock-render'; +export * from './lib/mock-render/types'; + +export { registerMockFunction } from './lib/mock-service/helper'; +export * from './lib/mock-service/mock-service'; +export * from './lib/mock-service/types'; diff --git a/jasmine.ts b/jasmine.ts index 11aafefb58..43bbd4a364 100644 --- a/jasmine.ts +++ b/jasmine.ts @@ -1,5 +1,5 @@ -import { mockServiceHelper } from 'ng-mocks'; +import { registerMockFunction } from 'ng-mocks'; declare const jasmine: any; -mockServiceHelper.registerMockFunction(mockName => jasmine.createSpy(mockName)); +registerMockFunction(mockName => jasmine.createSpy(mockName)); diff --git a/jest.ts b/jest.ts index d869bd546b..eadd3789dd 100644 --- a/jest.ts +++ b/jest.ts @@ -1,3 +1,3 @@ -import { mockServiceHelper } from 'ng-mocks'; +import { registerMockFunction } from 'ng-mocks'; -mockServiceHelper.registerMockFunction(name => jest.fn().mockName(name)); +registerMockFunction(name => jest.fn().mockName(name)); diff --git a/lib/common/core.helpers.ts b/lib/common/core.helpers.ts new file mode 100644 index 0000000000..cdf375e054 --- /dev/null +++ b/lib/common/core.helpers.ts @@ -0,0 +1,76 @@ +import { InjectionToken } from '@angular/core'; +import { getTestBed } from '@angular/core/testing'; + +import { jitReflector } from './core.reflect'; +import { Type } from './core.types'; + +export const getTestBedInjection = (token: Type | InjectionToken): I | undefined => { + const testBed: any = getTestBed(); + try { + /* istanbul ignore next */ + return testBed.inject ? testBed.inject(token) : testBed.get(token); + } catch (e) { + return undefined; + } +}; + +export const getInjection = (token: Type | InjectionToken): I => { + const testBed: any = getTestBed(); + /* istanbul ignore next */ + return testBed.inject ? testBed.inject(token) : testBed.get(token); +}; + +export const flatten = (values: T | T[], result: T[] = []): T[] => { + if (Array.isArray(values)) { + values.forEach((value: T | T[]) => flatten(value, result)); + } else { + result.push(values); + } + return result; +}; + +export const mapKeys = (set: Map): T[] => { + const result: T[] = []; + set.forEach((_, value: T) => result.push(value)); + return result; +}; + +export const mapValues = (set: { forEach(a1: (value: T) => void): void }): T[] => { + const result: T[] = []; + set.forEach((value: T) => result.push(value)); + return result; +}; + +export const mapEntries = (set: Map): Array<[K, T]> => { + const result: Array<[K, T]> = []; + set.forEach((value: T, key: K) => result.push([key, value])); + return result; +}; + +export const extendClass = (base: Type): Type => { + let child: any; + const parent: any = base; + + // first we try to eval es2015 style and if it fails to use es5 transpilation in the catch block. + (window as any).ngMocksParent = parent; + /* istanbul ignore next */ + try { + // tslint:disable-next-line:no-eval + eval(` + class child extends window.ngMocksParent { + } + window.ngMocksResult = child + `); + child = (window as any).ngMocksResult; + } catch (e) { + class ClassEs5 extends parent {} + + child = ClassEs5; + } + (window as any).ngMocksParent = undefined; + + // the next step is to respect constructor parameters as the parent class. + child.parameters = jitReflector.parameters(parent); + + return child; +}; diff --git a/lib/common/reflect.ts b/lib/common/core.reflect.ts similarity index 100% rename from lib/common/reflect.ts rename to lib/common/core.reflect.ts diff --git a/lib/common/core.tokens.ts b/lib/common/core.tokens.ts new file mode 100644 index 0000000000..dcdba581cc --- /dev/null +++ b/lib/common/core.tokens.ts @@ -0,0 +1,12 @@ +import { InjectionToken } from '@angular/core'; +import { MetadataOverride } from '@angular/core/testing'; + +import { AbstractType, Type } from './core.types'; + +export const NG_MOCKS = new InjectionToken>('NG_MOCKS'); +export const NG_MOCKS_TOUCHES = new InjectionToken>('NG_MOCKS_TOUCHES'); +export const NG_MOCKS_OVERRIDES = new InjectionToken | AbstractType, MetadataOverride>>( + 'NG_MOCKS_OVERRIDES' +); +export const NG_GUARDS = new InjectionToken('NG_MOCKS_GUARDS'); +export const NG_INTERCEPTORS = new InjectionToken('NG_MOCKS_INTERCEPTORS'); diff --git a/lib/common/core.types.ts b/lib/common/core.types.ts new file mode 100644 index 0000000000..a008bb95f0 --- /dev/null +++ b/lib/common/core.types.ts @@ -0,0 +1,14 @@ +// It has to be an interface. +// tslint:disable-next-line:interface-name +export interface AbstractType extends Function { + prototype: T; +} + +// It has to be an interface. +// tslint:disable-next-line:interface-name +export interface Type extends Function { + // tslint:disable-next-line:callable-types + new (...args: any[]): T; +} + +export type AnyType = Type | AbstractType; diff --git a/lib/common/decorate.inputs.ts b/lib/common/decorate.inputs.ts new file mode 100644 index 0000000000..b82a16568c --- /dev/null +++ b/lib/common/decorate.inputs.ts @@ -0,0 +1,21 @@ +import { Input } from '@angular/core'; + +import { Type } from './core.types'; + +// Looks like an A9 bug, that queries from @Component aren't processed. +// Also we have to pass prototype, not the class. +// The same issue happens with outputs, but time to time +// (when I restart tests with refreshing browser manually). +// https://github.com/ike18t/ng-mocks/issues/109 +export default function (cls: Type, inputs?: string[], exclude?: string[]) { + /* istanbul ignore else */ + if (inputs) { + for (const input of inputs) { + const [key, alias] = input.split(': '); + if (exclude && exclude.indexOf(key) !== -1) { + continue; + } + Input(alias)(cls.prototype, key); + } + } +} diff --git a/lib/common/decorate.outputs.ts b/lib/common/decorate.outputs.ts new file mode 100644 index 0000000000..54d39225d3 --- /dev/null +++ b/lib/common/decorate.outputs.ts @@ -0,0 +1,18 @@ +import { Output } from '@angular/core'; + +import { Type } from './core.types'; + +// Looks like an A9 bug, that queries from @Component aren't processed. +// Also we have to pass prototype, not the class. +// The same issue happens with outputs, but time to time +// (when I restart tests with refreshing browser manually). +// https://github.com/ike18t/ng-mocks/issues/109 +export default function (cls: Type, outputs?: string[]) { + /* istanbul ignore else */ + if (outputs) { + for (const output of outputs) { + const [key, alias] = output.split(': '); + Output(alias)(cls.prototype, key); + } + } +} diff --git a/lib/common/decorate.queries.ts b/lib/common/decorate.queries.ts new file mode 100644 index 0000000000..127622f288 --- /dev/null +++ b/lib/common/decorate.queries.ts @@ -0,0 +1,29 @@ +import { ContentChild, ContentChildren, Query, ViewChild, ViewChildren } from '@angular/core'; + +import { Type } from './core.types'; + +// Looks like an A9 bug, that queries from @Component aren't processed. +// Also we have to pass prototype, not the class. +// The same issue happens with outputs, but time to time +// (when I restart tests with refreshing browser manually). +// https://github.com/ike18t/ng-mocks/issues/109 +export default function (cls: Type, queries?: { [key: string]: Query }) { + /* istanbul ignore else */ + if (queries) { + for (const key of Object.keys(queries)) { + const query: any = queries[key]; + if (query.ngMetadataName === 'ContentChild') { + ContentChild(query.selector, query)(cls.prototype, key); + } + if (query.ngMetadataName === 'ContentChildren') { + ContentChildren(query.selector, query)(cls.prototype, key); + } + if (query.ngMetadataName === 'ViewChild') { + ViewChild(query.selector, query)(cls.prototype, key); + } + if (query.ngMetadataName === 'ViewChildren') { + ViewChildren(query.selector, query)(cls.prototype, key); + } + } + } +} diff --git a/lib/common/decorate.ts b/lib/common/decorate.ts deleted file mode 100644 index af65a5cfc4..0000000000 --- a/lib/common/decorate.ts +++ /dev/null @@ -1,70 +0,0 @@ -// tslint:disable:no-unnecessary-type-assertion - -import { ContentChild, ContentChildren, Input, Output, Query, ViewChild, ViewChildren } from '@angular/core'; - -import { Type } from './lib'; - -// Looks like an A9 bug, that queries from @Component aren't processed. -// Also we have to pass prototype, not the class. -// The same issue happens with outputs, but time to time -// (when I restart tests with refreshing browser manually). -// https://github.com/ike18t/ng-mocks/issues/109 - -/** - * See comment at the beginning of the file. - * - * @internal - */ -export function decorateInputs(cls: Type, inputs?: string[], exclude?: string[]) { - /* istanbul ignore else */ - if (inputs) { - for (const input of inputs) { - const [key, alias] = input.split(': '); - if (exclude && exclude.indexOf(key) !== -1) { - continue; - } - Input(alias)(cls.prototype, key); - } - } -} - -/** - * See comment at the beginning of the file. - * - * @internal - */ -export function decorateOutputs(cls: Type, outputs?: string[]) { - /* istanbul ignore else */ - if (outputs) { - for (const output of outputs) { - const [key, alias] = output.split(': '); - Output(alias)(cls.prototype, key); - } - } -} - -/** - * See comment at the beginning of the file. - * - * @internal - */ -export function decorateQueries(cls: Type, queries?: { [key: string]: Query }) { - /* istanbul ignore else */ - if (queries) { - for (const key of Object.keys(queries)) { - const query: any = queries[key]; - if (query.ngMetadataName === 'ContentChild') { - ContentChild(query.selector, query)(cls.prototype, key); - } - if (query.ngMetadataName === 'ContentChildren') { - ContentChildren(query.selector, query)(cls.prototype, key); - } - if (query.ngMetadataName === 'ViewChild') { - ViewChild(query.selector, query)(cls.prototype, key); - } - if (query.ngMetadataName === 'ViewChildren') { - ViewChildren(query.selector, query)(cls.prototype, key); - } - } - } -} diff --git a/lib/common/func.get-mocked-ng-def-of.ts b/lib/common/func.get-mocked-ng-def-of.ts new file mode 100644 index 0000000000..86fb62b288 --- /dev/null +++ b/lib/common/func.get-mocked-ng-def-of.ts @@ -0,0 +1,76 @@ +import { MockedComponent } from '../mock-component/types'; +import { MockedDirective } from '../mock-directive/types'; +import { MockedModule } from '../mock-module/types'; +import { MockedPipe } from '../mock-pipe/types'; + +import { getTestBedInjection } from './core.helpers'; +import { NG_MOCKS } from './core.tokens'; +import { Type } from './core.types'; +import { isMockedNgDefOf } from './func.is-mocked-ng-def-of'; +import { ngMocksUniverse } from './ng-mocks-universe'; + +/** + * Returns a def of a mocked module based on a mocked module or a source module. + * + * @see https://github.com/ike18t/ng-mocks#getmockedngdefof + */ +export function getMockedNgDefOf(declaration: Type, type: 'm'): Type>; + +/** + * Returns a def of a mocked component based on a mocked component or a source component. + * + * @see https://github.com/ike18t/ng-mocks#getmockedngdefof + */ +export function getMockedNgDefOf(declaration: Type, type: 'c'): Type>; + +/** + * Returns a def of a mocked directive based on a mocked directive or a source directive. + * + * @see https://github.com/ike18t/ng-mocks#getmockedngdefof + */ +export function getMockedNgDefOf(declaration: Type, type: 'd'): Type>; + +/** + * Returns a def of a mocked pipe based on a mocked pipe or a source pipe. + * + * @see https://github.com/ike18t/ng-mocks#getmockedngdefof + */ +export function getMockedNgDefOf(declaration: Type, type: 'p'): Type>; + +/** + * Returns a def of a mocked class based on a mocked class or a source class decorated by a ng type. + * + * @see https://github.com/ike18t/ng-mocks#getmockedngdefof + */ +export function getMockedNgDefOf(declaration: Type): Type; + +export function getMockedNgDefOf(declaration: any, type?: any): any { + const source = declaration.mockOf ? declaration.mockOf : declaration; + const mocks = getTestBedInjection(NG_MOCKS); + + let mock: any; + + // If mocks exists, we are in the MockBuilder env and it's enough for the check. + if (mocks && mocks.has(source)) { + mock = mocks.get(source); + } else if (mocks) { + throw new Error(`There is no mock for ${source.name}`); + } + + // If we are not in the MockBuilder env we can rely on the current cache. + if (!mock && source !== declaration) { + mock = declaration; + } else if (!mock && ngMocksUniverse.cacheMocks.has(source)) { + mock = ngMocksUniverse.cacheMocks.get(source); + } + + if (mock && !type) { + return mock; + } + if (mock && type && isMockedNgDefOf(mock, source, type)) { + return mock; + } + + // Looks like the def hasn't been mocked. + throw new Error(`There is no mock for ${source.name}`); +} diff --git a/lib/common/func.get-source-of-mock.ts b/lib/common/func.get-source-of-mock.ts new file mode 100644 index 0000000000..21005585c4 --- /dev/null +++ b/lib/common/func.get-source-of-mock.ts @@ -0,0 +1,50 @@ +import { MockedComponent } from '../mock-component/types'; +import { MockedDirective } from '../mock-directive/types'; +import { MockedModule } from '../mock-module/types'; +import { MockedPipe } from '../mock-pipe/types'; + +import { Type } from './core.types'; + +/** + * Returns an original type. + * + * @see https://github.com/ike18t/ng-mocks#getsourceofmock + */ +export function getSourceOfMock(declaration: Type>): Type; + +/** + * Returns an original type. + * + * @see https://github.com/ike18t/ng-mocks#getsourceofmock + */ +export function getSourceOfMock(declaration: Type>): Type; + +/** + * Returns an original type. + * + * @see https://github.com/ike18t/ng-mocks#getsourceofmock + */ +export function getSourceOfMock(declaration: Type>): Type; + +/** + * Returns an original type. + * + * @see https://github.com/ike18t/ng-mocks#getsourceofmock + */ +export function getSourceOfMock(declaration: Type>): Type; + +/** + * Returns an original type. + * + * @see https://github.com/ike18t/ng-mocks#getsourceofmock + */ +export function getSourceOfMock(declaration: Type): Type; + +/** + * Returns an original type. + * + * @see https://github.com/ike18t/ng-mocks#getsourceofmock + */ +export function getSourceOfMock(declaration: any): Type { + return typeof declaration === 'function' && declaration.mockOf ? declaration.mockOf : declaration; +} diff --git a/lib/common/func.is-mock-of.ts b/lib/common/func.is-mock-of.ts new file mode 100644 index 0000000000..6e1e1f0cd0 --- /dev/null +++ b/lib/common/func.is-mock-of.ts @@ -0,0 +1,57 @@ +import { PipeTransform } from '@angular/core'; + +import { MockedComponent } from '../mock-component/types'; +import { MockedDirective } from '../mock-directive/types'; +import { MockedModule } from '../mock-module/types'; +import { MockedPipe } from '../mock-pipe/types'; + +import { Type } from './core.types'; +import { isNgDef } from './func.is-ng-def'; + +/** + * Checks whether the instance derives from a mocked module. + * + * @see https://github.com/ike18t/ng-mocks#ismockof + */ +export function isMockOf(instance: any, declaration: Type, ngType: 'm'): instance is MockedModule; + +/** + * Checks whether the instance derives from a mocked component. + * + * @see https://github.com/ike18t/ng-mocks#ismockof + */ +export function isMockOf(instance: any, declaration: Type, ngType: 'c'): instance is MockedComponent; + +/** + * Checks whether the instance derives from a mocked directive. + * + * @see https://github.com/ike18t/ng-mocks#ismockof + */ +export function isMockOf(instance: any, declaration: Type, ngType: 'd'): instance is MockedDirective; + +/** + * Checks whether the instance derives from a mocked pipe. + * + * @see https://github.com/ike18t/ng-mocks#ismockof + */ +export function isMockOf( + instance: any, + declaration: Type, + ngType: 'p' +): instance is MockedPipe; + +/** + * Checks whether the instance derives from a mocked type. + * + * @see https://github.com/ike18t/ng-mocks#ismockof + */ +export function isMockOf(instance: any, declaration: Type): instance is T; + +export function isMockOf(instance: any, declaration: Type, ngType?: any): instance is T { + return ( + typeof instance === 'object' && + instance.__ngMocksMock && + instance.constructor === declaration && + (ngType ? isNgDef(instance.constructor, ngType) : isNgDef(instance.constructor)) + ); +} diff --git a/lib/common/func.is-mocked-ng-def-of.ts b/lib/common/func.is-mocked-ng-def-of.ts new file mode 100644 index 0000000000..ef25694805 --- /dev/null +++ b/lib/common/func.is-mocked-ng-def-of.ts @@ -0,0 +1,62 @@ +import { PipeTransform } from '@angular/core'; + +import { MockedComponent } from '../mock-component/types'; +import { MockedDirective } from '../mock-directive/types'; +import { MockedModule } from '../mock-module/types'; +import { MockedPipe } from '../mock-pipe/types'; + +import { Type } from './core.types'; +import { isNgDef } from './func.is-ng-def'; + +/** + * Checks whether the declaration is a mocked one and derives from the specified module. + * + * @see https://github.com/ike18t/ng-mocks#ismockedngdefof + */ +export function isMockedNgDefOf(declaration: any, type: Type, ngType: 'm'): declaration is Type>; + +/** + * Checks whether the declaration is a mocked one and derives from the specified component. + * + * @see https://github.com/ike18t/ng-mocks#ismockedngdefof + */ +export function isMockedNgDefOf( + declaration: any, + type: Type, + ngType: 'c' +): declaration is Type>; + +/** + * Checks whether the declaration is a mocked one and derives from the specified directive. + * + * @see https://github.com/ike18t/ng-mocks#ismockedngdefof + */ +export function isMockedNgDefOf( + declaration: any, + type: Type, + ngType: 'd' +): declaration is Type>; + +/** + * Checks whether the declaration is a mocked one and derives from the specified pipe. + * + * @see https://github.com/ike18t/ng-mocks#ismockedngdefof + */ +export function isMockedNgDefOf( + declaration: any, + type: Type, + ngType: 'p' +): declaration is Type>; + +/** + * Checks whether the declaration is a mocked one and derives from the specified type. + * + * @see https://github.com/ike18t/ng-mocks#ismockedngdefof + */ +export function isMockedNgDefOf(declaration: any, type: Type): declaration is Type; + +export function isMockedNgDefOf(declaration: any, type: Type, ngType?: any): declaration is Type { + return ( + typeof declaration === 'function' && declaration.mockOf === type && (ngType ? isNgDef(declaration, ngType) : true) + ); +} diff --git a/lib/common/func.is-ng-def.ts b/lib/common/func.is-ng-def.ts new file mode 100644 index 0000000000..9e69ea9934 --- /dev/null +++ b/lib/common/func.is-ng-def.ts @@ -0,0 +1,47 @@ +import { PipeTransform } from '@angular/core'; + +import { Type } from './core.types'; +import { isNgType } from './func.is-ng-type'; + +/** + * Checks whether a class was decorated by @NgModule. + * + * @see https://github.com/ike18t/ng-mocks#isngdef + */ +export function isNgDef(declaration: any, ngType: 'm'): declaration is Type; + +/** + * Checks whether a class was decorated by @Component. + * + * @see https://github.com/ike18t/ng-mocks#isngdef + */ +export function isNgDef(declaration: any, ngType: 'c'): declaration is Type; + +/** + * Checks whether a class was decorated by @Directive. + * + * @see https://github.com/ike18t/ng-mocks#isngdef + */ +export function isNgDef(declaration: any, ngType: 'd'): declaration is Type; + +/** + * Checks whether a class was decorated by @Pipe. + * + * @see https://github.com/ike18t/ng-mocks#isngdef + */ +export function isNgDef(declaration: any, ngType: 'p'): declaration is Type; + +/** + * Checks whether a class was decorated by a ng type. + * + * @see https://github.com/ike18t/ng-mocks#isngdef + */ +export function isNgDef(declaration: any): declaration is Type; + +export function isNgDef(declaration: any, ngType?: string): declaration is Type { + const isModule = (!ngType || ngType === 'm') && isNgType(declaration, 'NgModule'); + const isComponent = (!ngType || ngType === 'c') && isNgType(declaration, 'Component'); + const isDirective = (!ngType || ngType === 'd') && isNgType(declaration, 'Directive'); + const isPipe = (!ngType || ngType === 'p') && isNgType(declaration, 'Pipe'); + return isModule || isComponent || isDirective || isPipe; +} diff --git a/lib/common/func.is-ng-injection-token.ts b/lib/common/func.is-ng-injection-token.ts new file mode 100644 index 0000000000..da06868d27 --- /dev/null +++ b/lib/common/func.is-ng-injection-token.ts @@ -0,0 +1,9 @@ +import { InjectionToken } from '@angular/core'; + +/** + * Checks whether a variable is a real token. + * + * @see https://github.com/ike18t/ng-mocks#isnginjectiontoken + */ +export const isNgInjectionToken = (token: any): token is InjectionToken => + typeof token === 'object' && token.ngMetadataName === 'InjectionToken'; diff --git a/lib/common/func.is-ng-module-def-with-providers.ts b/lib/common/func.is-ng-module-def-with-providers.ts new file mode 100644 index 0000000000..3b13ed18df --- /dev/null +++ b/lib/common/func.is-ng-module-def-with-providers.ts @@ -0,0 +1,15 @@ +import { Provider } from '@angular/core'; + +import { Type } from './core.types'; +import { isNgDef } from './func.is-ng-def'; + +// remove after removal of A5 support +// tslint:disable-next-line:interface-name +export interface NgModuleWithProviders { + ngModule: Type; + providers?: Provider[]; +} + +// Checks if an object implements ModuleWithProviders. +export const isNgModuleDefWithProviders = (declaration: any): declaration is NgModuleWithProviders => + declaration.ngModule !== undefined && isNgDef(declaration.ngModule, 'm'); diff --git a/lib/common/func.is-ng-type.ts b/lib/common/func.is-ng-type.ts new file mode 100644 index 0000000000..84a746e528 --- /dev/null +++ b/lib/common/func.is-ng-type.ts @@ -0,0 +1,5 @@ +import { jitReflector } from './core.reflect'; +import { Type } from './core.types'; + +export const isNgType = (declaration: Type, type: string): boolean => + jitReflector.annotations(declaration).some(annotation => annotation.ngMetadataName === type); diff --git a/lib/common/index.ts b/lib/common/index.ts deleted file mode 100644 index 808b6322d3..0000000000 --- a/lib/common/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './Mock'; -export * from './lib'; -export * from './mock-of.decorator'; diff --git a/lib/common/lib.ts b/lib/common/lib.ts deleted file mode 100644 index 6329dbceac..0000000000 --- a/lib/common/lib.ts +++ /dev/null @@ -1,379 +0,0 @@ -import { InjectionToken, PipeTransform, Provider } from '@angular/core'; -import { getTestBed, MetadataOverride } from '@angular/core/testing'; - -import { MockedComponent } from '../mock-component'; -import { MockedDirective } from '../mock-directive'; -import { MockedModule } from '../mock-module'; -import { MockedPipe } from '../mock-pipe'; - -import { ngMocksUniverse } from './ng-mocks-universe'; -import { jitReflector } from './reflect'; - -// It has to be an interface. -// tslint:disable-next-line:interface-name -export interface AbstractType extends Function { - prototype: T; -} - -// It has to be an interface. -// tslint:disable-next-line:interface-name -export interface Type extends Function { - // tslint:disable-next-line:callable-types - new (...args: any[]): T; -} - -export type AnyType = Type | AbstractType; - -// remove after removal of A5 support -// tslint:disable-next-line:interface-name -export interface NgModuleWithProviders { - ngModule: Type; - providers?: Provider[]; -} - -export const NG_MOCKS = new InjectionToken>('NG_MOCKS'); -export const NG_MOCKS_TOUCHES = new InjectionToken>('NG_MOCKS_TOUCHES'); -export const NG_MOCKS_OVERRIDES = new InjectionToken | AbstractType, MetadataOverride>>( - 'NG_MOCKS_OVERRIDES' -); -export const NG_GUARDS = new InjectionToken('NG_MOCKS_GUARDS'); -export const NG_INTERCEPTORS = new InjectionToken('NG_MOCKS_INTERCEPTORS'); - -/** - * Can be changed any time. - * - * @internal - */ -export const getTestBedInjection = (token: Type | InjectionToken): I | undefined => { - const testBed: any = getTestBed(); - try { - /* istanbul ignore next */ - return testBed.inject ? testBed.inject(token) : testBed.get(token); - } catch (e) { - return undefined; - } -}; - -export const flatten = (values: T | T[], result: T[] = []): T[] => { - if (Array.isArray(values)) { - values.forEach((value: T | T[]) => flatten(value, result)); - } else { - result.push(values); - } - return result; -}; - -export const mapKeys = (set: Map): T[] => { - const result: T[] = []; - set.forEach((_, value: T) => result.push(value)); - return result; -}; - -export const mapValues = (set: { forEach(a1: (value: T) => void): void }): T[] => { - const result: T[] = []; - set.forEach((value: T) => result.push(value)); - return result; -}; - -export const mapEntries = (set: Map): Array<[K, T]> => { - const result: Array<[K, T]> = []; - set.forEach((value: T, key: K) => result.push([key, value])); - return result; -}; - -export const extendClass = (base: Type): Type => { - let child: any; - const parent: any = base; - - // first we try to eval es2015 style and if it fails to use es5 transpilation in the catch block. - (window as any).ngMocksParent = parent; - /* istanbul ignore next */ - try { - // tslint:disable-next-line:no-eval - eval(` - class child extends window.ngMocksParent { - } - window.ngMocksResult = child - `); - child = (window as any).ngMocksResult; - } catch (e) { - class ClassEs5 extends parent {} - - child = ClassEs5; - } - (window as any).ngMocksParent = undefined; - - // the next step is to respect constructor parameters as the parent class. - child.parameters = jitReflector.parameters(parent); - - return child; -}; - -export const isNgType = (declaration: Type, type: string): boolean => - jitReflector.annotations(declaration).some(annotation => annotation.ngMetadataName === type); - -/** - * Checks whether a class was decorated by @NgModule. - * - * @see https://github.com/ike18t/ng-mocks#isngdef - */ -export function isNgDef(declaration: any, ngType: 'm'): declaration is Type; - -/** - * Checks whether a class was decorated by @Component. - * - * @see https://github.com/ike18t/ng-mocks#isngdef - */ -export function isNgDef(declaration: any, ngType: 'c'): declaration is Type; - -/** - * Checks whether a class was decorated by @Directive. - * - * @see https://github.com/ike18t/ng-mocks#isngdef - */ -export function isNgDef(declaration: any, ngType: 'd'): declaration is Type; - -/** - * Checks whether a class was decorated by @Pipe. - * - * @see https://github.com/ike18t/ng-mocks#isngdef - */ -export function isNgDef(declaration: any, ngType: 'p'): declaration is Type; - -/** - * Checks whether a class was decorated by a ng type. - * - * @see https://github.com/ike18t/ng-mocks#isngdef - */ -export function isNgDef(declaration: any): declaration is Type; - -export function isNgDef(declaration: any, ngType?: string): declaration is Type { - const isModule = (!ngType || ngType === 'm') && isNgType(declaration, 'NgModule'); - const isComponent = (!ngType || ngType === 'c') && isNgType(declaration, 'Component'); - const isDirective = (!ngType || ngType === 'd') && isNgType(declaration, 'Directive'); - const isPipe = (!ngType || ngType === 'p') && isNgType(declaration, 'Pipe'); - return isModule || isComponent || isDirective || isPipe; -} - -/** - * Checks whether the declaration is a mocked one and derives from the specified module. - * - * @see https://github.com/ike18t/ng-mocks#ismockedngdefof - */ -export function isMockedNgDefOf(declaration: any, type: Type, ngType: 'm'): declaration is Type>; - -/** - * Checks whether the declaration is a mocked one and derives from the specified component. - * - * @see https://github.com/ike18t/ng-mocks#ismockedngdefof - */ -export function isMockedNgDefOf( - declaration: any, - type: Type, - ngType: 'c' -): declaration is Type>; - -/** - * Checks whether the declaration is a mocked one and derives from the specified directive. - * - * @see https://github.com/ike18t/ng-mocks#ismockedngdefof - */ -export function isMockedNgDefOf( - declaration: any, - type: Type, - ngType: 'd' -): declaration is Type>; - -/** - * Checks whether the declaration is a mocked one and derives from the specified pipe. - * - * @see https://github.com/ike18t/ng-mocks#ismockedngdefof - */ -export function isMockedNgDefOf( - declaration: any, - type: Type, - ngType: 'p' -): declaration is Type>; - -/** - * Checks whether the declaration is a mocked one and derives from the specified type. - * - * @see https://github.com/ike18t/ng-mocks#ismockedngdefof - */ -export function isMockedNgDefOf(declaration: any, type: Type): declaration is Type; - -export function isMockedNgDefOf(declaration: any, type: Type, ngType?: any): declaration is Type { - return ( - typeof declaration === 'function' && declaration.mockOf === type && (ngType ? isNgDef(declaration, ngType) : true) - ); -} - -/** - * Checks whether a variable is a real token. - * - * @see https://github.com/ike18t/ng-mocks#isnginjectiontoken - */ -export const isNgInjectionToken = (token: any): token is InjectionToken => - typeof token === 'object' && token.ngMetadataName === 'InjectionToken'; - -// Checks if an object implements ModuleWithProviders. -export const isNgModuleDefWithProviders = (declaration: any): declaration is NgModuleWithProviders => - declaration.ngModule !== undefined && isNgDef(declaration.ngModule, 'm'); - -/** - * Checks whether the instance derives from a mocked module. - * - * @see https://github.com/ike18t/ng-mocks#ismockof - */ -export function isMockOf(instance: any, declaration: Type, ngType: 'm'): instance is MockedModule; - -/** - * Checks whether the instance derives from a mocked component. - * - * @see https://github.com/ike18t/ng-mocks#ismockof - */ -export function isMockOf(instance: any, declaration: Type, ngType: 'c'): instance is MockedComponent; - -/** - * Checks whether the instance derives from a mocked directive. - * - * @see https://github.com/ike18t/ng-mocks#ismockof - */ -export function isMockOf(instance: any, declaration: Type, ngType: 'd'): instance is MockedDirective; - -/** - * Checks whether the instance derives from a mocked pipe. - * - * @see https://github.com/ike18t/ng-mocks#ismockof - */ -export function isMockOf( - instance: any, - declaration: Type, - ngType: 'p' -): instance is MockedPipe; - -/** - * Checks whether the instance derives from a mocked type. - * - * @see https://github.com/ike18t/ng-mocks#ismockof - */ -export function isMockOf(instance: any, declaration: Type): instance is T; - -export function isMockOf(instance: any, declaration: Type, ngType?: any): instance is T { - return ( - typeof instance === 'object' && - instance.__ngMocksMock && - instance.constructor === declaration && - (ngType ? isNgDef(instance.constructor, ngType) : isNgDef(instance.constructor)) - ); -} - -/** - * Returns a def of a mocked module based on a mocked module or a source module. - * - * @see https://github.com/ike18t/ng-mocks#getmockedngdefof - */ -export function getMockedNgDefOf(declaration: Type, type: 'm'): Type>; - -/** - * Returns a def of a mocked component based on a mocked component or a source component. - * - * @see https://github.com/ike18t/ng-mocks#getmockedngdefof - */ -export function getMockedNgDefOf(declaration: Type, type: 'c'): Type>; - -/** - * Returns a def of a mocked directive based on a mocked directive or a source directive. - * - * @see https://github.com/ike18t/ng-mocks#getmockedngdefof - */ -export function getMockedNgDefOf(declaration: Type, type: 'd'): Type>; - -/** - * Returns a def of a mocked pipe based on a mocked pipe or a source pipe. - * - * @see https://github.com/ike18t/ng-mocks#getmockedngdefof - */ -export function getMockedNgDefOf(declaration: Type, type: 'p'): Type>; - -/** - * Returns a def of a mocked class based on a mocked class or a source class decorated by a ng type. - * - * @see https://github.com/ike18t/ng-mocks#getmockedngdefof - */ -export function getMockedNgDefOf(declaration: Type): Type; - -export function getMockedNgDefOf(declaration: any, type?: any): any { - const source = declaration.mockOf ? declaration.mockOf : declaration; - const mocks = getTestBedInjection(NG_MOCKS); - - let mock: any; - - // If mocks exists, we are in the MockBuilder env and it's enough for the check. - if (mocks && mocks.has(source)) { - mock = mocks.get(source); - } else if (mocks) { - throw new Error(`There is no mock for ${source.name}`); - } - - // If we are not in the MockBuilder env we can rely on the current cache. - if (!mock && source !== declaration) { - mock = declaration; - } else if (!mock && ngMocksUniverse.cacheMocks.has(source)) { - mock = ngMocksUniverse.cacheMocks.get(source); - } - - if (mock && !type) { - return mock; - } - if (mock && type && isMockedNgDefOf(mock, source, type)) { - return mock; - } - - // Looks like the def hasn't been mocked. - throw new Error(`There is no mock for ${source.name}`); -} - -/** - * Returns an original type. - * - * @see https://github.com/ike18t/ng-mocks#getsourceofmock - */ -export function getSourceOfMock(declaration: Type>): Type; - -/** - * Returns an original type. - * - * @see https://github.com/ike18t/ng-mocks#getsourceofmock - */ -export function getSourceOfMock(declaration: Type>): Type; - -/** - * Returns an original type. - * - * @see https://github.com/ike18t/ng-mocks#getsourceofmock - */ -export function getSourceOfMock(declaration: Type>): Type; - -/** - * Returns an original type. - * - * @see https://github.com/ike18t/ng-mocks#getsourceofmock - */ -export function getSourceOfMock(declaration: Type>): Type; - -/** - * Returns an original type. - * - * @see https://github.com/ike18t/ng-mocks#getsourceofmock - */ -export function getSourceOfMock(declaration: Type): Type; - -/** - * Returns an original type. - * - * @see https://github.com/ike18t/ng-mocks#getsourceofmock - */ -export function getSourceOfMock(declaration: any): Type { - return typeof declaration === 'function' && declaration.mockOf ? declaration.mockOf : declaration; -} diff --git a/lib/common/mock-control-value-accessor.ts b/lib/common/mock-control-value-accessor.ts new file mode 100644 index 0000000000..df64deb2d3 --- /dev/null +++ b/lib/common/mock-control-value-accessor.ts @@ -0,0 +1,36 @@ +// tslint:disable:variable-name + +import { AbstractControl, ControlValueAccessor, ValidationErrors, Validator } from '@angular/forms'; + +import { Mock } from './mock'; + +export class MockControlValueAccessor extends Mock implements ControlValueAccessor, Validator { + public readonly __ngMocksMockControlValueAccessor: true = true; + + /* istanbul ignore next */ + __simulateChange = (value: any) => {}; + + /* istanbul ignore next */ + __simulateTouch = () => {}; + + /* istanbul ignore next */ + __simulateValidatorChange = () => {}; + + registerOnChange(fn: (value: any) => void): void { + this.__simulateChange = fn; + } + + registerOnTouched(fn: () => void): void { + this.__simulateTouch = fn; + } + + registerOnValidatorChange(fn: () => void): void { + this.__simulateValidatorChange = fn; + } + + setDisabledState = (isDisabled: boolean): void => {}; + + validate = (control: AbstractControl): ValidationErrors | null => null; + + writeValue = (value: any) => {}; +} diff --git a/lib/common/mock-of.decorator.spec.ts b/lib/common/mock-of.spec.ts similarity index 88% rename from lib/common/mock-of.decorator.spec.ts rename to lib/common/mock-of.spec.ts index 180bdd6c4b..8ae930313b 100644 --- a/lib/common/mock-of.decorator.spec.ts +++ b/lib/common/mock-of.spec.ts @@ -1,4 +1,4 @@ -import { MockOf } from './mock-of.decorator'; +import { MockOf } from './mock-of'; describe('DebuggableMock', () => { it('prefixes the class name with MockOf', () => { diff --git a/lib/common/mock-of.decorator.ts b/lib/common/mock-of.ts similarity index 91% rename from lib/common/mock-of.decorator.ts rename to lib/common/mock-of.ts index 20eb1e0325..93ccdbc689 100644 --- a/lib/common/mock-of.decorator.ts +++ b/lib/common/mock-of.ts @@ -1,5 +1,5 @@ -import { Type } from './lib'; -import { ngMocksMockConfig } from './Mock'; +import { Type } from './core.types'; +import { ngMocksMockConfig } from './mock'; // This helps with debugging in the browser. Decorating mock classes with this // will change the display-name of the class to 'MockOf-` so our diff --git a/lib/common/Mock.spec.ts b/lib/common/mock.spec.ts similarity index 95% rename from lib/common/Mock.spec.ts rename to lib/common/mock.spec.ts index 2cada90289..426fdfee8d 100644 --- a/lib/common/Mock.spec.ts +++ b/lib/common/mock.spec.ts @@ -1,14 +1,14 @@ import { Component, Directive, NgModule, Pipe, PipeTransform } from '@angular/core'; import { ControlValueAccessor } from '@angular/forms'; -import { MockComponent } from '../mock-component'; -import { MockDirective } from '../mock-directive'; -import { MockModule } from '../mock-module'; -import { MockPipe } from '../mock-pipe'; - -import { Type } from './lib'; -import { Mock } from './Mock'; -import { MockOf } from './mock-of.decorator'; +import { MockComponent } from '../mock-component/mock-component'; +import { MockDirective } from '../mock-directive/mock-directive'; +import { MockModule } from '../mock-module/mock-module'; +import { MockPipe } from '../mock-pipe/mock-pipe'; + +import { Type } from './core.types'; +import { Mock } from './mock'; +import { MockOf } from './mock-of'; class ParentClass { protected parentValue = true; diff --git a/lib/common/Mock.ts b/lib/common/mock.ts similarity index 74% rename from lib/common/Mock.ts rename to lib/common/mock.ts index f234643300..c1ac67772b 100644 --- a/lib/common/Mock.ts +++ b/lib/common/mock.ts @@ -1,9 +1,9 @@ // tslint:disable: no-bitwise variable-name interface-over-type-literal import { EventEmitter, Injector, Optional } from '@angular/core'; -import { AbstractControl, ControlValueAccessor, NgControl, ValidationErrors, Validator } from '@angular/forms'; +import { NgControl } from '@angular/forms'; -import { mockServiceHelper } from '../mock-service'; +import mockServiceHelper from '../mock-service/helper'; import { ngMocksUniverse } from './ng-mocks-universe'; @@ -88,34 +88,3 @@ export class Mock { } } } - -export class MockControlValueAccessor extends Mock implements ControlValueAccessor, Validator { - public readonly __ngMocksMockControlValueAccessor: true = true; - - /* istanbul ignore next */ - __simulateChange = (value: any) => {}; - - /* istanbul ignore next */ - __simulateTouch = () => {}; - - /* istanbul ignore next */ - __simulateValidatorChange = () => {}; - - registerOnChange(fn: (value: any) => void): void { - this.__simulateChange = fn; - } - - registerOnTouched(fn: () => void): void { - this.__simulateTouch = fn; - } - - registerOnValidatorChange(fn: () => void): void { - this.__simulateValidatorChange = fn; - } - - setDisabledState = (isDisabled: boolean): void => {}; - - validate = (control: AbstractControl): ValidationErrors | null => null; - - writeValue = (value: any) => {}; -} diff --git a/lib/common/ng-mocks-universe.ts b/lib/common/ng-mocks-universe.ts index 034c3b3063..749b03db56 100644 --- a/lib/common/ng-mocks-universe.ts +++ b/lib/common/ng-mocks-universe.ts @@ -1,6 +1,6 @@ import { InjectionToken } from '@angular/core'; -import { AnyType } from './lib'; +import { AnyType } from './core.types'; /** * Can be changed any time. diff --git a/lib/mock-builder/index.ts b/lib/mock-builder/index.ts deleted file mode 100644 index babe587833..0000000000 --- a/lib/mock-builder/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './mock-builder'; diff --git a/lib/mock-builder/mock-builder-performance.spec.ts b/lib/mock-builder/mock-builder-performance.spec.ts index c6ea4ff73a..5972edfbe2 100644 --- a/lib/mock-builder/mock-builder-performance.spec.ts +++ b/lib/mock-builder/mock-builder-performance.spec.ts @@ -1,6 +1,6 @@ import { Component, Directive, Injectable, InjectionToken, NgModule } from '@angular/core'; -import { ngMocks } from '../mock-helper'; +import { ngMocks } from '../mock-helper/mock-helper'; import { MockBuilder } from './mock-builder'; diff --git a/lib/mock-builder/mock-builder-performance.ts b/lib/mock-builder/mock-builder-performance.ts index 83b0fb04f0..cb8c6202ed 100644 --- a/lib/mock-builder/mock-builder-performance.ts +++ b/lib/mock-builder/mock-builder-performance.ts @@ -1,10 +1,11 @@ import { NgModule } from '@angular/core'; import { TestBed, TestModuleMetadata } from '@angular/core/testing'; -import { flatten, mapEntries, mapKeys, mapValues } from '../common'; +import { flatten, mapEntries, mapKeys, mapValues } from '../common/core.helpers'; import { ngMocksUniverse } from '../common/ng-mocks-universe'; -import { IMockBuilderResult, MockBuilderPromise } from './mock-builder-promise'; +import { MockBuilderPromise } from './mock-builder-promise'; +import { IMockBuilderResult } from './types'; export class MockBuilderPerformance extends MockBuilderPromise { public build(): NgModule { diff --git a/lib/mock-builder/mock-builder-promise.spec.ts b/lib/mock-builder/mock-builder-promise.spec.ts index fa93c21216..f7c80d0f73 100644 --- a/lib/mock-builder/mock-builder-promise.spec.ts +++ b/lib/mock-builder/mock-builder-promise.spec.ts @@ -2,7 +2,7 @@ import { Component, Directive, Injectable, InjectionToken, NgModule, Pipe, PipeTransform } from '@angular/core'; -import { getTestBedInjection } from '../common/lib'; +import { getTestBedInjection } from '../common/core.helpers'; import { MockBuilder } from './mock-builder'; diff --git a/lib/mock-builder/mock-builder-promise.ts b/lib/mock-builder/mock-builder-promise.ts index 27fa1de0cf..c252f093e8 100644 --- a/lib/mock-builder/mock-builder-promise.ts +++ b/lib/mock-builder/mock-builder-promise.ts @@ -3,65 +3,22 @@ import { InjectionToken, NgModule, PipeTransform, Provider } from '@angular/core import { MetadataOverride, TestBed } from '@angular/core/testing'; import { EVENT_MANAGER_PLUGINS } from '@angular/platform-browser'; -import { - AnyType, - flatten, - isNgDef, - isNgInjectionToken, - isNgModuleDefWithProviders, - mapEntries, - mapValues, - NgModuleWithProviders, - NG_MOCKS, - NG_MOCKS_OVERRIDES, - NG_MOCKS_TOUCHES, - Type, -} from '../common/lib'; +import { flatten, mapEntries, mapValues } from '../common/core.helpers'; +import { directiveResolver, jitReflector, ngModuleResolver } from '../common/core.reflect'; +import { NG_MOCKS, NG_MOCKS_OVERRIDES, NG_MOCKS_TOUCHES } from '../common/core.tokens'; +import { AnyType, Type } from '../common/core.types'; +import { isNgDef } from '../common/func.is-ng-def'; +import { isNgInjectionToken } from '../common/func.is-ng-injection-token'; +import { isNgModuleDefWithProviders, NgModuleWithProviders } from '../common/func.is-ng-module-def-with-providers'; import { ngMocksUniverse } from '../common/ng-mocks-universe'; -import { directiveResolver, jitReflector, ngModuleResolver } from '../common/reflect'; import { MockComponent } from '../mock-component/mock-component'; import { MockDirective } from '../mock-directive/mock-directive'; -import { MockModule, MockNgDef, MockProvider } from '../mock-module/mock-module'; +import { MockModule, MockNgDef } from '../mock-module/mock-module'; import { MockPipe } from '../mock-pipe/mock-pipe'; -import { mockServiceHelper } from '../mock-service/mock-service'; +import mockServiceHelper from '../mock-service/helper'; +import MockProvider from '../mock-service/mock-provider'; -export interface IMockBuilderResult { - testBed: typeof TestBed; -} -export interface IMockBuilderConfigAll { - dependency?: boolean; // won't be added to TestBedModule. - export?: boolean; // will be forced for export in its module. -} - -export interface IMockBuilderConfigModule { - exportAll?: boolean; // exports all declarations and imports. -} - -export interface IMockBuilderConfigComponent { - render?: { - [blockName: string]: - | boolean - | { - $implicit?: any; - variables?: { [key: string]: any }; - }; - }; -} - -export interface IMockBuilderConfigDirective { - render?: - | boolean - | { - $implicit?: any; - variables?: { [key: string]: any }; - }; -} - -export type IMockBuilderConfig = - | IMockBuilderConfigAll - | IMockBuilderConfigModule - | IMockBuilderConfigComponent - | IMockBuilderConfigDirective; +import { IMockBuilderConfig, IMockBuilderResult } from './types'; const defaultMock = {}; // simulating Symbol diff --git a/lib/mock-builder/mock-builder.ts b/lib/mock-builder/mock-builder.ts index 3f195de271..b7ededf082 100644 --- a/lib/mock-builder/mock-builder.ts +++ b/lib/mock-builder/mock-builder.ts @@ -1,12 +1,15 @@ import { InjectionToken } from '@angular/core'; import { MetadataOverride, TestBed, TestModuleMetadata } from '@angular/core/testing'; -import { AnyType, flatten, isNgDef, mapEntries, NG_MOCKS, NG_MOCKS_OVERRIDES } from '../common/lib'; +import { flatten, mapEntries } from '../common/core.helpers'; +import { NG_MOCKS, NG_MOCKS_OVERRIDES } from '../common/core.tokens'; +import { AnyType } from '../common/core.types'; +import { isNgDef } from '../common/func.is-ng-def'; import { ngMocksUniverse } from '../common/ng-mocks-universe'; import { ngMocks } from '../mock-helper/mock-helper'; import { MockBuilderPerformance } from './mock-builder-performance'; -import { MockBuilderPromise } from './mock-builder-promise'; +import { IMockBuilder } from './types'; /** * @see https://github.com/ike18t/ng-mocks#mockbuilder @@ -14,7 +17,7 @@ import { MockBuilderPromise } from './mock-builder-promise'; export function MockBuilder( keepDeclaration?: AnyType | InjectionToken | null | undefined, itsModuleToMock?: AnyType | null | undefined -): MockBuilderPromise { +): IMockBuilder { if (!(TestBed as any).ngMocks) { const configureTestingModule = TestBed.configureTestingModule; TestBed.configureTestingModule = (moduleDef: TestModuleMetadata) => { diff --git a/lib/mock-builder/types.ts b/lib/mock-builder/types.ts new file mode 100644 index 0000000000..a927144dee --- /dev/null +++ b/lib/mock-builder/types.ts @@ -0,0 +1,96 @@ +// tslint:disable:interface-name + +import { InjectionToken, NgModule, PipeTransform, Provider } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; + +import { AnyType, Type } from '../common/core.types'; +import { NgModuleWithProviders } from '../common/func.is-ng-module-def-with-providers'; + +export interface IMockBuilderResult { + testBed: typeof TestBed; +} +export interface IMockBuilderConfigAll { + dependency?: boolean; // won't be added to TestBedModule. + export?: boolean; // will be forced for export in its module. +} + +export interface IMockBuilderConfigModule { + exportAll?: boolean; // exports all declarations and imports. +} + +export interface IMockBuilderConfigComponent { + render?: { + [blockName: string]: + | boolean + | { + $implicit?: any; + variables?: { [key: string]: any }; + }; + }; +} + +export interface IMockBuilderConfigDirective { + render?: + | boolean + | { + $implicit?: any; + variables?: { [key: string]: any }; + }; +} + +export type IMockBuilderConfig = + | IMockBuilderConfigAll + | IMockBuilderConfigModule + | IMockBuilderConfigComponent + | IMockBuilderConfigDirective; + +export interface IMockBuilder { + beforeCompileComponents(callback: (testBed: typeof TestBed) => void): this; + + build(): NgModule; + + exclude(def: any): this; + + keep(def: NgModuleWithProviders, config?: IMockBuilderConfig): this; + + keep(token: InjectionToken, config?: IMockBuilderConfig): this; + + keep(def: AnyType, config?: IMockBuilderConfig): this; + + keep(def: any, config?: IMockBuilderConfig): this; + + keep(input: any, config?: IMockBuilderConfig): this; + + mock(pipe: AnyType, config?: IMockBuilderConfig): this; + + mock(pipe: AnyType, mock?: PipeTransform['transform'], config?: IMockBuilderConfig): this; + + mock(token: InjectionToken, mock: any, config: IMockBuilderConfig): this; + + mock(provider: AnyType, mock: AnyType, config: IMockBuilderConfig): this; + + mock(provider: AnyType, mock: Partial, config: IMockBuilderConfig): this; + + mock(provider: AnyType, mock: AnyType, config: IMockBuilderConfig): this; + + mock(token: InjectionToken, mock?: any): this; + + mock(def: NgModuleWithProviders): this; + + mock(def: AnyType, config: IMockBuilderConfig): this; + + mock(provider: AnyType, mock?: Partial): this; + + mock(def: AnyType): this; + + mock(input: any, a1: any, a2?: any): this; + + provide(def: Provider): this; + + replace(source: Type, destination: Type, config?: IMockBuilderConfig): this; + + then( + fulfill?: (value: IMockBuilderResult) => PromiseLike, + reject?: (reason: any) => PromiseLike + ): PromiseLike; +} diff --git a/lib/mock-component/index.ts b/lib/mock-component/index.ts deleted file mode 100644 index e57afc6175..0000000000 --- a/lib/mock-component/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './mock-component'; diff --git a/lib/mock-component/mock-component.spec.ts b/lib/mock-component/mock-component.spec.ts index 87832ac263..e1d0bc1a56 100644 --- a/lib/mock-component/mock-component.spec.ts +++ b/lib/mock-component/mock-component.spec.ts @@ -13,18 +13,19 @@ import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; import { staticTrue } from '../../tests'; -import { isMockOf } from '../common/lib'; -import { MockedDirective } from '../mock-directive'; -import { ngMocks } from '../mock-helper'; -import { MockRender } from '../mock-render'; +import { isMockOf } from '../common/func.is-mock-of'; +import { MockedDirective } from '../mock-directive/types'; +import { ngMocks } from '../mock-helper/mock-helper'; +import { MockRender } from '../mock-render/mock-render'; -import { MockComponent, MockComponents, MockedComponent } from './mock-component'; +import { MockComponent, MockComponents } from './mock-component'; import { ChildComponent } from './mock-component.spec.child-component.fixtures'; import { CustomFormControlComponent } from './mock-component.spec.custom-form-control.component.fixtures'; import { EmptyComponent } from './mock-component.spec.empty-component.component.fixtures'; import { GetterSetterComponent } from './mock-component.spec.getter-setter.component.fixtures'; import { SimpleComponent } from './mock-component.spec.simple-component.component.fixtures'; import { TemplateOutletComponent } from './mock-component.spec.template-outlet.component.fixtures'; +import { MockedComponent } from './types'; @Component({ selector: 'example-component-container', diff --git a/lib/mock-component/mock-component.ts b/lib/mock-component/mock-component.ts index cee4ba5816..4fdb4419c2 100644 --- a/lib/mock-component/mock-component.ts +++ b/lib/mock-component/mock-component.ts @@ -14,20 +14,19 @@ import { import { getTestBed } from '@angular/core/testing'; import { NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { AnyType, flatten, getMockedNgDefOf, MockControlValueAccessor, MockOf, Type } from '../common'; -import { decorateInputs, decorateOutputs, decorateQueries } from '../common/decorate'; +import { flatten } from '../common/core.helpers'; +import { directiveResolver } from '../common/core.reflect'; +import { AnyType, Type } from '../common/core.types'; +import decorateInputs from '../common/decorate.inputs'; +import decorateOutputs from '../common/decorate.outputs'; +import decorateQueries from '../common/decorate.queries'; +import { getMockedNgDefOf } from '../common/func.get-mocked-ng-def-of'; +import { MockControlValueAccessor } from '../common/mock-control-value-accessor'; +import { MockOf } from '../common/mock-of'; import { ngMocksUniverse } from '../common/ng-mocks-universe'; -import { directiveResolver } from '../common/reflect'; -import { mockServiceHelper } from '../mock-service/mock-service'; +import mockServiceHelper from '../mock-service/helper'; -export type MockedComponent = T & - MockControlValueAccessor & { - /** Helper function to hide rendered @ContentChild() template. */ - __hide(contentChildSelector: string): void; - - /** Helper function to render any @ContentChild() template with any context. */ - __render(contentChildSelector: string, $implicit?: any, variables?: { [key: string]: any }): void; - }; +import { MockedComponent } from './types'; export function MockComponents(...components: Array>): Array>> { return components.map(component => MockComponent(component, undefined)); diff --git a/lib/mock-component/types.ts b/lib/mock-component/types.ts new file mode 100644 index 0000000000..ffc130fc85 --- /dev/null +++ b/lib/mock-component/types.ts @@ -0,0 +1,10 @@ +import { MockControlValueAccessor } from '../common/mock-control-value-accessor'; + +export type MockedComponent = T & + MockControlValueAccessor & { + /** Helper function to hide rendered @ContentChild() template. */ + __hide(contentChildSelector: string): void; + + /** Helper function to render any @ContentChild() template with any context. */ + __render(contentChildSelector: string, $implicit?: any, variables?: { [key: string]: any }): void; + }; diff --git a/lib/mock-declaration/index.ts b/lib/mock-declaration/index.ts deleted file mode 100644 index 14a163c24f..0000000000 --- a/lib/mock-declaration/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './mock-declaration'; diff --git a/lib/mock-declaration/mock-declaration.ts b/lib/mock-declaration/mock-declaration.ts index 76ceba6e9a..a712feb02b 100644 --- a/lib/mock-declaration/mock-declaration.ts +++ b/lib/mock-declaration/mock-declaration.ts @@ -1,7 +1,11 @@ -import { AnyType, isNgDef, Type } from '../common'; -import { MockComponent, MockedComponent } from '../mock-component'; -import { MockDirective, MockedDirective } from '../mock-directive'; -import { MockedPipe, MockPipe } from '../mock-pipe'; +import { AnyType, Type } from '../common/core.types'; +import { isNgDef } from '../common/func.is-ng-def'; +import { MockComponent } from '../mock-component/mock-component'; +import { MockedComponent } from '../mock-component/types'; +import { MockDirective } from '../mock-directive/mock-directive'; +import { MockedDirective } from '../mock-directive/types'; +import { MockPipe } from '../mock-pipe/mock-pipe'; +import { MockedPipe } from '../mock-pipe/types'; export function MockDeclarations(...declarations: Array>): Array> { return declarations.map(MockDeclaration); diff --git a/lib/mock-directive/index.ts b/lib/mock-directive/index.ts deleted file mode 100644 index 806820e785..0000000000 --- a/lib/mock-directive/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './mock-directive'; diff --git a/lib/mock-directive/mock-directive.spec.ts b/lib/mock-directive/mock-directive.spec.ts index cada72fd80..3016f72e95 100644 --- a/lib/mock-directive/mock-directive.spec.ts +++ b/lib/mock-directive/mock-directive.spec.ts @@ -17,12 +17,13 @@ import { FormControl, FormControlDirective } from '@angular/forms'; import { By } from '@angular/platform-browser'; import { staticFalse } from '../../tests'; -import { isMockedNgDefOf } from '../common/lib'; -import { MockBuilder } from '../mock-builder'; -import { ngMocks } from '../mock-helper'; -import { MockRender } from '../mock-render'; +import { isMockedNgDefOf } from '../common/func.is-mocked-ng-def-of'; +import { MockBuilder } from '../mock-builder/mock-builder'; +import { ngMocks } from '../mock-helper/mock-helper'; +import { MockRender } from '../mock-render/mock-render'; -import { MockDirective, MockDirectives, MockedDirective } from './mock-directive'; +import { MockDirective, MockDirectives } from './mock-directive'; +import { MockedDirective } from './types'; @Injectable() class TargetService {} @@ -74,9 +75,7 @@ class GettersAndSettersDirective { template: `
-
- hi -
+
hi
`, diff --git a/lib/mock-directive/mock-directive.ts b/lib/mock-directive/mock-directive.ts index 32ae0078cd..4b4c46fea0 100644 --- a/lib/mock-directive/mock-directive.ts +++ b/lib/mock-directive/mock-directive.ts @@ -13,29 +13,19 @@ import { import { getTestBed } from '@angular/core/testing'; import { NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { AnyType, flatten, getMockedNgDefOf, MockControlValueAccessor, MockOf, Type } from '../common'; -import { decorateInputs, decorateOutputs, decorateQueries } from '../common/decorate'; +import { flatten } from '../common/core.helpers'; +import { directiveResolver } from '../common/core.reflect'; +import { AnyType, Type } from '../common/core.types'; +import decorateInputs from '../common/decorate.inputs'; +import decorateOutputs from '../common/decorate.outputs'; +import decorateQueries from '../common/decorate.queries'; +import { getMockedNgDefOf } from '../common/func.get-mocked-ng-def-of'; +import { MockControlValueAccessor } from '../common/mock-control-value-accessor'; +import { MockOf } from '../common/mock-of'; import { ngMocksUniverse } from '../common/ng-mocks-universe'; -import { directiveResolver } from '../common/reflect'; -import { mockServiceHelper } from '../mock-service/mock-service'; +import mockServiceHelper from '../mock-service/helper'; -export type MockedDirective = T & - MockControlValueAccessor & { - /** Pointer to current element in case of Attribute Directives. */ - __element?: ElementRef; - - /** Just a flag for easy understanding what it is. */ - __isStructural: boolean; - - /** Pointer to the template of Structural Directives. */ - __template?: TemplateRef; - - /** Pointer to the view of Structural Directives. */ - __viewContainer?: ViewContainerRef; - - /** Helper function to render any Structural Directive with any context. */ - __render($implicit?: any, variables?: { [key: string]: any }): void; - }; +import { MockedDirective } from './types'; export function MockDirectives(...directives: Array>): Array>> { return directives.map(MockDirective); diff --git a/lib/mock-directive/types.ts b/lib/mock-directive/types.ts new file mode 100644 index 0000000000..00021aa0f0 --- /dev/null +++ b/lib/mock-directive/types.ts @@ -0,0 +1,21 @@ +import { ElementRef, TemplateRef, ViewContainerRef } from '@angular/core'; + +import { MockControlValueAccessor } from '../common/mock-control-value-accessor'; + +export type MockedDirective = T & + MockControlValueAccessor & { + /** Pointer to current element in case of Attribute Directives. */ + __element?: ElementRef; + + /** Just a flag for easy understanding what it is. */ + __isStructural: boolean; + + /** Pointer to the template of Structural Directives. */ + __template?: TemplateRef; + + /** Pointer to the view of Structural Directives. */ + __viewContainer?: ViewContainerRef; + + /** Helper function to render any Structural Directive with any context. */ + __render($implicit?: any, variables?: { [key: string]: any }): void; + }; diff --git a/lib/mock-helper/index.ts b/lib/mock-helper/index.ts deleted file mode 100644 index 7cfd6aa59e..0000000000 --- a/lib/mock-helper/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './mock-helper'; diff --git a/lib/mock-helper/mock-helper.faster.ts b/lib/mock-helper/mock-helper.faster.ts index a9a6e2dbed..0c0cd08e26 100644 --- a/lib/mock-helper/mock-helper.faster.ts +++ b/lib/mock-helper/mock-helper.faster.ts @@ -1,5 +1,3 @@ -// tslint:disable:no-default-export no-default-import - import { getTestBed, TestBed } from '@angular/core/testing'; import { ngMocksUniverse } from '../common/ng-mocks-universe'; diff --git a/lib/mock-helper/mock-helper.find.ts b/lib/mock-helper/mock-helper.find.ts index a9782d52e4..f6ed64c64f 100644 --- a/lib/mock-helper/mock-helper.find.ts +++ b/lib/mock-helper/mock-helper.find.ts @@ -1,9 +1,8 @@ -// tslint:disable:no-default-export - import { By } from '@angular/platform-browser'; -import { getSourceOfMock, Type } from '../common'; -import { MockedDebugElement } from '../mock-render'; +import { Type } from '../common/core.types'; +import { getSourceOfMock } from '../common/func.get-source-of-mock'; +import { MockedDebugElement } from '../mock-render/types'; const defaultNotFoundValue = {}; // simulating Symbol diff --git a/lib/mock-helper/mock-helper.findAll.ts b/lib/mock-helper/mock-helper.findAll.ts index 71b0223a32..0654a14280 100644 --- a/lib/mock-helper/mock-helper.findAll.ts +++ b/lib/mock-helper/mock-helper.findAll.ts @@ -1,8 +1,6 @@ -// tslint:disable:no-default-export - import { By } from '@angular/platform-browser'; -import { getSourceOfMock } from '../common'; +import { getSourceOfMock } from '../common/func.get-source-of-mock'; export default (el: any, sel: any) => { const term = typeof sel === 'string' ? By.css(sel) : By.directive(getSourceOfMock(sel)); diff --git a/lib/mock-helper/mock-helper.findInstance.ts b/lib/mock-helper/mock-helper.findInstance.ts index 9bcb03dcca..4a761f1eab 100644 --- a/lib/mock-helper/mock-helper.findInstance.ts +++ b/lib/mock-helper/mock-helper.findInstance.ts @@ -1,7 +1,6 @@ -// tslint:disable:no-default-export no-default-import - -import { getSourceOfMock, Type } from '../common'; -import { MockedDebugElement } from '../mock-render'; +import { Type } from '../common/core.types'; +import { getSourceOfMock } from '../common/func.get-source-of-mock'; +import { MockedDebugElement } from '../mock-render/types'; import findInstances from './mock-helper.findInstances'; diff --git a/lib/mock-helper/mock-helper.findInstances.ts b/lib/mock-helper/mock-helper.findInstances.ts index b906d5ebb7..bf3de11eda 100644 --- a/lib/mock-helper/mock-helper.findInstances.ts +++ b/lib/mock-helper/mock-helper.findInstances.ts @@ -1,7 +1,6 @@ -// tslint:disable:no-default-export - -import { getSourceOfMock, Type } from '../common'; -import { MockedDebugNode } from '../mock-render'; +import { Type } from '../common/core.types'; +import { getSourceOfMock } from '../common/func.get-source-of-mock'; +import { MockedDebugNode } from '../mock-render/types'; function nestedCheck( result: T[], diff --git a/lib/mock-helper/mock-helper.flushTestBed.ts b/lib/mock-helper/mock-helper.flushTestBed.ts index 724d044ebb..c523f6ac9c 100644 --- a/lib/mock-helper/mock-helper.flushTestBed.ts +++ b/lib/mock-helper/mock-helper.flushTestBed.ts @@ -1,5 +1,3 @@ -// tslint:disable:no-default-export - import { getTestBed } from '@angular/core/testing'; export default (): void => { diff --git a/lib/mock-helper/mock-helper.get.ts b/lib/mock-helper/mock-helper.get.ts index fec8493049..2275fc3231 100644 --- a/lib/mock-helper/mock-helper.get.ts +++ b/lib/mock-helper/mock-helper.get.ts @@ -1,7 +1,6 @@ -// tslint:disable:no-default-export - -import { getSourceOfMock, Type } from '../common'; -import { MockedDebugElement } from '../mock-render'; +import { Type } from '../common/core.types'; +import { getSourceOfMock } from '../common/func.get-source-of-mock'; +import { MockedDebugElement } from '../mock-render/types'; const defaultNotFoundValue = {}; // simulating Symbol diff --git a/lib/mock-helper/mock-helper.guts.spec.ts b/lib/mock-helper/mock-helper.guts.spec.ts index e8792eab3f..8a0eda62e2 100644 --- a/lib/mock-helper/mock-helper.guts.spec.ts +++ b/lib/mock-helper/mock-helper.guts.spec.ts @@ -14,7 +14,12 @@ import { Pipe, PipeTransform, } from '@angular/core'; -import { getMockedNgDefOf, isMockedNgDefOf, isNgDef, ngMocks } from 'ng-mocks'; + +import { getMockedNgDefOf } from '../common/func.get-mocked-ng-def-of'; +import { isMockedNgDefOf } from '../common/func.is-mocked-ng-def-of'; +import { isNgDef } from '../common/func.is-ng-def'; + +import { ngMocks } from './mock-helper'; const TARGET1 = new InjectionToken('TARGET1'); const TARGET2 = new InjectionToken('TARGET2'); diff --git a/lib/mock-helper/mock-helper.guts.ts b/lib/mock-helper/mock-helper.guts.ts index 2d7d1b6972..c5b31a8597 100644 --- a/lib/mock-helper/mock-helper.guts.ts +++ b/lib/mock-helper/mock-helper.guts.ts @@ -1,14 +1,16 @@ -// tslint:disable:no-default-export - import { core } from '@angular/compiler'; import { TestModuleMetadata } from '@angular/core/testing'; -import { flatten, isNgDef, isNgInjectionToken, isNgModuleDefWithProviders } from '../common/lib'; -import { ngModuleResolver } from '../common/reflect'; +import { flatten } from '../common/core.helpers'; +import { ngModuleResolver } from '../common/core.reflect'; +import { isNgDef } from '../common/func.is-ng-def'; +import { isNgInjectionToken } from '../common/func.is-ng-injection-token'; +import { isNgModuleDefWithProviders } from '../common/func.is-ng-module-def-with-providers'; import { MockComponent } from '../mock-component/mock-component'; import { MockDirective } from '../mock-directive/mock-directive'; -import { MockModule, MockProvider } from '../mock-module/mock-module'; +import { MockModule } from '../mock-module/mock-module'; import { MockPipe } from '../mock-pipe/mock-pipe'; +import MockProvider from '../mock-service/mock-provider'; export default (keep: any, mock: any = null, exclude: any = null): TestModuleMetadata => { const declarations: any[] = []; diff --git a/lib/mock-helper/mock-helper.input.ts b/lib/mock-helper/mock-helper.input.ts index 660491b05b..894b18e375 100644 --- a/lib/mock-helper/mock-helper.input.ts +++ b/lib/mock-helper/mock-helper.input.ts @@ -1,9 +1,7 @@ -// tslint:disable:no-default-export no-default-import - import { core } from '@angular/compiler'; -import { directiveResolver } from '../common/reflect'; -import { MockedDebugElement } from '../mock-render'; +import { directiveResolver } from '../common/core.reflect'; +import { MockedDebugElement } from '../mock-render/types'; import get from './mock-helper.get'; diff --git a/lib/mock-helper/mock-helper.output.ts b/lib/mock-helper/mock-helper.output.ts index 1e25029175..cdb0a1598f 100644 --- a/lib/mock-helper/mock-helper.output.ts +++ b/lib/mock-helper/mock-helper.output.ts @@ -1,9 +1,7 @@ -// tslint:disable:no-default-export no-default-import - import { core } from '@angular/compiler'; -import { directiveResolver } from '../common/reflect'; -import { MockedDebugElement } from '../mock-render'; +import { directiveResolver } from '../common/core.reflect'; +import { MockedDebugElement } from '../mock-render/types'; import get from './mock-helper.get'; diff --git a/lib/mock-helper/mock-helper.reset.ts b/lib/mock-helper/mock-helper.reset.ts index b9b4faa3cc..bac0ae7532 100644 --- a/lib/mock-helper/mock-helper.reset.ts +++ b/lib/mock-helper/mock-helper.reset.ts @@ -1,5 +1,3 @@ -// tslint:disable:no-default-export - import { ngMocksUniverse } from '../common/ng-mocks-universe'; export default (): void => { diff --git a/lib/mock-helper/mock-helper.spec.ts b/lib/mock-helper/mock-helper.spec.ts index 7414f740ce..6676264dc5 100644 --- a/lib/mock-helper/mock-helper.spec.ts +++ b/lib/mock-helper/mock-helper.spec.ts @@ -2,8 +2,9 @@ import { Component, Directive, EventEmitter, Input, Output } from '@angular/core import { async, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; -import { MockDirective, MockedDirective } from '../mock-directive'; -import { MockRender } from '../mock-render'; +import { MockDirective } from '../mock-directive/mock-directive'; +import { MockedDirective } from '../mock-directive/types'; +import { MockRender } from '../mock-render/mock-render'; import { ngMocks } from './mock-helper'; diff --git a/lib/mock-helper/mock-helper.stub.ts b/lib/mock-helper/mock-helper.stub.ts index 164535803c..837c93f85a 100644 --- a/lib/mock-helper/mock-helper.stub.ts +++ b/lib/mock-helper/mock-helper.stub.ts @@ -1,6 +1,5 @@ -// tslint:disable:no-default-export - -import { MockedFunction, mockServiceHelper } from '../mock-service/mock-service'; +import mockServiceHelper from '../mock-service/helper'; +import { MockedFunction } from '../mock-service/types'; export default (instance: any, override: any, style?: 'get' | 'set'): T => { if (typeof override === 'string') { diff --git a/lib/mock-helper/mock-helper.ts b/lib/mock-helper/mock-helper.ts index 204a032a8b..c7dcef5572 100644 --- a/lib/mock-helper/mock-helper.ts +++ b/lib/mock-helper/mock-helper.ts @@ -1,11 +1,12 @@ -// tslint:disable:variable-name unified-signatures no-default-import +// tslint:disable:variable-name import { EventEmitter, InjectionToken, Provider } from '@angular/core'; import { ComponentFixture, TestModuleMetadata } from '@angular/core/testing'; -import { AbstractType, AnyType, NgModuleWithProviders, Type } from '../common'; -import { MockedDebugElement, MockedDebugNode } from '../mock-render'; -import { MockedFunction } from '../mock-service'; +import { AbstractType, AnyType, Type } from '../common/core.types'; +import { NgModuleWithProviders } from '../common/func.is-ng-module-def-with-providers'; +import { MockedDebugElement, MockedDebugNode } from '../mock-render/types'; +import { MockedFunction } from '../mock-service/types'; import ngMocksFaster from './mock-helper.faster'; import ngMocksFind from './mock-helper.find'; diff --git a/lib/mock-instance/index.ts b/lib/mock-instance/index.ts deleted file mode 100644 index d37bc2a77a..0000000000 --- a/lib/mock-instance/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './mock-instance'; diff --git a/lib/mock-instance/mock-instance.ts b/lib/mock-instance/mock-instance.ts index 8fab18083b..12f2bbbc73 100644 --- a/lib/mock-instance/mock-instance.ts +++ b/lib/mock-instance/mock-instance.ts @@ -1,6 +1,6 @@ import { Injector } from '@angular/core'; -import { AbstractType, Type } from '../common'; +import { AbstractType, Type } from '../common/core.types'; import { ngMocksUniverse } from '../common/ng-mocks-universe'; /** diff --git a/lib/mock-module/index.ts b/lib/mock-module/index.ts deleted file mode 100644 index c1cf7801a2..0000000000 --- a/lib/mock-module/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './mock-module'; diff --git a/lib/mock-module/mock-module.spec.fixtures.ts b/lib/mock-module/mock-module.spec.fixtures.ts index f2dc2e56bb..514c89a513 100644 --- a/lib/mock-module/mock-module.spec.fixtures.ts +++ b/lib/mock-module/mock-module.spec.fixtures.ts @@ -2,7 +2,7 @@ import { CommonModule } from '@angular/common'; import { Component, Directive, ElementRef, Injectable, NgModule, OnInit, Pipe, PipeTransform } from '@angular/core'; import { RouterModule } from '@angular/router'; -import { NgModuleWithProviders } from '../common'; +import { NgModuleWithProviders } from '../common/func.is-ng-module-def-with-providers'; @Directive({ selector: '[example-directive]' }) export class ExampleDirective implements OnInit { diff --git a/lib/mock-module/mock-module.spec.ts b/lib/mock-module/mock-module.spec.ts index a1016bb2cf..230d8b38cf 100644 --- a/lib/mock-module/mock-module.spec.ts +++ b/lib/mock-module/mock-module.spec.ts @@ -13,12 +13,13 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { BrowserModule, By } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { ngModuleResolver } from '../common/core.reflect'; import { ngMocksUniverse } from '../common/ng-mocks-universe'; -import { ngModuleResolver } from '../common/reflect'; -import { MockComponent } from '../mock-component'; -import { MockModule, MockProvider } from '../mock-module'; -import { MockRender } from '../mock-render'; +import { MockComponent } from '../mock-component/mock-component'; +import { MockRender } from '../mock-render/mock-render'; +import MockProvider from '../mock-service/mock-provider'; +import { MockModule } from './mock-module'; import { AppRoutingModule, CustomWithServiceComponent, diff --git a/lib/mock-module/mock-module.ts b/lib/mock-module/mock-module.ts index c0590fc613..3b7490d1f0 100644 --- a/lib/mock-module/mock-module.ts +++ b/lib/mock-module/mock-module.ts @@ -1,152 +1,21 @@ import { CommonModule } from '@angular/common'; import { core } from '@angular/compiler'; -import { ApplicationModule, APP_INITIALIZER, NgModule, Provider } from '@angular/core'; +import { ApplicationModule, NgModule, Provider } from '@angular/core'; import { getTestBed } from '@angular/core/testing'; -import { EVENT_MANAGER_PLUGINS, HAMMER_GESTURE_CONFIG } from '@angular/platform-browser'; - -import { - extendClass, - flatten, - getMockedNgDefOf, - isNgDef, - isNgInjectionToken, - isNgModuleDefWithProviders, - Mock, - MockOf, - NgModuleWithProviders, - Type, -} from '../common'; -import { ngMocksUniverse } from '../common/ng-mocks-universe'; -import { ngModuleResolver } from '../common/reflect'; -import { MockComponent } from '../mock-component'; -import { MockDirective } from '../mock-directive'; -import { MockPipe } from '../mock-pipe'; -import { MockService, mockServiceHelper } from '../mock-service'; - -export type MockedModule = T & Mock & {}; - -const neverMockProvidedFunction = [ - 'DomRendererFactory2', - 'DomSharedStylesHost', - 'EventManager', - 'Injector', - 'RendererFactory2', -]; -const neverMockToken = [APP_INITIALIZER, EVENT_MANAGER_PLUGINS, HAMMER_GESTURE_CONFIG]; - -/** - * Can be changed any time. - * - * @internal - */ -export function MockProvider(provider: any): Provider | undefined { - const provide = typeof provider === 'object' && provider.provide ? provider.provide : provider; - - if (typeof provide === 'function' && neverMockProvidedFunction.indexOf(provide.name) !== -1) { - return provider; - } - if (isNgInjectionToken(provide) && neverMockToken.indexOf(provide) !== -1) { - return undefined; - } - - // Only pure provides should be cached to avoid their influence on - // another different declarations. - if ( - provide === provider && - ngMocksUniverse.flags.has('cacheProvider') && - ngMocksUniverse.cacheProviders.has(provide) - ) { - return ngMocksUniverse.cacheProviders.get(provide); - } - - let mockedProvider: Provider | undefined; - if (typeof provide === 'function' && !mockedProvider) { - mockedProvider = mockServiceHelper.useFactory(ngMocksUniverse.cacheMocks.get(provide) || provide, () => { - const instance = MockService(provide); - // Magic below adds missed properties to the instance to - // fulfill missed abstract methods. - if (provide !== provider && Object.keys(provider).indexOf('useClass') !== -1) { - const existing = Object.getOwnPropertyNames(instance); - const child = MockService(provider.useClass); - for (const name of Object.getOwnPropertyNames(child)) { - if (existing.indexOf(name) !== -1) { - continue; - } - const def = Object.getOwnPropertyDescriptor(child, name); - /* istanbul ignore else */ - if (def) { - Object.defineProperty(instance, name, def); - } - } - } - return instance; - }); - } - if (provide === provider && mockedProvider && ngMocksUniverse.flags.has('cacheProvider')) { - ngMocksUniverse.cacheProviders.set(provide, mockedProvider); - } - if (mockedProvider) { - return mockedProvider; - } - - // Not sure if this case is possible, all classes should be already - // mocked by the code above, below we should have only tokens and - // string literals with a proper definition. - if (provide === provider) { - return undefined; - } - - // Tokens are special subject, we can skip adding them because in a mocked module they are useless. - // The main problem is that providing undefined to HTTP_INTERCEPTORS and others breaks their code. - // If a testing module / component requires omitted tokens then they should be provided manually - // during creation of TestBed module. - if (provider.multi) { - if (ngMocksUniverse.config.has('multi')) { - (ngMocksUniverse.config.get('multi') as Set).add(provide); - } - return undefined; - } - - // if a token has a primitive type, we can return its initial state. - if (!mockedProvider && Object.keys(provider).indexOf('useValue') !== -1) { - mockedProvider = - provider.useValue && typeof provider.useValue === 'object' - ? mockServiceHelper.useFactory(ngMocksUniverse.cacheMocks.get(provide) || provide, () => - MockService(provider.useValue) - ) - : { - provide, - useValue: - typeof provider.useValue === 'boolean' - ? false - : typeof provider.useValue === 'number' - ? 0 - : typeof provider.useValue === 'string' - ? '' - : provider.useValue === null - ? null - : undefined, - }; - } - if (!mockedProvider && Object.keys(provider).indexOf('useExisting') !== -1) { - mockedProvider = provider; - } - if (!mockedProvider && Object.keys(provider).indexOf('useClass') !== -1) { - mockedProvider = - ngMocksUniverse.builder.has(provider.useClass) && - ngMocksUniverse.builder.get(provider.useClass) === provider.useClass - ? provider - : mockServiceHelper.useFactory(ngMocksUniverse.cacheMocks.get(provide) || provide, () => - MockService(provider.useClass) - ); - } - if (!mockedProvider && Object.keys(provider).indexOf('useFactory') !== -1) { - mockedProvider = mockServiceHelper.useFactory(ngMocksUniverse.cacheMocks.get(provide) || provide, () => ({})); - } - - return mockedProvider; -} +import { extendClass, flatten } from '../common/core.helpers'; +import { ngModuleResolver } from '../common/core.reflect'; +import { Type } from '../common/core.types'; +import { getMockedNgDefOf } from '../common/func.get-mocked-ng-def-of'; +import { isNgDef } from '../common/func.is-ng-def'; +import { isNgModuleDefWithProviders, NgModuleWithProviders } from '../common/func.is-ng-module-def-with-providers'; +import { Mock } from '../common/mock'; +import { MockOf } from '../common/mock-of'; +import { ngMocksUniverse } from '../common/ng-mocks-universe'; +import { MockComponent } from '../mock-component/mock-component'; +import { MockDirective } from '../mock-directive/mock-directive'; +import { MockPipe } from '../mock-pipe/mock-pipe'; +import mockServiceHelper from '../mock-service/helper'; /** * @see https://github.com/ike18t/ng-mocks#how-to-mock-a-module @@ -277,7 +146,7 @@ export function MockNgDef(ngModuleDef: NgModule, ngModule?: Type): [boolean // resolveProvider is a special case because of the def structure. const resolveProvider = (def: Provider) => - mockServiceHelper.resolveProvider(def, resolutions, flag => { + mockServiceHelper.resolveProvider(def, resolutions, (flag: boolean) => { changed = changed || flag; }); diff --git a/lib/mock-module/providers.spec.ts b/lib/mock-module/providers.spec.ts index 49e43d37bc..f3af3f10dd 100644 --- a/lib/mock-module/providers.spec.ts +++ b/lib/mock-module/providers.spec.ts @@ -1,6 +1,6 @@ import { Injectable, NgModule } from '@angular/core'; -import { isMockedNgDefOf } from '../common/lib'; +import { isMockedNgDefOf } from '../common/func.is-mocked-ng-def-of'; import { MockModule } from './mock-module'; diff --git a/lib/mock-module/types.ts b/lib/mock-module/types.ts new file mode 100644 index 0000000000..3473f4d286 --- /dev/null +++ b/lib/mock-module/types.ts @@ -0,0 +1,3 @@ +import { Mock } from '../common/mock'; + +export type MockedModule = T & Mock & {}; diff --git a/lib/mock-pipe/index.ts b/lib/mock-pipe/index.ts deleted file mode 100644 index 566975e8bb..0000000000 --- a/lib/mock-pipe/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './mock-pipe'; diff --git a/lib/mock-pipe/mock-pipe.spec.ts b/lib/mock-pipe/mock-pipe.spec.ts index 94bdee1e5f..946921cb89 100644 --- a/lib/mock-pipe/mock-pipe.spec.ts +++ b/lib/mock-pipe/mock-pipe.spec.ts @@ -2,7 +2,7 @@ import { Component, Pipe, PipeTransform } from '@angular/core'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; -import { isMockedNgDefOf } from '../common/lib'; +import { isMockedNgDefOf } from '../common/func.is-mocked-ng-def-of'; import { MockPipe, MockPipes } from './mock-pipe'; diff --git a/lib/mock-pipe/mock-pipe.ts b/lib/mock-pipe/mock-pipe.ts index 75763e4d1f..55f023935f 100644 --- a/lib/mock-pipe/mock-pipe.ts +++ b/lib/mock-pipe/mock-pipe.ts @@ -2,12 +2,18 @@ import { core } from '@angular/compiler'; import { Pipe, PipeTransform } from '@angular/core'; import { getTestBed } from '@angular/core/testing'; -import { AnyType, getMockedNgDefOf, Mock, MockOf, Type } from '../common'; +import { pipeResolver } from '../common/core.reflect'; +import { AnyType, Type } from '../common/core.types'; +import { getMockedNgDefOf } from '../common/func.get-mocked-ng-def-of'; +import { Mock } from '../common/mock'; +import { MockOf } from '../common/mock-of'; import { ngMocksUniverse } from '../common/ng-mocks-universe'; -import { pipeResolver } from '../common/reflect'; -export type MockedPipe = T & Mock & {}; +import { MockedPipe } from './types'; +/** + * @see https://github.com/ike18t/ng-mocks#how-to-mock-a-pipe + */ export function MockPipes(...pipes: Array>): Array> { return pipes.map(pipe => MockPipe(pipe, undefined)); } diff --git a/lib/mock-pipe/types.ts b/lib/mock-pipe/types.ts new file mode 100644 index 0000000000..ca41b6f8aa --- /dev/null +++ b/lib/mock-pipe/types.ts @@ -0,0 +1,3 @@ +import { Mock } from '../common/mock'; + +export type MockedPipe = T & Mock & {}; diff --git a/lib/mock-provider/mock-provider.spec.ts b/lib/mock-provider/mock-provider.spec.ts new file mode 100644 index 0000000000..6b8d1ee356 --- /dev/null +++ b/lib/mock-provider/mock-provider.spec.ts @@ -0,0 +1,48 @@ +import { Injectable, InjectionToken, Injector } from '@angular/core'; + +import { MockProvider, MockProviders } from './mock-provider'; + +const TARGET_TOKEN = new InjectionToken('TARGET_TOKEN'); + +@Injectable() +class TargetService { + public name = 'target'; +} + +describe('mock-provider', () => { + it('returns undefined for tokens', () => { + const actual = MockProvider(TARGET_TOKEN); + expect(actual).toEqual({ + provide: TARGET_TOKEN, + useValue: undefined, + }); + }); + + it('returns factories for services', () => { + const actual: any = MockProvider(TargetService); + expect(actual).toEqual({ + deps: [Injector], + provide: TargetService, + useFactory: jasmine.anything(), + }); + + const instance = actual.useFactory(); + + expect(instance).toEqual(jasmine.any(TargetService)); + expect(instance.name).toBeUndefined(); + }); + + it('returns an array of mocked providers', () => { + const actual = MockProviders(TARGET_TOKEN, TargetService); + expect(actual.length).toEqual(2); + expect(actual[0]).toEqual({ + provide: TARGET_TOKEN, + useValue: undefined, + }); + expect(actual[1]).toEqual({ + deps: [Injector], + provide: TargetService, + useFactory: jasmine.anything(), + }); + }); +}); diff --git a/lib/mock-provider/mock-provider.ts b/lib/mock-provider/mock-provider.ts new file mode 100644 index 0000000000..2ea0965075 --- /dev/null +++ b/lib/mock-provider/mock-provider.ts @@ -0,0 +1,57 @@ +import { + ClassProvider, + ConstructorProvider, + ExistingProvider, + FactoryProvider, + InjectionToken, + Provider, + ValueProvider, +} from '@angular/core'; + +import { AnyType } from '../common/core.types'; +import useFactory from '../mock-service/helper.use-factory'; +import { MockService } from '../mock-service/mock-service'; + +const defaultValue = {}; + +export function MockProviders(...providers: Array | InjectionToken>): Provider[] { + return providers.map((provider: any) => MockProvider(provider, defaultValue)); +} + +/** + * @see https://github.com/ike18t/ng-mocks#how-to-mock-a-provider + */ +export function MockProvider(instance: AnyType, overrides?: Partial): Provider; + +/** + * @see https://github.com/ike18t/ng-mocks#how-to-mock-a-provider + */ +export function MockProvider(provider: InjectionToken, instance?: I): Provider; + +export function MockProvider(provide: any, useValue: any = defaultValue): Provider { + if (useValue !== defaultValue) { + return { + provide, + useValue, + }; + } + + let mockedProvider: + | ValueProvider + | ClassProvider + | ConstructorProvider + | ExistingProvider + | FactoryProvider + | undefined; + + if (!mockedProvider && typeof provide === 'function') { + mockedProvider = useFactory(provide, () => MockService(provide)); + } else { + mockedProvider = { + provide, + useValue: undefined, + }; + } + + return mockedProvider; +} diff --git a/lib/mock-render/index.ts b/lib/mock-render/index.ts deleted file mode 100644 index 879dd24f44..0000000000 --- a/lib/mock-render/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './mock-render'; diff --git a/lib/mock-render/mock-render.spec.ts b/lib/mock-render/mock-render.spec.ts index 0b7ac81540..582ee824e5 100644 --- a/lib/mock-render/mock-render.spec.ts +++ b/lib/mock-render/mock-render.spec.ts @@ -5,11 +5,12 @@ import { By } from '@angular/platform-browser'; import { Subject } from 'rxjs'; import { first } from 'rxjs/operators'; -import { ngMocks } from '../mock-helper'; -import { MockService } from '../mock-service'; +import { ngMocks } from '../mock-helper/mock-helper'; +import { MockService } from '../mock-service/mock-service'; -import { MockedComponentFixture, MockedDebugElement, MockedDebugNode, MockRender } from './mock-render'; +import { MockRender } from './mock-render'; import { RenderRealComponent, WithoutSelectorComponent } from './mock-render.spec.fixtures'; +import { MockedComponentFixture, MockedDebugElement, MockedDebugNode } from './types'; describe('MockRender', () => { beforeEach(() => { diff --git a/lib/mock-render/mock-render.ts b/lib/mock-render/mock-render.ts index 2d63503171..57166d99ff 100644 --- a/lib/mock-render/mock-render.ts +++ b/lib/mock-render/mock-render.ts @@ -1,38 +1,14 @@ import { core } from '@angular/compiler'; -import { Component, DebugElement, DebugNode, EventEmitter, Provider } from '@angular/core'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Component, EventEmitter } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; import { Subject } from 'rxjs'; -import { Type } from '../common'; -import { directiveResolver } from '../common/reflect'; -import { ngMocks } from '../mock-helper'; -import { mockServiceHelper } from '../mock-service'; +import { directiveResolver } from '../common/core.reflect'; +import { Type } from '../common/core.types'; +import { ngMocks } from '../mock-helper/mock-helper'; +import mockServiceHelper from '../mock-service/helper'; -// tslint:disable-next-line:interface-name -export interface MockedDebugNode extends DebugNode { - componentInstance: T; -} - -// tslint:disable-next-line:interface-name -export interface MockedDebugElement extends DebugElement, MockedDebugNode { - componentInstance: T; -} - -export interface IMockRenderOptions { - detectChanges?: boolean; - providers?: Provider[]; -} - -// tslint:disable-next-line:interface-name -export interface MockedComponentFixture> extends ComponentFixture { - point: MockedDebugElement; -} - -// tslint:disable: interface-over-type-literal interface-name -export type DefaultRenderComponent> = { - [K in keyof MComponent]: MComponent[K]; -}; -// tslint:enable: interface-over-type-literal interface-name +import { IMockRenderOptions, MockedComponentFixture } from './types'; function solveOutput(output: any): string { if (typeof output === 'function') { diff --git a/lib/mock-render/types.ts b/lib/mock-render/types.ts new file mode 100644 index 0000000000..30d6a1bb7c --- /dev/null +++ b/lib/mock-render/types.ts @@ -0,0 +1,25 @@ +// tslint:disable:interface-name + +import { DebugElement, DebugNode, Provider } from '@angular/core'; +import { ComponentFixture } from '@angular/core/testing'; + +export interface MockedDebugNode extends DebugNode { + componentInstance: T; +} + +export interface MockedDebugElement extends DebugElement, MockedDebugNode { + componentInstance: T; +} + +export interface IMockRenderOptions { + detectChanges?: boolean; + providers?: Provider[]; +} + +export interface MockedComponentFixture> extends ComponentFixture { + point: MockedDebugElement; +} + +export type DefaultRenderComponent> = { + [K in keyof MComponent]: MComponent[K]; +}; diff --git a/lib/mock-service/func.is-class.ts b/lib/mock-service/func.is-class.ts new file mode 100644 index 0000000000..1437ec791d --- /dev/null +++ b/lib/mock-service/func.is-class.ts @@ -0,0 +1,16 @@ +import isFunc from './func.is-func'; + +export default (value: any): boolean => { + if (typeof value !== 'function') { + return false; + } + if (isFunc(value)) { + return false; + } + const proto = value.toString(); + /* istanbul ignore next */ + if (proto.match(/^class\b/) !== null) { + return true; + } + return proto.match(/^function\s*\(/) === null; +}; diff --git a/lib/mock-service/func.is-func.ts b/lib/mock-service/func.is-func.ts new file mode 100644 index 0000000000..2f384d07a7 --- /dev/null +++ b/lib/mock-service/func.is-func.ts @@ -0,0 +1,11 @@ +export default (value: any): boolean => { + if (typeof value !== 'function') { + return false; + } + const proto = value.toString(); + /* istanbul ignore next */ + if (proto.match(/^\(/) !== null) { + return true; + } + return proto.match(/^function\s*\(/) !== null; +}; diff --git a/lib/mock-service/func.is-inst.ts b/lib/mock-service/func.is-inst.ts new file mode 100644 index 0000000000..4a2a185104 --- /dev/null +++ b/lib/mock-service/func.is-inst.ts @@ -0,0 +1,12 @@ +export default (value: any): boolean => { + if (value === null) { + return false; + } + if (typeof value !== 'object') { + return false; + } + if (value.ngMetadataName === 'InjectionToken') { + return false; + } + return typeof Object.getPrototypeOf(value) === 'object'; +}; diff --git a/lib/mock-service/helper.create-mock-from-prototype.ts b/lib/mock-service/helper.create-mock-from-prototype.ts new file mode 100644 index 0000000000..b4bb79d003 --- /dev/null +++ b/lib/mock-service/helper.create-mock-from-prototype.ts @@ -0,0 +1,24 @@ +import mockServiceHelper from './helper'; +import { MockedFunction } from './types'; + +export default (service: any): { [key in keyof any]: MockedFunction } => { + const mockName = `${service && service.constructor ? service.constructor.name : 'unknown'}`; + const value: any = {}; + + const methods = mockServiceHelper.extractMethodsFromPrototype(service); + for (const method of methods) { + mockServiceHelper.mock(value, method, mockName); + } + + const properties = mockServiceHelper.extractPropertiesFromPrototype(service); + for (const property of properties) { + mockServiceHelper.mock(value, property, 'get', mockName); + mockServiceHelper.mock(value, property, 'set', mockName); + } + + if (typeof value === 'object' && typeof service === 'object') { + Object.setPrototypeOf(value, service); + } + + return value; +}; diff --git a/lib/mock-service/helper.extract-methods-from-prototype.ts b/lib/mock-service/helper.extract-methods-from-prototype.ts new file mode 100644 index 0000000000..27a2530a94 --- /dev/null +++ b/lib/mock-service/helper.extract-methods-from-prototype.ts @@ -0,0 +1,20 @@ +export default (service: T): string[] => { + const result: string[] = []; + let prototype = service; + while (prototype && Object.getPrototypeOf(prototype) !== null) { + for (const method of Object.getOwnPropertyNames(prototype)) { + if ((method as any) === 'constructor') { + continue; + } + + const descriptor = Object.getOwnPropertyDescriptor(prototype, method); + const isGetterSetter = descriptor && (descriptor.get || descriptor.set); + if (isGetterSetter || result.indexOf(method) !== -1) { + continue; + } + result.push(method); + } + prototype = Object.getPrototypeOf(prototype); + } + return result; +}; diff --git a/lib/mock-service/helper.extract-properties-from-prototype.ts b/lib/mock-service/helper.extract-properties-from-prototype.ts new file mode 100644 index 0000000000..2e05f95b0f --- /dev/null +++ b/lib/mock-service/helper.extract-properties-from-prototype.ts @@ -0,0 +1,20 @@ +export default (service: T): string[] => { + const result: string[] = []; + let prototype = service; + while (prototype && Object.getPrototypeOf(prototype) !== null) { + for (const prop of Object.getOwnPropertyNames(prototype)) { + if ((prop as any) === 'constructor') { + continue; + } + + const descriptor = Object.getOwnPropertyDescriptor(prototype, prop); + const isGetterSetter = descriptor && (descriptor.get || descriptor.set); + if (!isGetterSetter || result.indexOf(prop) !== -1) { + continue; + } + result.push(prop); + } + prototype = Object.getPrototypeOf(prototype); + } + return result; +}; diff --git a/lib/mock-service/helper.extract-property-descriptor.ts b/lib/mock-service/helper.extract-property-descriptor.ts new file mode 100644 index 0000000000..ca7f3185e6 --- /dev/null +++ b/lib/mock-service/helper.extract-property-descriptor.ts @@ -0,0 +1,10 @@ +export default (service: T, prop: string): PropertyDescriptor | undefined => { + let prototype = service; + while (prototype && Object.getPrototypeOf(prototype) !== null) { + const descriptor = Object.getOwnPropertyDescriptor(prototype, prop); + if (descriptor) { + return descriptor; + } + prototype = Object.getPrototypeOf(prototype); + } +}; diff --git a/lib/mock-service/helper.mock-function.ts b/lib/mock-service/helper.mock-function.ts new file mode 100644 index 0000000000..6eedba7574 --- /dev/null +++ b/lib/mock-service/helper.mock-function.ts @@ -0,0 +1,31 @@ +import { CustomMockFunction, MockedFunction } from './types'; + +const mockFunction: { + (mockName: string, original?: boolean): MockedFunction; + customMockFunction?: CustomMockFunction; +} = (mockName: string, original: boolean = false): MockedFunction => { + let func: any; + if (mockFunction.customMockFunction && !original) { + func = mockFunction.customMockFunction(mockName); + } else { + func = (val: any) => { + if (setValue) { + setValue(val); + } + return value; + }; + } + + // magic to make getters / setters working + + let value: any; + let setValue: any; + + func.__ngMocks = true; + func.__ngMocksSet = (newSetValue: any) => (setValue = newSetValue); + func.__ngMocksGet = (newValue: any) => (value = newValue); + + return func; +}; + +export default mockFunction; diff --git a/lib/mock-service/helper.mock.ts b/lib/mock-service/helper.mock.ts new file mode 100644 index 0000000000..6e1adba268 --- /dev/null +++ b/lib/mock-service/helper.mock.ts @@ -0,0 +1,65 @@ +import mockServiceHelper from './helper'; +import { MockedFunction } from './types'; + +export default (instance: any, name: string, ...args: string[]): T => { + let accessType: 'get' | 'set' | undefined; + let mockName: string | undefined; + + if (args.length && args[0] !== 'get' && args[0] !== 'set') { + mockName = args[0]; + } else if (args.length && (args[0] === 'get' || args[0] === 'set')) { + accessType = args[0] as any; + mockName = args[1]; + } + + const def = Object.getOwnPropertyDescriptor(instance, name); + if (def && def[accessType || 'value']) { + return def[accessType || 'value']; + } + + /* istanbul ignore next */ + const detectedMockName = `${ + mockName + ? mockName + : typeof instance.prototype === 'function' + ? instance.prototype.name + : typeof instance.constructor === 'function' + ? instance.constructor.name + : 'unknown' + }.${name}${accessType ? `:${accessType}` : ''}`; + const mock: any = mockServiceHelper.mockFunction(detectedMockName, !!accessType); + + const mockDef: PropertyDescriptor = { + // keeping setter if we adding getter + ...(accessType === 'get' && def && def.set + ? { + set: def.set, + } + : {}), + + // keeping getter if we adding setter + ...(accessType === 'set' && def && def.get + ? { + get: def.get, + } + : {}), + + // to allow replacement for functions + ...(accessType + ? {} + : { + writable: true, + }), + + [accessType || 'value']: mock, + configurable: true, + enumerable: true, + }; + + if (mockDef.get && mockDef.set && (mockDef.get as any).__ngMocks && (mockDef.set as any).__ngMocks) { + (mockDef.set as any).__ngMocksSet((val: any) => (mockDef.get as any).__ngMocksGet(val)); + } + + Object.defineProperty(instance, name, mockDef); + return mock; +}; diff --git a/lib/mock-service/helper.replace-with-mocks.ts b/lib/mock-service/helper.replace-with-mocks.ts new file mode 100644 index 0000000000..1a1261d810 --- /dev/null +++ b/lib/mock-service/helper.replace-with-mocks.ts @@ -0,0 +1,70 @@ +import { NG_GUARDS } from '../common/core.tokens'; +import { ngMocksUniverse } from '../common/ng-mocks-universe'; + +const replaceWithMocks = (value: any): any => { + if (ngMocksUniverse.cacheMocks.has(value)) { + return ngMocksUniverse.cacheMocks.get(value); + } + if (typeof value !== 'object') { + return value; + } + + let mocked: any; + let updated = false; + + if (Array.isArray(value)) { + mocked = []; + for (const valueItem of value) { + if (ngMocksUniverse.builder.has(valueItem) && ngMocksUniverse.builder.get(valueItem) === null) { + updated = updated || true; + continue; + } + mocked.push(replaceWithMocks(valueItem)); + updated = updated || mocked[mocked.length - 1] !== valueItem; + } + } else if (value) { + mocked = {}; + for (const key of Object.keys(value)) { + if (ngMocksUniverse.builder.has(value[key]) && ngMocksUniverse.builder.get(value[key]) === null) { + updated = updated || true; + continue; + } + mocked[key] = replaceWithMocks(value[key]); + updated = updated || mocked[key] !== value[key]; + } + + // Removal of guards. + for (const section of ['canActivate', 'canActivateChild', 'canDeactivate', 'canLoad']) { + if (!Array.isArray(mocked[section])) { + continue; + } + + const guards: any[] = []; + for (const guard of mocked[section]) { + if (ngMocksUniverse.builder.has(guard) && ngMocksUniverse.builder.get(guard) !== null) { + guards.push(guard); + continue; + } + if (ngMocksUniverse.builder.has(NG_GUARDS) && ngMocksUniverse.builder.get(NG_GUARDS) === null) { + continue; + } + guards.push(guard); + } + if (mocked[section].length !== guards.length) { + updated = updated || true; + mocked = { + ...mocked, + [section]: guards, + }; + } + } + } + + if (updated) { + Object.setPrototypeOf(mocked, Object.getPrototypeOf(value)); + return mocked; + } + return value; +}; + +export default replaceWithMocks; diff --git a/lib/mock-service/helper.resolve-provider.ts b/lib/mock-service/helper.resolve-provider.ts new file mode 100644 index 0000000000..8683123e17 --- /dev/null +++ b/lib/mock-service/helper.resolve-provider.ts @@ -0,0 +1,123 @@ +import { NG_INTERCEPTORS } from '../common/core.tokens'; +import { isNgInjectionToken } from '../common/func.is-ng-injection-token'; +import { ngMocksUniverse } from '../common/ng-mocks-universe'; + +import mockServiceHelper from './helper'; +import MockProvider from './mock-provider'; + +// tries to resolve a provider based on current universe state. +export default (def: any, resolutions: Map, changed?: (flag: boolean) => void) => { + const provider = typeof def === 'object' && def.provide ? def.provide : def; + const multi = def !== provider && !!def.multi; + + // we shouldn't touch our system providers. + if (typeof def === 'object' && def.useExisting && def.useExisting.__ngMocksSkip) { + return def; + } + + let mockedDef: typeof def; + if (resolutions.has(provider)) { + mockedDef = resolutions.get(provider); + // A case when a provider is actually a component, directive, pipe. + if (typeof mockedDef === 'function') { + mockedDef = { + provide: provider, + useClass: mockedDef, + }; + } + return multi && typeof mockedDef === 'object' ? { ...mockedDef, multi } : mockedDef; + } + + // we shouldn't touch excluded providers. + if (ngMocksUniverse.builder.has(provider) && ngMocksUniverse.builder.get(provider) === null) { + /* istanbul ignore else */ + if (changed) { + changed(true); + } + return; + } + + if ( + ngMocksUniverse.builder.has(NG_INTERCEPTORS) && + ngMocksUniverse.builder.get(NG_INTERCEPTORS) === null && + isNgInjectionToken(provider) && + provider.toString() === 'InjectionToken HTTP_INTERCEPTORS' && + provider !== def + ) { + if (def.useFactory || def.useValue) { + /* istanbul ignore else */ + if (changed) { + changed(true); + } + return; + } + const interceptor = def.useExisting || def.useClass; + if (!ngMocksUniverse.builder.has(interceptor) || ngMocksUniverse.builder.get(interceptor) === null) { + /* istanbul ignore else */ + if (changed) { + changed(true); + } + return; + } + } + + // Then we check decisions whether we should keep or replace a def. + if (!mockedDef && ngMocksUniverse.builder.has(provider)) { + mockedDef = ngMocksUniverse.builder.get(provider); + if (mockedDef === provider) { + mockedDef = def; + } else if (mockedDef === undefined) { + mockedDef = { + provide: provider, + useValue: undefined, + }; + } + } + + if (!mockedDef && ngMocksUniverse.flags.has('skipMock')) { + mockedDef = def; + } + 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); + } + let differs = false; + if (def === provider && mockedDef !== def) { + differs = true; + } else if ( + def !== provider && + (!mockedDef || + def.provide !== mockedDef.provide || + def.useValue !== mockedDef.useValue || + def.useClass !== mockedDef.useClass || + def.useExisting !== mockedDef.useExisting || + def.useFactory !== mockedDef.useFactory || + def.deps !== mockedDef.deps) + ) { + differs = true; + } + if (changed && differs) { + changed(true); + } + + // Touching only when we really provide a value. + if (mockedDef) { + ngMocksUniverse.touches.add(provider); + } + + return multi && typeof mockedDef === 'object' ? { ...mockedDef, multi } : mockedDef; +}; diff --git a/lib/mock-service/helper.ts b/lib/mock-service/helper.ts new file mode 100644 index 0000000000..258aa75a7f --- /dev/null +++ b/lib/mock-service/helper.ts @@ -0,0 +1,42 @@ +import createMockFromPrototype from './helper.create-mock-from-prototype'; +import extractMethodsFromPrototype from './helper.extract-methods-from-prototype'; +import extractPropertiesFromPrototype from './helper.extract-properties-from-prototype'; +import extractPropertyDescriptor from './helper.extract-property-descriptor'; +import mock from './helper.mock'; +import mockFunction from './helper.mock-function'; +import replaceWithMocks from './helper.replace-with-mocks'; +import resolveProvider from './helper.resolve-provider'; +import useFactory from './helper.use-factory'; +import { CustomMockFunction } from './types'; + +/* istanbul ignore next */ +const getGlobal = (): any => window || global; + +// We need a single pointer to the object among all environments. +getGlobal().ngMocksMockServiceHelper = getGlobal().ngMocksMockServiceHelper || { + mockFunction, + + registerMockFunction: (func: CustomMockFunction | undefined) => { + getGlobal().ngMocksMockServiceHelper.mockFunction.customMockFunction = func; + }, + + createMockFromPrototype, + extractMethodsFromPrototype, + extractPropertiesFromPrototype, + extractPropertyDescriptor, + mock, + replaceWithMocks, + resolveProvider, + useFactory, +}; + +/** + * DO NOT USE this object outside of the library. + * It can be changed any time without a notice. + * + * @internal + */ +export default getGlobal().ngMocksMockServiceHelper; + +export const registerMockFunction: (func: CustomMockFunction | undefined) => void = getGlobal().ngMocksMockServiceHelper + .registerMockFunction; diff --git a/lib/mock-service/helper.use-factory.ts b/lib/mock-service/helper.use-factory.ts new file mode 100644 index 0000000000..8fbe1a6e5b --- /dev/null +++ b/lib/mock-service/helper.use-factory.ts @@ -0,0 +1,16 @@ +import { FactoryProvider, Injector } from '@angular/core'; + +import { ngMocksUniverse } from '../common/ng-mocks-universe'; + +export default (def: D, mock: () => I): FactoryProvider => ({ + deps: [Injector], + provide: def, + useFactory: (injector?: Injector) => { + const instance = mock(); + const config = ngMocksUniverse.config.get(def); + if (injector && instance && config && config.init) { + config.init(instance, injector); + } + return instance; + }, +}); diff --git a/lib/mock-service/index.ts b/lib/mock-service/index.ts deleted file mode 100644 index b02eab2f00..0000000000 --- a/lib/mock-service/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './mock-service'; diff --git a/lib/mock-service/mock-provider.ts b/lib/mock-service/mock-provider.ts new file mode 100644 index 0000000000..0c54e34c75 --- /dev/null +++ b/lib/mock-service/mock-provider.ts @@ -0,0 +1,122 @@ +import { APP_INITIALIZER, Provider } from '@angular/core'; +import { EVENT_MANAGER_PLUGINS, HAMMER_GESTURE_CONFIG } from '@angular/platform-browser'; + +import { isNgInjectionToken } from '../common/func.is-ng-injection-token'; +import { ngMocksUniverse } from '../common/ng-mocks-universe'; + +import useFactory from './helper.use-factory'; +import { MockService } from './mock-service'; + +const neverMockProvidedFunction = [ + 'DomRendererFactory2', + 'DomSharedStylesHost', + 'EventManager', + 'Injector', + 'RendererFactory2', +]; +const neverMockToken = [APP_INITIALIZER, EVENT_MANAGER_PLUGINS, HAMMER_GESTURE_CONFIG]; + +export default function (provider: any): Provider | undefined { + const provide = typeof provider === 'object' && provider.provide ? provider.provide : provider; + + if (typeof provide === 'function' && neverMockProvidedFunction.indexOf(provide.name) !== -1) { + return provider; + } + if (isNgInjectionToken(provide) && neverMockToken.indexOf(provide) !== -1) { + return undefined; + } + + // Only pure provides should be cached to avoid their influence on + // another different declarations. + if ( + provide === provider && + ngMocksUniverse.flags.has('cacheProvider') && + ngMocksUniverse.cacheProviders.has(provide) + ) { + return ngMocksUniverse.cacheProviders.get(provide); + } + + let mockedProvider: Provider | undefined; + if (typeof provide === 'function' && !mockedProvider) { + mockedProvider = useFactory(ngMocksUniverse.cacheMocks.get(provide) || provide, () => { + const instance = MockService(provide); + // Magic below adds missed properties to the instance to + // fulfill missed abstract methods. + if (provide !== provider && Object.keys(provider).indexOf('useClass') !== -1) { + const existing = Object.getOwnPropertyNames(instance); + const child = MockService(provider.useClass); + for (const name of Object.getOwnPropertyNames(child)) { + if (existing.indexOf(name) !== -1) { + continue; + } + const def = Object.getOwnPropertyDescriptor(child, name); + /* istanbul ignore else */ + if (def) { + Object.defineProperty(instance, name, def); + } + } + } + return instance; + }); + } + + if (provide === provider && mockedProvider && ngMocksUniverse.flags.has('cacheProvider')) { + ngMocksUniverse.cacheProviders.set(provide, mockedProvider); + } + if (mockedProvider) { + return mockedProvider; + } + + // Not sure if this case is possible, all classes should be already + // mocked by the code above, below we should have only tokens and + // string literals with a proper definition. + if (provide === provider) { + return undefined; + } + + // Tokens are special subject, we can skip adding them because in a mocked module they are useless. + // The main problem is that providing undefined to HTTP_INTERCEPTORS and others breaks their code. + // If a testing module / component requires omitted tokens then they should be provided manually + // during creation of TestBed module. + if (provider.multi) { + if (ngMocksUniverse.config.has('multi')) { + (ngMocksUniverse.config.get('multi') as Set).add(provide); + } + return undefined; + } + + // if a token has a primitive type, we can return its initial state. + if (!mockedProvider && Object.keys(provider).indexOf('useValue') !== -1) { + mockedProvider = + provider.useValue && typeof provider.useValue === 'object' + ? useFactory(ngMocksUniverse.cacheMocks.get(provide) || provide, () => MockService(provider.useValue)) + : { + provide, + useValue: + typeof provider.useValue === 'boolean' + ? false + : typeof provider.useValue === 'number' + ? 0 + : typeof provider.useValue === 'string' + ? '' + : provider.useValue === null + ? null + : undefined, + }; + } + if (!mockedProvider && Object.keys(provider).indexOf('useExisting') !== -1) { + mockedProvider = provider; + } + if (!mockedProvider && Object.keys(provider).indexOf('useClass') !== -1) { + mockedProvider = + ngMocksUniverse.builder.has(provider.useClass) && + ngMocksUniverse.builder.get(provider.useClass) === provider.useClass + ? provider + : useFactory(ngMocksUniverse.cacheMocks.get(provide) || provide, () => MockService(provider.useClass)); + } + if (!mockedProvider && Object.keys(provider).indexOf('useFactory') !== -1) { + mockedProvider = useFactory(ngMocksUniverse.cacheMocks.get(provide) || provide, () => ({})); + } + + return mockedProvider; +} diff --git a/lib/mock-service/mock-service.spec.ts b/lib/mock-service/mock-service.spec.ts index 6a51f26ccf..0bee447f55 100644 --- a/lib/mock-service/mock-service.spec.ts +++ b/lib/mock-service/mock-service.spec.ts @@ -4,13 +4,14 @@ import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { InjectionToken, NgModule } from '@angular/core'; import { TestBed } from '@angular/core/testing'; -import { MockBuilder, NG_INTERCEPTORS } from 'ng-mocks'; -import { NG_GUARDS } from '../common'; +import { NG_GUARDS, NG_INTERCEPTORS } from '../common/core.tokens'; import { ngMocksUniverse } from '../common/ng-mocks-universe'; -import { ngMocks } from '../mock-helper'; +import { MockBuilder } from '../mock-builder/mock-builder'; +import { ngMocks } from '../mock-helper/mock-helper'; -import { MockService, mockServiceHelper } from './mock-service'; +import { MockService } from './mock-service'; +import mockServiceHelper from './helper'; class DeepParentClass { public deepParentMethodName = 'deepParentMethod'; diff --git a/lib/mock-service/mock-service.ts b/lib/mock-service/mock-service.ts index aa89084304..ed45ff6d6d 100644 --- a/lib/mock-service/mock-service.ts +++ b/lib/mock-service/mock-service.ts @@ -1,448 +1,9 @@ // tslint:disable:no-misused-new unified-signatures -import { Injector, Provider } from '@angular/core'; - -import { isNgInjectionToken, NG_GUARDS, NG_INTERCEPTORS } from '../common'; -import { ngMocksUniverse } from '../common/ng-mocks-universe'; -import { MockProvider } from '../mock-module'; - -export type MockedFunction = () => any; - -/* istanbul ignore next */ -const getGlobal = (): any => window || global; - -const isFunc = (value: any): boolean => { - if (typeof value !== 'function') { - return false; - } - const proto = value.toString(); - /* istanbul ignore next */ - if (proto.match(/^\(/) !== null) { - return true; - } - return proto.match(/^function\s*\(/) !== null; -}; - -const isClass = (value: any): boolean => { - if (typeof value !== 'function') { - return false; - } - if (isFunc(value)) { - return false; - } - const proto = value.toString(); - /* istanbul ignore next */ - if (proto.match(/^class\b/) !== null) { - return true; - } - return proto.match(/^function\s*\(/) === null; -}; - -const isInst = (value: any): boolean => { - if (value === null) { - return false; - } - if (typeof value !== 'object') { - return false; - } - if (value.ngMetadataName === 'InjectionToken') { - return false; - } - return typeof Object.getPrototypeOf(value) === 'object'; -}; - -let customMockFunction: ((mockName: string) => MockedFunction) | undefined; - -const mockServiceHelperPrototype = { - mockFunction: (mockName: string, original: boolean = false): MockedFunction => { - let func: any; - if (customMockFunction && !original) { - func = customMockFunction(mockName); - } else { - func = (val: any) => { - if (setValue) { - setValue(val); - } - return value; - }; - } - - // magic to make getters / setters working - - let value: any; - let setValue: any; - - func.__ngMocks = true; - func.__ngMocksSet = (newSetValue: any) => (setValue = newSetValue); - func.__ngMocksGet = (newValue: any) => (value = newValue); - - return func; - }, - - registerMockFunction: (mockFunction: typeof customMockFunction) => { - customMockFunction = mockFunction; - }, - - createMockFromPrototype: (service: any): { [key in keyof any]: MockedFunction } => { - const mockName = `${service && service.constructor ? service.constructor.name : 'unknown'}`; - const value: any = {}; - - const methods = mockServiceHelperPrototype.extractMethodsFromPrototype(service); - for (const method of methods) { - mockServiceHelperPrototype.mock(value, method, mockName); - } - - const properties = mockServiceHelperPrototype.extractPropertiesFromPrototype(service); - for (const property of properties) { - mockServiceHelperPrototype.mock(value, property, 'get', mockName); - mockServiceHelperPrototype.mock(value, property, 'set', mockName); - } - - if (typeof value === 'object' && typeof service === 'object') { - Object.setPrototypeOf(value, service); - } - - return value; - }, - - extractMethodsFromPrototype: (service: T): string[] => { - const result: string[] = []; - let prototype = service; - while (prototype && Object.getPrototypeOf(prototype) !== null) { - for (const method of Object.getOwnPropertyNames(prototype)) { - if ((method as any) === 'constructor') { - continue; - } - - const descriptor = Object.getOwnPropertyDescriptor(prototype, method); - const isGetterSetter = descriptor && (descriptor.get || descriptor.set); - if (isGetterSetter || result.indexOf(method) !== -1) { - continue; - } - result.push(method); - } - prototype = Object.getPrototypeOf(prototype); - } - return result; - }, - - extractPropertiesFromPrototype: (service: T): string[] => { - const result: string[] = []; - let prototype = service; - while (prototype && Object.getPrototypeOf(prototype) !== null) { - for (const prop of Object.getOwnPropertyNames(prototype)) { - if ((prop as any) === 'constructor') { - continue; - } - - const descriptor = Object.getOwnPropertyDescriptor(prototype, prop); - const isGetterSetter = descriptor && (descriptor.get || descriptor.set); - if (!isGetterSetter || result.indexOf(prop) !== -1) { - continue; - } - result.push(prop); - } - prototype = Object.getPrototypeOf(prototype); - } - return result; - }, - - extractPropertyDescriptor: (service: T, prop: string): PropertyDescriptor | undefined => { - let prototype = service; - while (prototype && Object.getPrototypeOf(prototype) !== null) { - const descriptor = Object.getOwnPropertyDescriptor(prototype, prop); - if (descriptor) { - return descriptor; - } - prototype = Object.getPrototypeOf(prototype); - } - }, - - mock: (instance: any, name: string, ...args: string[]): T => { - let accessType: 'get' | 'set' | undefined; - let mockName: string | undefined; - - if (args.length && args[0] !== 'get' && args[0] !== 'set') { - mockName = args[0]; - } else if (args.length && (args[0] === 'get' || args[0] === 'set')) { - accessType = args[0] as any; - mockName = args[1]; - } - - const def = Object.getOwnPropertyDescriptor(instance, name); - if (def && def[accessType || 'value']) { - return def[accessType || 'value']; - } - - /* istanbul ignore next */ - const detectedMockName = `${ - mockName - ? mockName - : typeof instance.prototype === 'function' - ? instance.prototype.name - : typeof instance.constructor === 'function' - ? instance.constructor.name - : 'unknown' - }.${name}${accessType ? `:${accessType}` : ''}`; - const mock: any = mockServiceHelperPrototype.mockFunction(detectedMockName, !!accessType); - - const mockDef: PropertyDescriptor = { - // keeping setter if we adding getter - ...(accessType === 'get' && def && def.set - ? { - set: def.set, - } - : {}), - - // keeping getter if we adding setter - ...(accessType === 'set' && def && def.get - ? { - get: def.get, - } - : {}), - - // to allow replacement for functions - ...(accessType - ? {} - : { - writable: true, - }), - - [accessType || 'value']: mock, - configurable: true, - enumerable: true, - }; - - if (mockDef.get && mockDef.set && (mockDef.get as any).__ngMocks && (mockDef.set as any).__ngMocks) { - (mockDef.set as any).__ngMocksSet((val: any) => (mockDef.get as any).__ngMocksGet(val)); - } - - Object.defineProperty(instance, name, mockDef); - return mock; - }, - - replaceWithMocks(value: any): any { - if (ngMocksUniverse.cacheMocks.has(value)) { - return ngMocksUniverse.cacheMocks.get(value); - } - if (typeof value !== 'object') { - return value; - } - - let mocked: any; - let updated = false; - - if (Array.isArray(value)) { - mocked = []; - for (const valueItem of value) { - if (ngMocksUniverse.builder.has(valueItem) && ngMocksUniverse.builder.get(valueItem) === null) { - updated = updated || true; - continue; - } - mocked.push(mockServiceHelper.replaceWithMocks(valueItem)); - updated = updated || mocked[mocked.length - 1] !== valueItem; - } - } else if (value) { - mocked = {}; - for (const key of Object.keys(value)) { - if (ngMocksUniverse.builder.has(value[key]) && ngMocksUniverse.builder.get(value[key]) === null) { - updated = updated || true; - continue; - } - mocked[key] = mockServiceHelper.replaceWithMocks(value[key]); - updated = updated || mocked[key] !== value[key]; - } - - // Removal of guards. - for (const section of ['canActivate', 'canActivateChild', 'canDeactivate', 'canLoad']) { - if (!Array.isArray(mocked[section])) { - continue; - } - - const guards: any[] = []; - for (const guard of mocked[section]) { - if (ngMocksUniverse.builder.has(guard) && ngMocksUniverse.builder.get(guard) !== null) { - guards.push(guard); - continue; - } - if (ngMocksUniverse.builder.has(NG_GUARDS) && ngMocksUniverse.builder.get(NG_GUARDS) === null) { - continue; - } - guards.push(guard); - } - if (mocked[section].length !== guards.length) { - updated = updated || true; - mocked = { - ...mocked, - [section]: guards, - }; - } - } - } - - if (updated) { - Object.setPrototypeOf(mocked, Object.getPrototypeOf(value)); - return mocked; - } - return value; - }, - - // tries to resolve a provider based on current universe state. - resolveProvider: (def: any, resolutions: Map, changed?: (flag: boolean) => void) => { - const provider = typeof def === 'object' && def.provide ? def.provide : def; - const multi = def !== provider && !!def.multi; - - // we shouldn't touch our system providers. - if (typeof def === 'object' && def.useExisting && def.useExisting.__ngMocksSkip) { - return def; - } - - let mockedDef: typeof def; - if (resolutions.has(provider)) { - mockedDef = resolutions.get(provider); - // A case when a provider is actually a component, directive, pipe. - if (typeof mockedDef === 'function') { - mockedDef = { - provide: provider, - useClass: mockedDef, - }; - } - return multi && typeof mockedDef === 'object' ? { ...mockedDef, multi } : mockedDef; - } - - // we shouldn't touch excluded providers. - if (ngMocksUniverse.builder.has(provider) && ngMocksUniverse.builder.get(provider) === null) { - /* istanbul ignore else */ - if (changed) { - changed(true); - } - return; - } - - if ( - ngMocksUniverse.builder.has(NG_INTERCEPTORS) && - ngMocksUniverse.builder.get(NG_INTERCEPTORS) === null && - isNgInjectionToken(provider) && - provider.toString() === 'InjectionToken HTTP_INTERCEPTORS' && - provider !== def - ) { - if (def.useFactory || def.useValue) { - /* istanbul ignore else */ - if (changed) { - changed(true); - } - return; - } - const interceptor = def.useExisting || def.useClass; - if (!ngMocksUniverse.builder.has(interceptor) || ngMocksUniverse.builder.get(interceptor) === null) { - /* istanbul ignore else */ - if (changed) { - changed(true); - } - return; - } - } - - // Then we check decisions whether we should keep or replace a def. - if (!mockedDef && ngMocksUniverse.builder.has(provider)) { - mockedDef = ngMocksUniverse.builder.get(provider); - if (mockedDef === provider) { - mockedDef = def; - } else if (mockedDef === undefined) { - mockedDef = { - provide: provider, - useValue: undefined, - }; - } - } - - if (!mockedDef && ngMocksUniverse.flags.has('skipMock')) { - mockedDef = def; - } - 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); - } - let differs = false; - if (def === provider && mockedDef !== def) { - differs = true; - } else if ( - def !== provider && - (!mockedDef || - def.provide !== mockedDef.provide || - def.useValue !== mockedDef.useValue || - def.useClass !== mockedDef.useClass || - def.useExisting !== mockedDef.useExisting || - def.useFactory !== mockedDef.useFactory || - def.deps !== mockedDef.deps) - ) { - differs = true; - } - if (changed && differs) { - changed(true); - } - - // Touching only when we really provide a value. - if (mockedDef) { - ngMocksUniverse.touches.add(provider); - } - - return multi && typeof mockedDef === 'object' ? { ...mockedDef, multi } : mockedDef; - }, - - useFactory(def: D, mock: () => I): Provider { - return { - deps: [Injector], - provide: def, - useFactory: (injector?: Injector) => { - const instance = mock(); - const config = ngMocksUniverse.config.get(def); - if (injector && instance && config && config.init) { - config.init(instance, injector); - } - return instance; - }, - }; - }, -}; - -// We need a single pointer to the object among all environments. -getGlobal().ngMocksMockServiceHelper = getGlobal().ngMocksMockServiceHelper || mockServiceHelperPrototype; - -const localHelper: typeof mockServiceHelperPrototype = getGlobal().ngMocksMockServiceHelper; - -/** - * DO NOT USE this object outside of the library. - * It can be changed any time without a notice. - * - * @internal - */ -export const mockServiceHelper: { - extractMethodsFromPrototype(service: any): string[]; - extractPropertiesFromPrototype(service: any): string[]; - extractPropertyDescriptor(service: any, prop: string): PropertyDescriptor | undefined; - mock(instance: any, name: string, style: 'get' | 'set', mockName?: string): T; - mock(instance: any, name: string, mockName?: string): T; - mockFunction(mockName: string): MockedFunction; - registerMockFunction(mockFunction: (mockName: string) => MockedFunction | undefined): void; - replaceWithMocks(value: any): any; - resolveProvider(def: Provider, resolutions: Map, changed?: (flag: boolean) => void): Provider; - useFactory(def: D, instance: () => I): Provider; -} = getGlobal().ngMocksMockServiceHelper; +import isClass from './func.is-class'; +import isFunc from './func.is-func'; +import isInst from './func.is-inst'; +import mockServiceHelper from './helper'; /** * @see https://github.com/ike18t/ng-mocks#how-to-mock-a-service @@ -462,13 +23,15 @@ export function MockService(service: any, mockNamePrefix?: string): any { // mocking all methods / properties of a class / object. let value: any; if (isClass(service)) { - value = localHelper.createMockFromPrototype(service.prototype); + value = mockServiceHelper.createMockFromPrototype(service.prototype); } else if (isFunc(service)) { - value = localHelper.mockFunction(`func:${mockNamePrefix ? mockNamePrefix : service.name || 'arrow-function'}`); + value = mockServiceHelper.mockFunction( + `func:${mockNamePrefix ? mockNamePrefix : service.name || 'arrow-function'}` + ); } else if (Array.isArray(service)) { value = []; } else if (isInst(service)) { - value = localHelper.createMockFromPrototype(service.constructor.prototype); + value = mockServiceHelper.createMockFromPrototype(service.constructor.prototype); for (const property of Object.keys(service)) { const mock: any = MockService(service[property], `${mockNamePrefix ? mockNamePrefix : 'instance'}.${property}`); if (mock !== undefined) { diff --git a/lib/mock-service/types.ts b/lib/mock-service/types.ts new file mode 100644 index 0000000000..ee279f87c3 --- /dev/null +++ b/lib/mock-service/types.ts @@ -0,0 +1,3 @@ +export type MockedFunction = (...args: any[]) => any; + +export type CustomMockFunction = (mockName: string) => MockedFunction; diff --git a/tests/issue-145/components.spec.ts b/tests/issue-145/components.spec.ts index 05302b7b08..99113cbf95 100644 --- a/tests/issue-145/components.spec.ts +++ b/tests/issue-145/components.spec.ts @@ -1,7 +1,7 @@ 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'; +import { directiveResolver } from 'ng-mocks/dist/lib/common/core.reflect'; @Component({ selector: 'component', diff --git a/tests/issue-145/directives.spec.ts b/tests/issue-145/directives.spec.ts index 9843d88172..e71b80ba89 100644 --- a/tests/issue-145/directives.spec.ts +++ b/tests/issue-145/directives.spec.ts @@ -1,7 +1,7 @@ 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'; +import { directiveResolver } from 'ng-mocks/dist/lib/common/core.reflect'; @Directive({ selector: 'directive', diff --git a/tests/multi-tokens/test.spec.ts b/tests/multi-tokens/test.spec.ts index 74e554a5df..2e196a88d8 100644 --- a/tests/multi-tokens/test.spec.ts +++ b/tests/multi-tokens/test.spec.ts @@ -1,6 +1,6 @@ import { Inject, Injectable, InjectionToken, NgModule } from '@angular/core'; import { TestBed } from '@angular/core/testing'; -import { getTestBedInjection, MockBuilder } from 'ng-mocks'; +import { getInjection, MockBuilder } from 'ng-mocks'; const TARGET_TOKEN = new InjectionToken('MY_TOKEN_SINGLE'); @@ -66,12 +66,8 @@ describe('multi-tokens:real', () => { ); it('returns all provided tokens', () => { - const service = getTestBedInjection(TargetService); - if (service) { - expect(service.tokens).toEqual(['1', '2', '3', '4', '5', '6']); - } else { - fail('Cannot find TargetService'); - } + const service = getInjection(TargetService); + expect(service.tokens).toEqual(['1', '2', '3', '4', '5', '6']); }); }); @@ -93,12 +89,8 @@ describe('multi-tokens:builder', () => { ); it('returns all provided tokens', () => { - const service = getTestBedInjection(TargetService); - if (service) { - expect(service.tokens).toEqual(['1', '2', '3', '4', '5', '6']); - } else { - fail('Cannot find TargetService'); - } + const service = getInjection(TargetService); + expect(service.tokens).toEqual(['1', '2', '3', '4', '5', '6']); }); }); @@ -120,11 +112,7 @@ describe('multi-tokens:builder:mock', () => { ); it('returns all provided tokens', () => { - const service = getTestBedInjection(TargetService); - if (service) { - expect(service.tokens).toEqual(['1', '2', '5', '6']); - } else { - fail('Cannot find TargetService'); - } + const service = getInjection(TargetService); + expect(service.tokens).toEqual(['1', '2', '5', '6']); }); }); diff --git a/tests/provider-with-custom-dependencies/test.spec.ts b/tests/provider-with-custom-dependencies/test.spec.ts new file mode 100644 index 0000000000..025aed5db3 --- /dev/null +++ b/tests/provider-with-custom-dependencies/test.spec.ts @@ -0,0 +1,104 @@ +import { Component, Injectable as InjectableSource, NgModule, Optional } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { MockBuilder, MockRender } from 'ng-mocks'; + +// Because of A5 we need to cast Injectable to any type. +// But because of A10+ we need to do it via a middle function. +function Injectable(...args: any[]): any { + return InjectableSource(...args); +} + +@Injectable({ + providedIn: 'root', +}) +class Dep1Service { + public readonly name = 'dep-1'; +} + +@Injectable({ + providedIn: 'root', +}) +class Dep2Service { + public readonly name = 'dep-2'; +} + +// Not root. +@Injectable() +class Dep3Service { + public readonly name = 'dep-3'; +} + +@Injectable() +class TargetService { + public readonly optional?: { name: string }; + public readonly service: { name: string }; + + constructor(service: Dep1Service, optional: Dep1Service) { + this.service = service; + this.optional = optional; + } +} + +@Component({ + selector: 'target', + template: ` + "service:{{ service.service ? service.service.name : 'missed' }}" "optional:{{ + service.optional ? service.optional.name : 'missed' + }}" + `, +}) +class TargetComponent { + public readonly service: TargetService; + + constructor(service: TargetService) { + this.service = service; + } +} + +@NgModule({ + declarations: [TargetComponent], + exports: [TargetComponent], + providers: [ + { + deps: [Dep2Service, [new Optional(), Dep3Service]], + provide: TargetService, + useClass: TargetService, + }, + ], +}) +class TargetModule {} + +xdescribe('provider-with-custom-dependencies', () => { + describe('real', () => { + beforeEach(() => + TestBed.configureTestingModule({ + imports: [TargetModule], + }).compileComponents() + ); + + it('creates component with custom dependencies', () => { + const fixture = TestBed.createComponent(TargetComponent); + fixture.detectChanges(); + // Injects root dependency correctly. + expect(fixture.nativeElement.innerHTML).toContain('"service:dep-2"'); + // Skips unprovided local dependency. + expect(fixture.nativeElement.innerHTML).toContain('"optional:missed"'); + // The dependency should not be provided in TestBed. + expect(() => TestBed.get(Dep3Service)).toThrowError(/No provider for Dep3Service/); + }); + }); + + describe('mock-builder', () => { + beforeEach(() => MockBuilder(TargetComponent, TargetModule).keep(TargetService)); + + it('creates component with mocked custom dependencies', () => { + const fixture = MockRender(TargetComponent); + // Injects root dependency correctly, it is not missed, it is mocked. + expect(fixture.nativeElement.innerHTML).toContain('"service:"'); + // Skips unprovided local dependency despite its mocked copy. + expect(fixture.nativeElement.innerHTML).toContain('"optional:missed"'); + // The dependency should not be provided in TestBed. + expect(() => TestBed.get(Dep3Service)).toThrowError(/No provider for Dep3Service/); + }); + }); +}); diff --git a/tests/root-provider-in-depths/test.spec.ts b/tests/root-provider-in-depths/test.spec.ts new file mode 100644 index 0000000000..6a499f3231 --- /dev/null +++ b/tests/root-provider-in-depths/test.spec.ts @@ -0,0 +1,72 @@ +import { Component, Injectable, NgModule } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { MockBuilder, MockRender } from 'ng-mocks'; + +@Injectable() +class Nested1Service { + public readonly name = 'nested-1'; +} + +@Injectable() +class Nested2Service { + public readonly name = 'nested-2'; +} + +@Injectable() +class Nested3Service { + public readonly name = 'nested-3'; +} + +@Injectable() +class Nested4Service { + public readonly name = 'nested-4'; +} + +@Injectable() +class TargetService { + public readonly name = 'target'; +} + +@Component({ + selector: 'target', + template: `{{ service.name }}`, +}) +class TargetComponent { + public readonly service: TargetService; + + constructor(service: TargetService) { + this.service = service; + } +} + +@NgModule({ + declarations: [TargetComponent], + exports: [TargetComponent], + providers: [Nested1Service, [Nested2Service, [Nested3Service, [Nested4Service, [TargetService]]]]], +}) +class TargetModule {} + +describe('root-provider-in-depths', () => { + describe('real', () => { + beforeEach(() => + TestBed.configureTestingModule({ + imports: [TargetModule], + }).compileComponents() + ); + + it('creates component with very nested service', () => { + const fixture = TestBed.createComponent(TargetComponent); + fixture.detectChanges(); + expect(fixture.nativeElement.innerHTML).toEqual('target'); + }); + }); + + describe('mock-builder', () => { + beforeEach(() => MockBuilder(TargetComponent, TargetModule).keep(TargetService)); + + it('creates component with very nested service', () => { + const fixture = MockRender(TargetComponent); + expect(fixture.point.nativeElement.innerHTML).toEqual('target'); + }); + }); +}); diff --git a/tslint.json b/tslint.json index 111a8ba1e8..289ea3b140 100644 --- a/tslint.json +++ b/tslint.json @@ -13,6 +13,8 @@ "newline-before-return": false, "newline-per-chained-call": false, "no-any": false, + "no-default-export": false, + "no-default-import": false, "no-disabled-tests": true, "no-empty": false, "no-floating-promises": false, @@ -30,6 +32,7 @@ "only-arrow-functions": false, "quotemark": [true, "single"], "semicolon": [true, "always", "strict-bound-class-methods"], + "space-before-function-paren": false, "strict-boolean-expressions": false, "strict-comparisons": [true, { "allow-object-equal-comparison": true }], "trailing-comma": [true, { "esSpecCompliant": true }],