Skip to content

Commit

Permalink
feat(core): hidden usage of MockBuilder in TestBed if kept and mock m…
Browse files Browse the repository at this point in the history
…odules are used together #4344
  • Loading branch information
satanTime committed Dec 11, 2022
1 parent e7400c5 commit d77b6f2
Show file tree
Hide file tree
Showing 26 changed files with 518 additions and 57 deletions.
9 changes: 2 additions & 7 deletions libs/ng-mocks/src/lib/common/func.extract-deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import coreConfig from './core.config';
import { flatten } from './core.helpers';
import { AnyDeclaration } from './core.types';
import { getNgType } from './func.get-ng-type';
import funcGetProvider from './func.get-provider';
import { isNgModuleDefWithProviders } from './func.is-ng-module-def-with-providers';
import funcGetType from './func.get-type';

export const funcExtractDeps = (def: any, result: Set<AnyDeclaration<any>>): Set<AnyDeclaration<any>> => {
const meta = collectDeclarations(def);
Expand All @@ -23,11 +22,7 @@ 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.
if (isNgModuleDefWithProviders(item)) {
result.add(item.ngModule);
} else {
result.add(funcGetProvider(item));
}
result.add(funcGetType(item));
}
}

Expand Down
3 changes: 0 additions & 3 deletions libs/ng-mocks/src/lib/common/func.get-provider.ts

This file was deleted.

9 changes: 9 additions & 0 deletions libs/ng-mocks/src/lib/common/func.get-type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { isNgModuleDefWithProviders } from './func.is-ng-module-def-with-providers';

export default (provider: any): any => {
return provider && typeof provider === 'object' && provider.provide
? provider.provide
: isNgModuleDefWithProviders(provider)
? provider.ngModule
: provider;
};
64 changes: 54 additions & 10 deletions libs/ng-mocks/src/lib/common/ng-mocks-global-overrides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Injector, ViewContainerRef } from '@angular/core';
import { getTestBed, MetadataOverride, TestBed, TestBedStatic, TestModuleMetadata } from '@angular/core/testing';

