Skip to content

Commit

Permalink
fix(#377): respect of providedIn in Injectable
Browse files Browse the repository at this point in the history
  • Loading branch information
satanTime committed Apr 12, 2021
1 parent 77e7d92 commit 91aba4b
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 3 deletions.
18 changes: 18 additions & 0 deletions libs/ng-mocks/src/lib/common/core.reflect.provided-in.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import coreReflectProvidedIn from './core.reflect.provided-in';

describe('core.reflect.provided-in', () => {
it('covers ngInjectableDef', () => {
expect(
coreReflectProvidedIn({
ngInjectableDef: {},
}),
).toEqual(undefined);
expect(
coreReflectProvidedIn({
ngInjectableDef: {
providedIn: 'root',
},
}),
).toEqual('root');
});
});
5 changes: 5 additions & 0 deletions libs/ng-mocks/src/lib/common/core.reflect.provided-in.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { AnyType } from './core.types';

export default (declaration: any): undefined | AnyType<any> | string => {
return declaration?.ɵprov?.providedIn ?? declaration?.ngInjectableDef?.providedIn;
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import addDefToRootProviderParameters from './add-def-to-root-provider-parameter
import checkRootProviderDependency from './check-root-provider-dependency';
import extractDep from './extract-dep';
import getRootProvidersData from './get-root-providers-data';
import handleProvidedInDependency from './handle-provided-in-dependency';
import skipRootProviderDependency from './skip-root-provider-dependency';
import { BuilderData } from './types';

Expand All @@ -19,6 +20,7 @@ export default (mockDef: BuilderData['mockDef']): Set<any> => {

for (const decorators of coreReflectJit().parameters(def)) {
const provide: any = extractDep(decorators);
handleProvidedInDependency(provide);
if (skipRootProviderDependency(provide)) {
continue;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import coreReflectProvidedIn from '../../common/core.reflect.provided-in';
import ngMocksUniverse from '../../common/ng-mocks-universe';

export default (provide: any): void => {
if (ngMocksUniverse.touches.has(provide)) {
return;
}

const providedIn = coreReflectProvidedIn(provide);
if (!providedIn) {
return;
}

if (ngMocksUniverse.config.get('ngMocksDepsSkip').has(providedIn)) {
ngMocksUniverse.config.get('ngMocksDepsSkip').add(provide);
}
};
6 changes: 3 additions & 3 deletions libs/ng-mocks/src/lib/mock-builder/promise/skip-dep.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { DOCUMENT } from '@angular/common';

import coreConfig from '../../common/core.config';
import coreReflectProvidedIn from '../../common/core.reflect.provided-in';
import { isNgInjectionToken } from '../../common/func.is-ng-injection-token';
import ngMocksUniverse from '../../common/ng-mocks-universe';

Expand All @@ -25,9 +26,8 @@ export default (provide: any): boolean => {
}

// Empty providedIn or things for a platform have to be skipped.
let skip = !provide.ɵprov?.providedIn || provide.ɵprov.providedIn === 'platform';
// istanbul ignore next: A6
skip = skip && (!provide.ngInjectableDef?.providedIn || provide.ngInjectableDef.providedIn === 'platform');
const providedIn = coreReflectProvidedIn(provide);
const skip = !providedIn || providedIn === 'platform';
if (typeof provide === 'function' && skip) {
return true;
}
Expand Down
91 changes: 91 additions & 0 deletions tests/issue-377/e2e.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import {
Component,
Inject,
Injectable as InjectableSource,
InjectionToken,
NgModule,
VERSION,
} from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { MockBuilder, MockRender, ngMocks } 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);
}

@NgModule({})
class TargetModule {}

@Injectable({
providedIn: TargetModule,
} as any)
class TargetService {
private readonly name = 'target';

public echo(): string {
return this.name;
}
}

// TODO Remove any with A5
const TOKEN = new (InjectionToken as any)('TOKEN', {
factory: () => 'TOKEN',
providedIn: TargetModule,
});

@Component({
selector: 'target',
template: `service:{{ service.echo() }} token:{{ token }}`,
})
class TargetComponent {
public constructor(
public readonly service: TargetService,
@Inject(TOKEN) public readonly token: string,
) {}
}

describe('issue-377', () => {
beforeEach(() => {
if (parseInt(VERSION.major, 10) <= 5) {
pending('Need Angular > 5');
}
});

describe('expected', () => {
beforeEach(() =>
TestBed.configureTestingModule({
declarations: [TargetComponent],
imports: [TargetModule],
}).compileComponents(),
);

it('sets TestBed correctly', () => {
const fixture = MockRender(TargetComponent);
expect(ngMocks.formatText(fixture)).toEqual(
'service:target token:TOKEN',
);
});
});

describe('keep', () => {
beforeEach(() => MockBuilder(TargetComponent).keep(TargetModule));

it('sets TestBed correctly', () => {
const fixture = MockRender(TargetComponent);
expect(ngMocks.formatText(fixture)).toEqual(
'service:target token:TOKEN',
);
});
});

describe('mock', () => {
beforeEach(() => MockBuilder(TargetComponent).mock(TargetModule));

it('sets TestBed correctly', () => {
const fixture = MockRender(TargetComponent);
expect(ngMocks.formatText(fixture)).toEqual('service: token:');
});
});
});
43 changes: 43 additions & 0 deletions tests/issue-377/test.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Component } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
import { MockBuilder, MockRender } from 'ng-mocks';

@Component({
selector: 'app-form',
template: `
<form [formGroup]="form">
<input type="text" formGroupName="name" />
</form>
`,
})
class FormComponent {
public form = this.fb.group({
name: [],
});

public constructor(private readonly fb: FormBuilder) {}
}

describe('issue-377:classic', () => {
beforeEach(() =>
TestBed.configureTestingModule({
declarations: [FormComponent],
imports: [ReactiveFormsModule],
}).compileComponents(),
);

it('sets TestBed correctly', () => {
expect(() => MockRender(FormComponent)).not.toThrow();
});
});

describe('issue-377:mock', () => {
beforeEach(() =>
MockBuilder(FormComponent).keep(ReactiveFormsModule),
);

it('sets TestBed correctly', () => {
expect(() => MockRender(FormComponent)).not.toThrow();
});
});

0 comments on commit 91aba4b

Please sign in to comment.