Skip to content

Commit

Permalink
Merge pull request #7439 from satanTime/issues/6402
Browse files Browse the repository at this point in the history
fix(MockBuilder): respects global rules as they would be chain calls #6402
  • Loading branch information
satanTime authored Nov 15, 2023
2 parents d6cadac + 23d9ba6 commit ffde5dd
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 11 deletions.
9 changes: 7 additions & 2 deletions libs/ng-mocks/src/lib/common/core.helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,18 @@ export const getInjection = <I>(token: AnyDeclaration<I>): I => {
return testBed.inject ? testBed.inject(token) : testBed.get(token);
};

export const flatten = <T>(values: T | T[], result: T[] = []): T[] => {
export const flatten = <T>(values: T | T[] | { ɵproviders: T[] }, result: T[] = []): T[] => {
if (Array.isArray(values)) {
for (const value of values) {
flatten(value, result);
}
} else if (values !== null && typeof values === 'object' && Array.isArray((values as any).ɵproviders)) {
for (const value of (values as any).ɵproviders) {
flatten(value, result);
}
} else {
result.push(values);
// any is needed to cover ɵproviders
result.push(values as any);
}

return result;
Expand Down
14 changes: 12 additions & 2 deletions libs/ng-mocks/src/lib/common/func.extract-deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import { AnyDeclaration } from './core.types';
import { getNgType } from './func.get-ng-type';
import funcGetType from './func.get-type';

export const funcExtractDeps = (def: any, result: Set<AnyDeclaration<any>>): Set<AnyDeclaration<any>> => {
export const funcExtractDeps = (
def: any,
result: Set<AnyDeclaration<any>>,
recursive = false,
): Set<AnyDeclaration<any>> => {
const meta = collectDeclarations(def);
const type = getNgType(def);
// istanbul ignore if
Expand All @@ -22,7 +26,13 @@ export const funcExtractDeps = (def: any, result: Set<AnyDeclaration<any>>): Set

for (const item of flatten(decorator[field])) {
// istanbul ignore if: it is here for standalone things, however they don't support modules with providers.
result.add(funcGetType(item));
const itemType = funcGetType(item);
if (!result.has(itemType)) {
result.add(itemType);
if (recursive) {
funcExtractDeps(itemType, result);
}
}
}
}

Expand Down
34 changes: 27 additions & 7 deletions libs/ng-mocks/src/lib/mock-builder/promise/init-universe.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { mapEntries, mapValues } from '../../common/core.helpers';
import { funcExtractDeps } from '../../common/func.extract-deps';
import ngMocksUniverse from '../../common/ng-mocks-universe';

import initExcludeDef from './init-exclude-def';
Expand Down Expand Up @@ -31,8 +32,20 @@ export default ({
const dependencies = initKeepDef(keepDef, configDef);
for (const dependency of mapValues(dependencies)) {
ngMocksUniverse.touches.add(dependency);

// MockBuilder has instruction about the dependency, skipping it.
}
for (const dependency of mapValues(keepDef)) {
dependencies.add(dependency);
funcExtractDeps(dependency, dependencies, true);
}
for (const dependency of mapValues(mockDef)) {
dependencies.add(dependency);
funcExtractDeps(dependency, dependencies, true);
}
for (const dependency of mapValues(replaceDef)) {
dependencies.add(dependency);
funcExtractDeps(dependency, dependencies, true);
}
for (const dependency of mapValues(dependencies)) {
if (configDef.has(dependency)) {
continue;
}
Expand All @@ -46,14 +59,21 @@ export default ({
keepDef.add(dependency);
} else if (resolution === 'exclude') {
excludeDef.add(dependency);
} else {
} else if (resolution === 'mock') {
mockDef.add(dependency);
} else if (ngMocksUniverse.touches.has(dependency)) {
mockDef.add(dependency);
}

configDef.set(dependency, {
dependency: true,
__internal: true,
});
configDef.set(
dependency,
ngMocksUniverse.touches.has(dependency)
? {
dependency: true,
__internal: true,
}
: {},
);
}

for (const [k, v] of mapEntries(configDef)) {
Expand Down
73 changes: 73 additions & 0 deletions tests/issue-6402/test.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { HttpClient, HttpClientModule } from '@angular/common/http';
import {
HttpClientTestingModule,
HttpTestingController,
} from '@angular/common/http/testing';
import { Injectable, NgModule } from '@angular/core';

import { MockBuilder, MockRender, ngMocks } from 'ng-mocks';

@Injectable()
class TargetService {
constructor(private http: HttpClient) {}

getConfig() {
return this.http.get('/api/config');
}
}

@NgModule({
imports: [HttpClientModule],
providers: [TargetService],
})
class TargetModule {}

// The issue was that MockBuilder didn't apply global rules to mocked declarations.
// @see https://github.com/help-me-mom/ng-mocks/issues/6402
describe('issue-6402', () => {
describe('MockBuilder:replace', () => {
beforeEach(() =>
MockBuilder(TargetService, TargetModule).replace(
HttpClientModule,
HttpClientTestingModule,
),
);

it('sends /api/config request', () => {
MockRender(TargetService);
const service = ngMocks.get(TargetService);
const controller = ngMocks.get(HttpTestingController);

service.getConfig().subscribe();

const expectation = controller.expectOne('/api/config');
expectation.flush([]);
controller.verify();
expect(expectation.request.method).toEqual('GET');
});
});

describe('ngMocks.globalReplace', () => {
beforeAll(() =>
ngMocks.globalReplace(
HttpClientModule,
HttpClientTestingModule,
),
);
beforeEach(() => MockBuilder(TargetService, TargetModule));
afterAll(() => ngMocks.globalWipe(HttpClientModule));

it('sends /api/config request', () => {
MockRender(TargetService);
const service = ngMocks.get(TargetService);
const controller = ngMocks.get(HttpTestingController);

service.getConfig().subscribe();

const expectation = controller.expectOne('/api/config');
expectation.flush([]);
controller.verify();
expect(expectation.request.method).toEqual('GET');
});
});
});

0 comments on commit ffde5dd

Please sign in to comment.