import funcExtractTokens from '../mock-builder/func.extract-tokens';
import { MockBuilder } from '../mock-builder/mock-builder';
import getOverrideDef from '../mock-builder/promise/get-override-def';
import { ngMocks } from '../mock-helper/mock-helper';
import mockHelperFasterInstall from '../mock-helper/mock-helper.faster-install';
Expand All @@ -15,9 +16,11 @@ import coreInjector from './core.injector';
import coreReflectMeta from './core.reflect.meta';
import coreReflectModuleResolve from './core.reflect.module-resolve';
import coreReflectProvidedIn from './core.reflect.provided-in';
import { NG_MOCKS, NG_MOCKS_TOUCHES } from './core.tokens';
import { NG_MOCKS, NG_MOCKS_ROOT_PROVIDERS, NG_MOCKS_TOUCHES } from './core.tokens';
import { AnyType, dependencyKeys } from './core.types';
import funcGetProvider from './func.get-provider';
import { getSourceOfMock } from './func.get-source-of-mock';
import funcGetType from './func.get-type';
import { isMockNgDef } from './func.is-mock-ng-def';
import { isNgDef } from './func.is-ng-def';
import { isNgModuleDefWithProviders } from './func.is-ng-module-def-with-providers';
import ngMocksUniverse from './ng-mocks-universe';
Expand Down Expand Up @@ -71,10 +74,9 @@ const initTestBed = () => {
const generateTouches = (moduleDef: Partial<Record<dependencyKeys, any>>, touches: Set<any>): void => {
for (const key of coreConfig.dependencies) {
for (const item of moduleDef[key] ? flatten(moduleDef[key]) : []) {
let def = funcGetProvider(item);
if (isNgModuleDefWithProviders(def)) {
generateTouches(def, touches);
def = def.ngModule;
const def = funcGetType(item);
if (isNgModuleDefWithProviders(item)) {
generateTouches(item, touches);
}
if (touches.has(def)) {
continue;
Expand Down Expand Up @@ -117,7 +119,7 @@ const defineTouches = (testBed: TestBed, moduleDef: TestModuleMetadata, knownTou
};

const applyPlatformOverrideDef = (def: any) => {
const ngModule = isNgModuleDefWithProviders(def) ? /* istanbul ignore next */ def.ngModule : def;
const ngModule = funcGetType(def);
if ((TestBed as any).ngMocksOverrides.has(ngModule)) {
return;
}
Expand Down Expand Up @@ -183,13 +185,55 @@ const configureTestingModule =
(moduleDef: TestModuleMetadata) => {
initTestBed();

const useMockBuilder =
typeof moduleDef === 'object' &&
!!moduleDef &&
(!moduleDef.providers || moduleDef.providers.indexOf(MockBuilder) === -1);
// 0b10 - mock exist
// 0b01 - real exist
let hasMocks = 0;
const mockBuilder: Array<[any, boolean]> = [];
for (const key of useMockBuilder ? ['imports', 'declarations'] : []) {
for (const declaration of flatten(moduleDef[key as never]) as any[]) {
if (!declaration) {
continue;
}
mockBuilder.push([
isNgModuleDefWithProviders(declaration)
? {
ngModule: getSourceOfMock(declaration.ngModule),
providers: declaration.providers,
}
: getSourceOfMock(declaration),
isMockNgDef(funcGetType(declaration)),
]);
if (key === 'imports') {
hasMocks |= mockBuilder[mockBuilder.length - 1][1] ? 0b10 : 0b01;
}
}
}
// We should do magic only then both mock and real exist.
let finalModuleDef = hasMocks === 0b11 ? undefined : moduleDef;
if (!finalModuleDef) {
let builder = MockBuilder(NG_MOCKS_ROOT_PROVIDERS);
for (const [def, isMock] of mockBuilder) {
builder = isMock ? builder.mock(def) : builder.keep(def);
}
finalModuleDef = builder.build();
finalModuleDef = {
...moduleDef,
...finalModuleDef,
providers: [...(moduleDef.providers ?? []), ...(finalModuleDef.providers as never)],
};
}

const testBed = getTestBed();

const providers = funcExtractTokens(moduleDef.providers);
const providers = funcExtractTokens(finalModuleDef.providers);
const { mocks, overrides } = providers;
// touches are important,
// therefore we are trying to fetch them from the known providers.
const touches = defineTouches(testBed, moduleDef, providers.touches);
const touches = defineTouches(testBed, finalModuleDef, providers.touches);

if (mocks) {
ngMocks.flushTestBed();
Expand All @@ -205,7 +249,7 @@ const configureTestingModule =
applyPlatformOverrides(testBed, touches);
}

return original.call(instance, moduleDef);
return original.call(instance, finalModuleDef);
};

const resetTestingModule =
Expand Down
2 changes: 2 additions & 0 deletions libs/ng-mocks/src/lib/mock-builder/mock-builder.promise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { isNgDef } from '../common/func.is-ng-def';
import { isNgModuleDefWithProviders } from '../common/func.is-ng-module-def-with-providers';
import ngMocksUniverse from '../common/ng-mocks-universe';

import { MockBuilder } from './mock-builder';
import { MockBuilderStash } from './mock-builder-stash';
import addRequestedProviders from './promise/add-requested-providers';
import applyPlatformModules from './promise/apply-platform-modules';
Expand Down Expand Up @@ -87,6 +88,7 @@ export class MockBuilderPromise implements IMockBuilder {
createNgMocksToken(),
createNgMocksTouchesToken(),
createNgMocksOverridesToken(this.replaceDef, this.defValue),
MockBuilder as never,
);

return ngModule;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { TestModuleMetadata } from '@angular/core/testing';

export default (
ngModule: TestModuleMetadata,
): {
): TestModuleMetadata & {
declarations: any[];
imports: any[];
providers: any[];
} => ({
...ngModule,
declarations: [...(ngModule.declarations || /* istanbul ignore next */ [])],
imports: [...(ngModule.imports || /* istanbul ignore next */ [])],
providers: [...(ngModule.providers || /* istanbul ignore next */ [])],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import CoreDefStack from '../../common/core.def-stack';
import { extractDependency, flatten, mapValues } from '../../common/core.helpers';
import coreReflectProvidedIn from '../../common/core.reflect.provided-in';
import funcGetProvider from '../../common/func.get-provider';
import funcGetType from '../../common/func.get-type';
import ngMocksUniverse from '../../common/ng-mocks-universe';
import helperResolveProvider from '../../mock-service/helper.resolve-provider';

Expand All @@ -15,7 +15,7 @@ export default (ngModule: NgMeta, { providerDef, mockDef }: BuilderData, resolut

// Analyzing providers.
for (const provider of flatten(ngModule.providers)) {
const provide = funcGetProvider(provider);
const provide = funcGetType(provider);
ngMocksUniverse.touches.add(provide);

if (provide !== provider && (provider as any).deps) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
import { getTestBed } from '@angular/core/testing';

import { flatten } from '../../common/core.helpers';
import funcGetType from '../../common/func.get-type';
import ngMocksUniverse from '../../common/ng-mocks-universe';

export default () => {
const testBed = getTestBed();
// istanbul ignore else
if (testBed.ngModule) {
for (const def of flatten<any>(testBed.ngModule)) {
// istanbul ignore if
if (typeof def === 'object' && /* istanbul ignore next */ def.ngModule) {
ngMocksUniverse.touches.add(def.ngModule);
} else {
ngMocksUniverse.touches.add(def);
}
ngMocksUniverse.touches.add(funcGetType(def));
}
}
};
2 changes: 1 addition & 1 deletion libs/ng-mocks/src/lib/mock-builder/promise/init-modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default (

const isModule = isNgDef(def, 'm');
if (providers.length > 0) {
const [, loDef] = mockNgDef({ providers, skipMarkProviders: !isModule });
const [, loDef] = mockNgDef({ providers, skipMarkProviders: !isModule, skipExports: true });
loProviders.set(def, loDef.providers);
}
if (isModule) {
Expand Down
6 changes: 4 additions & 2 deletions libs/ng-mocks/src/lib/mock-builder/promise/init-ng-modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import coreReflectProvidedIn from '../../common/core.reflect.provided-in';
import { AnyDeclaration } from '../../common/core.types';
import errorJestMock from '../../common/error.jest-mock';
import funcGetName from '../../common/func.get-name';
import funcGetProvider from '../../common/func.get-provider';
import funcGetType from '../../common/func.get-type';
import { isNgDef } from '../../common/func.is-ng-def';
import { isNgInjectionToken } from '../../common/func.is-ng-injection-token';
import { isStandalone } from '../../common/func.is-standalone';
Expand Down Expand Up @@ -31,7 +31,7 @@ const handleDef = ({ imports, declarations, providers }: NgMeta, def: any, defPr
// adding providers to touches
if (typeof extendedDef === 'object' && extendedDef.providers) {
for (const provider of flatten(extendedDef.providers)) {
ngMocksUniverse.touches.add(funcGetProvider(provider));
ngMocksUniverse.touches.add(funcGetType(provider));
}
}
}
Expand Down Expand Up @@ -70,6 +70,8 @@ export default (
if (!config.dependency && config.export && !configInstance?.exported && (isNgDef(def, 'i') || !isNgDef(def))) {
handleDef(meta, def, defProviders);
markProviders([def]);
} else if (!config.dependency && config.export && !configInstance?.exported) {
handleDef(meta, def, defProviders);
} else if (!ngMocksUniverse.touches.has(def) && !config.dependency) {
handleDef(meta, def, defProviders);
} else if (
Expand Down
4 changes: 2 additions & 2 deletions libs/ng-mocks/src/lib/mock-builder/promise/parse-provider.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import funcGetProvider from '../../common/func.get-provider';
import funcGetType from '../../common/func.get-type';

export default (
provider: any,
): {
multi: boolean;
provide: any;
} => {
const provide = funcGetProvider(provider);
const provide = funcGetType(provider);
const multi = provide !== provider && provider.multi;

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { DebugNode, Directive } from '@angular/core';

import coreInjector from '../common/core.injector';
import coreReflectDirectiveResolve from '../common/core.reflect.directive-resolve';
import funcGetProvider from '../common/func.get-provider';
import funcGetType from '../common/func.get-type';

const getMeta = (token: any): Directive | undefined => {
try {
Expand All @@ -19,7 +19,7 @@ export default (el: DebugNode | null | undefined, token: any): Directive | undef
}

try {
const provider = funcGetProvider(token);
const provider = funcGetType(token);
const instance = coreInjector(provider, el.injector);

return getMeta(instance.constructor);
Expand Down
4 changes: 2 additions & 2 deletions libs/ng-mocks/src/lib/mock-helper/mock-helper.guts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { TestModuleMetadata } from '@angular/core/testing';
import CoreDefStack from '../common/core.def-stack';
import { flatten, mapKeys, mapValues } from '../common/core.helpers';
import coreReflectModuleResolve from '../common/core.reflect.module-resolve';
import funcGetProvider from '../common/func.get-provider';
import funcGetType from '../common/func.get-type';
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';
Expand Down Expand Up @@ -121,7 +121,7 @@ const handleDestructuring = (data: Data, def: any, callback: any): void => {
};

const resolveProvider = ({ skip, keep, providers, exclude }: Data, def: any): void => {
const provider = funcGetProvider(def);
const provider = funcGetType(def);
skip.add(provider);
if (exclude.has(provider)) {
return;
Expand Down
5 changes: 3 additions & 2 deletions libs/ng-mocks/src/lib/mock-module/create-resolvers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Provider } from '@angular/core';

import CoreDefStack from '../common/core.def-stack';
import funcGetType from '../common/func.get-type';
import { isNgDef } from '../common/func.is-ng-def';
import { isNgModuleDefWithProviders } from '../common/func.is-ng-module-def-with-providers';
import ngMocksUniverse from '../common/ng-mocks-universe';
Expand Down Expand Up @@ -41,7 +42,7 @@ const createResolveProvider =
helperMockService.resolveProvider(def, resolutions, change);

const createResolveWithProviders = (def: any, mockDef: any): boolean =>
mockDef && mockDef.ngModule && isNgModuleDefWithProviders(def);
isNgModuleDefWithProviders(mockDef) && isNgModuleDefWithProviders(def);

const createResolveExisting = (
def: any,
Expand Down Expand Up @@ -73,7 +74,7 @@ const createResolve =
return createResolveExisting(def, resolutions, change);
}

const detectedDef = isNgModuleDefWithProviders(def) ? def.ngModule : def;
const detectedDef = funcGetType(def);
if (ngMocksUniverse.isExcludedDef(detectedDef)) {
return createResolveExcluded(def, resolutions, change);
}
Expand Down
8 changes: 5 additions & 3 deletions libs/ng-mocks/src/lib/mock-module/mark-providers.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { flatten } from '../common/core.helpers';
import funcGetProvider from '../common/func.get-provider';
import funcGetType from '../common/func.get-type';
import ngMocksUniverse from '../common/ng-mocks-universe';

export default (providers?: any[]): void => {
for (const provider of flatten(providers ?? [])) {
const provide = funcGetProvider(provider);
const provide = funcGetType(provider);

const config = ngMocksUniverse.configInstance.get(provide) ?? {};
config.exported = true;
if (!config.exported) {
config.exported = true;
}
ngMocksUniverse.configInstance.set(provide, config);
}
};
2 changes: 1 addition & 1 deletion libs/ng-mocks/src/lib/mock-module/mock-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ const detectMockModule = (ngModule: Type<any>, mockModule?: Type<any>): Type<any

const getMockProviders = (ngModuleProviders: NgModule['providers']): NgModule['providers'] => {
if (ngModuleProviders) {
const [changed, ngModuleDef] = mockNgDef({ providers: ngModuleProviders });
const [changed, ngModuleDef] = mockNgDef({ providers: ngModuleProviders, skipExports: true });

return changed ? ngModuleDef.providers : ngModuleProviders;
}
Expand Down
Loading

0 comments on commit d77b6f2

Please sign in to comment.