diff --git a/libs/ng-mocks/src/lib/common/func.extract-deps.ts b/libs/ng-mocks/src/lib/common/func.extract-deps.ts index a85e7f62f0..9f763c91a4 100644 --- a/libs/ng-mocks/src/lib/common/func.extract-deps.ts +++ b/libs/ng-mocks/src/lib/common/func.extract-deps.ts @@ -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>): Set> => { const meta = collectDeclarations(def); @@ -23,11 +22,7 @@ export const funcExtractDeps = (def: any, result: Set>): 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)); } } diff --git a/libs/ng-mocks/src/lib/common/func.get-provider.ts b/libs/ng-mocks/src/lib/common/func.get-provider.ts deleted file mode 100644 index 6ca2d262fd..0000000000 --- a/libs/ng-mocks/src/lib/common/func.get-provider.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default (provider: any): any => { - return provider && typeof provider === 'object' && provider.provide ? provider.provide : provider; -}; diff --git a/libs/ng-mocks/src/lib/common/func.get-type.ts b/libs/ng-mocks/src/lib/common/func.get-type.ts new file mode 100644 index 0000000000..df2b7cbce7 --- /dev/null +++ b/libs/ng-mocks/src/lib/common/func.get-type.ts @@ -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; +}; diff --git a/libs/ng-mocks/src/lib/common/ng-mocks-global-overrides.ts b/libs/ng-mocks/src/lib/common/ng-mocks-global-overrides.ts index 3b2cacdc1c..0c69ebd022 100644 --- a/libs/ng-mocks/src/lib/common/ng-mocks-global-overrides.ts +++ b/libs/ng-mocks/src/lib/common/ng-mocks-global-overrides.ts @@ -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'; @@ -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'; @@ -71,10 +74,9 @@ const initTestBed = () => { const generateTouches = (moduleDef: Partial>, touches: Set): 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; @@ -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; } @@ -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(); @@ -205,7 +249,7 @@ const configureTestingModule = applyPlatformOverrides(testBed, touches); } - return original.call(instance, moduleDef); + return original.call(instance, finalModuleDef); }; const resetTestingModule = diff --git a/libs/ng-mocks/src/lib/mock-builder/mock-builder.promise.ts b/libs/ng-mocks/src/lib/mock-builder/mock-builder.promise.ts index b720f8bc54..33fb7ccbf7 100644 --- a/libs/ng-mocks/src/lib/mock-builder/mock-builder.promise.ts +++ b/libs/ng-mocks/src/lib/mock-builder/mock-builder.promise.ts @@ -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'; @@ -87,6 +88,7 @@ export class MockBuilderPromise implements IMockBuilder { createNgMocksToken(), createNgMocksTouchesToken(), createNgMocksOverridesToken(this.replaceDef, this.defValue), + MockBuilder as never, ); return ngModule; diff --git a/libs/ng-mocks/src/lib/mock-builder/performance/required-metadata.ts b/libs/ng-mocks/src/lib/mock-builder/performance/required-metadata.ts index e972d71ca1..dd0b46c2ce 100644 --- a/libs/ng-mocks/src/lib/mock-builder/performance/required-metadata.ts +++ b/libs/ng-mocks/src/lib/mock-builder/performance/required-metadata.ts @@ -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 */ [])], diff --git a/libs/ng-mocks/src/lib/mock-builder/promise/add-requested-providers.ts b/libs/ng-mocks/src/lib/mock-builder/promise/add-requested-providers.ts index 453d0350ed..476222dbc5 100644 --- a/libs/ng-mocks/src/lib/mock-builder/promise/add-requested-providers.ts +++ b/libs/ng-mocks/src/lib/mock-builder/promise/add-requested-providers.ts @@ -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'; @@ -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) { diff --git a/libs/ng-mocks/src/lib/mock-builder/promise/apply-platform-modules.ts b/libs/ng-mocks/src/lib/mock-builder/promise/apply-platform-modules.ts index 53342f62a2..185608f141 100644 --- a/libs/ng-mocks/src/lib/mock-builder/promise/apply-platform-modules.ts +++ b/libs/ng-mocks/src/lib/mock-builder/promise/apply-platform-modules.ts @@ -1,6 +1,7 @@ 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 () => { @@ -8,12 +9,7 @@ export default () => { // istanbul ignore else if (testBed.ngModule) { for (const def of flatten(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)); } } }; diff --git a/libs/ng-mocks/src/lib/mock-builder/promise/init-modules.ts b/libs/ng-mocks/src/lib/mock-builder/promise/init-modules.ts index 4f9adabb9a..b4dbd17059 100644 --- a/libs/ng-mocks/src/lib/mock-builder/promise/init-modules.ts +++ b/libs/ng-mocks/src/lib/mock-builder/promise/init-modules.ts @@ -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) { diff --git a/libs/ng-mocks/src/lib/mock-builder/promise/init-ng-modules.ts b/libs/ng-mocks/src/lib/mock-builder/promise/init-ng-modules.ts index efbe1f6ca6..37078d7812 100644 --- a/libs/ng-mocks/src/lib/mock-builder/promise/init-ng-modules.ts +++ b/libs/ng-mocks/src/lib/mock-builder/promise/init-ng-modules.ts @@ -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'; @@ -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)); } } } @@ -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 ( diff --git a/libs/ng-mocks/src/lib/mock-builder/promise/parse-provider.ts b/libs/ng-mocks/src/lib/mock-builder/promise/parse-provider.ts index e030760335..4dc1c3a5c3 100644 --- a/libs/ng-mocks/src/lib/mock-builder/promise/parse-provider.ts +++ b/libs/ng-mocks/src/lib/mock-builder/promise/parse-provider.ts @@ -1,4 +1,4 @@ -import funcGetProvider from '../../common/func.get-provider'; +import funcGetType from '../../common/func.get-type'; export default ( provider: any, @@ -6,7 +6,7 @@ export default ( multi: boolean; provide: any; } => { - const provide = funcGetProvider(provider); + const provide = funcGetType(provider); const multi = provide !== provider && provider.multi; return { diff --git a/libs/ng-mocks/src/lib/mock-helper/func.parse-provider-tokens-directives.ts b/libs/ng-mocks/src/lib/mock-helper/func.parse-provider-tokens-directives.ts index 067d94358c..2d4b055702 100644 --- a/libs/ng-mocks/src/lib/mock-helper/func.parse-provider-tokens-directives.ts +++ b/libs/ng-mocks/src/lib/mock-helper/func.parse-provider-tokens-directives.ts @@ -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 { @@ -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); diff --git a/libs/ng-mocks/src/lib/mock-helper/mock-helper.guts.ts b/libs/ng-mocks/src/lib/mock-helper/mock-helper.guts.ts index cf33896d84..7c40743298 100644 --- a/libs/ng-mocks/src/lib/mock-helper/mock-helper.guts.ts +++ b/libs/ng-mocks/src/lib/mock-helper/mock-helper.guts.ts @@ -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'; @@ -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; diff --git a/libs/ng-mocks/src/lib/mock-module/create-resolvers.ts b/libs/ng-mocks/src/lib/mock-module/create-resolvers.ts index 88d7f80e85..0674a262ec 100644 --- a/libs/ng-mocks/src/lib/mock-module/create-resolvers.ts +++ b/libs/ng-mocks/src/lib/mock-module/create-resolvers.ts @@ -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'; @@ -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, @@ -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); } diff --git a/libs/ng-mocks/src/lib/mock-module/mark-providers.ts b/libs/ng-mocks/src/lib/mock-module/mark-providers.ts index 2ef17768f8..2ee6b77678 100644 --- a/libs/ng-mocks/src/lib/mock-module/mark-providers.ts +++ b/libs/ng-mocks/src/lib/mock-module/mark-providers.ts @@ -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); } }; diff --git a/libs/ng-mocks/src/lib/mock-module/mock-module.ts b/libs/ng-mocks/src/lib/mock-module/mock-module.ts index e8646f0075..a2fd1bdfa4 100644 --- a/libs/ng-mocks/src/lib/mock-module/mock-module.ts +++ b/libs/ng-mocks/src/lib/mock-module/mock-module.ts @@ -148,7 +148,7 @@ const detectMockModule = (ngModule: Type, mockModule?: Type): Type { if (ngModuleProviders) { - const [changed, ngModuleDef] = mockNgDef({ providers: ngModuleProviders }); + const [changed, ngModuleDef] = mockNgDef({ providers: ngModuleProviders, skipExports: true }); return changed ? ngModuleDef.providers : ngModuleProviders; } diff --git a/libs/ng-mocks/src/lib/mock-module/mock-ng-def.ts b/libs/ng-mocks/src/lib/mock-module/mock-ng-def.ts index 9455a0c839..24d871db04 100644 --- a/libs/ng-mocks/src/lib/mock-module/mock-ng-def.ts +++ b/libs/ng-mocks/src/lib/mock-module/mock-ng-def.ts @@ -3,7 +3,7 @@ import { Component, Directive, NgModule, Pipe, Provider } from '@angular/core'; import CoreDefStack from '../common/core.def-stack'; import { flatten } from '../common/core.helpers'; import { dependencyKeys, Type } from '../common/core.types'; -import { isNgModuleDefWithProviders } from '../common/func.is-ng-module-def-with-providers'; +import funcGetType from '../common/func.get-type'; import ngMocksUniverse from '../common/ng-mocks-universe'; import createResolvers from './create-resolvers'; @@ -66,7 +66,7 @@ const resolveDefForExport = ( ngModule?: Type, ): Type | undefined => { const moduleConfig = ngMocksUniverse.config.get(ngModule) || {}; - const instance = isNgModuleDefWithProviders(def) ? def.ngModule : def; + const instance = funcGetType(def); const mockDef = resolve(instance); if (!mockDef) { return undefined; @@ -85,6 +85,11 @@ const resolveDefForExport = ( return undefined; } + ngMocksUniverse.configInstance.set(instance, { + ...ngMocksUniverse.configInstance.get(instance), + exported: true, + }); + return mockDef; }; @@ -116,6 +121,7 @@ const addExports = ( export default ( ngModuleDef: NgModule & { skipMarkProviders?: boolean; + skipExports?: boolean; }, ngModule?: Type, ): [boolean, NgModule, Map] => { @@ -131,7 +137,9 @@ export default ( }; const { resolve, resolveProvider } = createResolvers(change, ngMocksUniverse.config.get('mockNgDefResolver')); const mockModuleDef = processMeta(ngModuleDef, resolve, resolveProvider); - addExports(resolve, change, ngModuleDef, mockModuleDef, ngModule); + if (!ngModuleDef.skipExports) { + addExports(resolve, change, ngModuleDef, mockModuleDef, ngModule); + } const resolutions = ngMocksUniverse.config.get('mockNgDefResolver').pop(); if (!hasResolver) { diff --git a/libs/ng-mocks/src/lib/mock-service/helper.resolve-provider.ts b/libs/ng-mocks/src/lib/mock-service/helper.resolve-provider.ts index 3268950342..aca862ef95 100644 --- a/libs/ng-mocks/src/lib/mock-service/helper.resolve-provider.ts +++ b/libs/ng-mocks/src/lib/mock-service/helper.resolve-provider.ts @@ -2,7 +2,7 @@ import CoreDefStack from '../common/core.def-stack'; import { extractDependency } from '../common/core.helpers'; import { NG_MOCKS_INTERCEPTORS } from '../common/core.tokens'; import funcExtractForwardRef from '../common/func.extract-forward-ref'; -import funcGetProvider from '../common/func.get-provider'; +import funcGetType from '../common/func.get-type'; import { isNgInjectionToken } from '../common/func.is-ng-injection-token'; import ngMocksUniverse from '../common/ng-mocks-universe'; @@ -67,7 +67,7 @@ const parseProvider = ( multi: boolean; provide: any; } => { - const provide = funcGetProvider(provider); + const provide = funcGetType(provider); const multi = provider !== provide && !!provider.multi; return { diff --git a/libs/ng-mocks/src/lib/mock-service/mock-provider.ts b/libs/ng-mocks/src/lib/mock-service/mock-provider.ts index 6ab3b4ea78..711135dabd 100644 --- a/libs/ng-mocks/src/lib/mock-service/mock-provider.ts +++ b/libs/ng-mocks/src/lib/mock-service/mock-provider.ts @@ -2,7 +2,7 @@ import { Provider } from '@angular/core'; import coreConfig from '../common/core.config'; import { Type } from '../common/core.types'; -import funcGetProvider from '../common/func.get-provider'; +import funcGetType from '../common/func.get-type'; import { isNgInjectionToken } from '../common/func.is-ng-injection-token'; import ngMocksUniverse from '../common/ng-mocks-universe'; @@ -116,7 +116,7 @@ const isNeverMockToken = (provide: any): boolean => isNgInjectionToken(provide) && neverMockToken.indexOf(provide.toString()) !== -1; export default (provider: any, useFactory = false): Provider | undefined => { - const provide = funcGetProvider(provider); + const provide = funcGetType(provider); if (ngMocksUniverse.getResolution(provide) === 'mock') { // nothing to do diff --git a/libs/ng-mocks/src/lib/mock/clone-providers.ts b/libs/ng-mocks/src/lib/mock/clone-providers.ts index 7d2887094a..f89bee602b 100644 --- a/libs/ng-mocks/src/lib/mock/clone-providers.ts +++ b/libs/ng-mocks/src/lib/mock/clone-providers.ts @@ -5,7 +5,7 @@ import coreForm from '../common/core.form'; import { flatten } from '../common/core.helpers'; import { AnyType } from '../common/core.types'; import funcExtractForwardRef from '../common/func.extract-forward-ref'; -import funcGetProvider from '../common/func.get-provider'; +import funcGetType from '../common/func.get-type'; import { MockAsyncValidatorProxy, MockControlValueAccessorProxy, @@ -17,7 +17,7 @@ import toExistingProvider from './to-existing-provider'; import toFactoryProvider from './to-factory-provider'; const processTokens = (mockType: AnyType, provider: any) => { - const provide = funcGetProvider(provider); + const provide = funcGetType(provider); if (coreForm.NG_VALIDATORS && provide === coreForm.NG_VALIDATORS) { return toFactoryProvider(provide, () => new MockValidatorProxy(mockType)); } @@ -32,7 +32,7 @@ const processTokens = (mockType: AnyType, provider: any) => { }; const processOwnUseExisting = (sourceType: AnyType, mockType: AnyType, provider: any) => { - const provide = funcGetProvider(provider); + const provide = funcGetType(provider); // Check tests/issue-302/test.spec.ts if (provide === coreForm.NgControl || provide === coreForm.FormControlDirective) { @@ -78,7 +78,7 @@ export default ( let setControlValueAccessor: boolean | undefined; for (const provider of flatten(providers || /* istanbul ignore next */ [])) { - const provide = funcGetProvider(provider); + const provide = funcGetType(provider); if (provide === coreForm.NG_VALUE_ACCESSOR) { setControlValueAccessor = false; } diff --git a/libs/ng-mocks/src/lib/mock/decorate-declaration.ts b/libs/ng-mocks/src/lib/mock/decorate-declaration.ts index 71206080ad..71e0f74175 100644 --- a/libs/ng-mocks/src/lib/mock/decorate-declaration.ts +++ b/libs/ng-mocks/src/lib/mock/decorate-declaration.ts @@ -65,7 +65,7 @@ export default ( } if (meta.standalone && meta.imports) { - const [, { imports }] = mockNgDef({ imports: meta.imports }); + const [, { imports }] = mockNgDef({ imports: meta.imports, skipExports: true }); if (imports?.length) { options.imports = imports as never; } diff --git a/libs/ng-mocks/src/lib/mock/return-cached-mock.ts b/libs/ng-mocks/src/lib/mock/return-cached-mock.ts index fd6cf00d5b..98a545ccb6 100644 --- a/libs/ng-mocks/src/lib/mock/return-cached-mock.ts +++ b/libs/ng-mocks/src/lib/mock/return-cached-mock.ts @@ -1,7 +1,20 @@ +import { NG_MOCKS } from '../common/core.tokens'; import ngMocksUniverse from '../common/ng-mocks-universe'; +import funcGetLastFixture from '../mock-helper/func.get-last-fixture'; export default (declaration: any) => { - const result = ngMocksUniverse.cacheDeclarations.get(declaration); + let result: any; + + try { + result = funcGetLastFixture().debugElement.injector.get(NG_MOCKS).get(declaration); + } catch { + // nothing to do. + } + + if (!result) { + result = ngMocksUniverse.cacheDeclarations.get(declaration); + } + if (declaration.__ngMocksResolutions && ngMocksUniverse.config.has('mockNgDefResolver')) { ngMocksUniverse.config.get('mockNgDefResolver').merge(declaration.__ngMocksResolutions); } diff --git a/tests-e2e/src/issue-4344/test.spec.ts b/tests-e2e/src/issue-4344/test.spec.ts new file mode 100644 index 0000000000..5b44268117 --- /dev/null +++ b/tests-e2e/src/issue-4344/test.spec.ts @@ -0,0 +1,70 @@ +import { + CdkFixedSizeVirtualScroll, + ScrollingModule, +} from '@angular/cdk/scrolling'; +import { Component, NgModule } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { + MockBuilder, + MockModule, + MockRender, + ngMocks, +} from 'ng-mocks'; + +@Component({ + selector: 'dependency', + template: + '', +}) +class DependencyComponent {} + +@NgModule({ + imports: [ScrollingModule], + declarations: [DependencyComponent], + exports: [DependencyComponent, ScrollingModule], +}) +class DependencyModule {} + +@Component({ + selector: 'target', + template: + '', +}) +class TargetComponent {} + +@NgModule({ + imports: [DependencyModule], + declarations: [TargetComponent], + exports: [TargetComponent], +}) +class TargetModule {} + +// @see https://github.com/help-me-mom/ng-mocks/issues/4344 +// Type CdkFixedSizeVirtualScroll is part of the declarations of 2 modules: +// MockOfScrollingModule and ScrollingModule! +// Please consider moving CdkFixedSizeVirtualScroll to a higher module +// that imports MockOfScrollingModule and ScrollingModule. +describe('issue-4344', () => { + beforeAll(() => ngMocks.globalKeep(CdkFixedSizeVirtualScroll)); + afterAll(() => ngMocks.globalWipe(CdkFixedSizeVirtualScroll)); + + describe('TestBed', () => { + beforeEach(() => + TestBed.configureTestingModule({ + imports: [MockModule(DependencyModule), TargetModule], + }).compileComponents(), + ); + + it('creates TargetComponent', () => { + expect(() => MockRender(TargetComponent)).not.toThrow(); + }); + }); + + describe('MockBuilder', () => { + beforeEach(() => MockBuilder(TargetModule, DependencyModule)); + + it('creates TargetComponent', () => { + expect(() => MockRender(TargetComponent)).not.toThrow(); + }); + }); +}); diff --git a/tests/issue-4344/standalone-explicit.spec.ts b/tests/issue-4344/standalone-explicit.spec.ts new file mode 100644 index 0000000000..e225f84fd7 --- /dev/null +++ b/tests/issue-4344/standalone-explicit.spec.ts @@ -0,0 +1,107 @@ +import { + AsyncPipe, + CommonModule, + DecimalPipe, +} from '@angular/common'; +import { + Component, + Injectable, + NgModule, + VERSION, +} from '@angular/core'; +import { TestBed } from '@angular/core/testing'; + +import { + isMockOf, + MockComponent, + MockModule, + MockRender, + ngMocks, +} from 'ng-mocks'; + +@Injectable() +class TargetService {} + +@Component({ + selector: 'target', + template: '{{ 1 | number }}', +}) +class TargetComponent { + constructor( + public readonly service: TargetService, + public readonly pipe: AsyncPipe, + ) {} +} +@NgModule({ + declarations: [TargetComponent], + imports: [CommonModule], + exports: [CommonModule, TargetComponent], + providers: [TargetService, AsyncPipe], +}) +class TargetModule {} + +@Component( + { + selector: 'standalone', + template: '{{ 1 | number }}', + standalone: true, + imports: [TargetModule], + providers: [AsyncPipe], + } as never /* TODO: remove after upgrade to a14 */, +) +class StandaloneComponent { + constructor( + public readonly service: TargetService, + public readonly pipe: AsyncPipe, + ) {} +} + +ngMocks.globalKeep(TargetComponent); +ngMocks.globalMock(TargetModule); + +// @see https://github.com/help-me-mom/ng-mocks/issues/4344 +// exporting AsyncPipe from CommonModule which is kept, +// causes an issue, because ng-mocks mocks AsyncPipe, whereas it shouldn't. +// That happens because a previously checked CommonModule doesn't expose its guts anymore. +describe('issue-4344:standalone:explicit', () => { + if (Number.parseInt(VERSION.major, 10) < 14) { + it('needs >=a14', () => { + expect(true).toBeTruthy(); + }); + + return; + } + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + MockModule(CommonModule), + MockComponent(StandaloneComponent), + MockModule(TargetModule), + ], + }).compileComponents(); + }); + + it('creates StandaloneComponent', () => { + expect(() => MockRender(StandaloneComponent)).not.toThrow(); + + const targetService = ngMocks.findInstance(TargetService); + expect(isMockOf(targetService, TargetService)).toEqual(true); + + const asyncPipe = ngMocks.findInstance(AsyncPipe); + expect(isMockOf(asyncPipe, AsyncPipe)).toEqual(false); + }); + + it('creates TargetComponent', () => { + expect(() => MockRender(TargetComponent)).not.toThrow(); + + const decimalPipe = ngMocks.findInstance(DecimalPipe); + expect(isMockOf(decimalPipe, DecimalPipe)).toEqual(false); + + const targetService = ngMocks.findInstance(TargetService); + expect(isMockOf(targetService, TargetService)).toEqual(true); + + const asyncPipe = ngMocks.findInstance(AsyncPipe); + expect(isMockOf(asyncPipe, AsyncPipe)).toEqual(false); + }); +}); diff --git a/tests/issue-4344/with-providers-explicit.spec.ts b/tests/issue-4344/with-providers-explicit.spec.ts new file mode 100644 index 0000000000..f470a2d5de --- /dev/null +++ b/tests/issue-4344/with-providers-explicit.spec.ts @@ -0,0 +1,107 @@ +import { + AsyncPipe, + CommonModule, + DecimalPipe, +} from '@angular/common'; +import { + Component, + Injectable, + NgModule, + VERSION, +} from '@angular/core'; +import { TestBed } from '@angular/core/testing'; + +import { + isMockOf, + MockComponent, + MockModule, + MockRender, + ngMocks, +} from 'ng-mocks'; + +@Injectable() +class TargetService {} + +@Component({ + selector: 'target', + template: '{{ 1 | number }}', +}) +class TargetComponent { + constructor( + public readonly service: TargetService, + public readonly pipe: AsyncPipe, + ) {} +} +@NgModule({ + declarations: [TargetComponent], + imports: [{ ngModule: CommonModule, providers: [] }], + exports: [CommonModule, TargetComponent], + providers: [TargetService, AsyncPipe], +}) +class TargetModule {} + +@Component( + { + selector: 'standalone', + template: '{{ 1 | number }}', + standalone: true, + imports: [TargetModule], + providers: [AsyncPipe], + } as never /* TODO: remove after upgrade to a14 */, +) +class StandaloneComponent { + constructor( + public readonly service: TargetService, + public readonly pipe: AsyncPipe, + ) {} +} + +ngMocks.globalKeep(TargetComponent); +ngMocks.globalMock(TargetModule); + +// @see https://github.com/help-me-mom/ng-mocks/issues/4344 +// exporting AsyncPipe from CommonModule which is kept, +// causes an issue, because ng-mocks mocks AsyncPipe, whereas it shouldn't. +// That happens because a previously checked CommonModule doesn't expose its guts anymore. +describe('issue-4344:with-providers:explicit', () => { + if (Number.parseInt(VERSION.major, 10) < 14) { + it('needs >=a14', () => { + expect(true).toBeTruthy(); + }); + + return; + } + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + MockModule({ ngModule: CommonModule, providers: [] }), + MockComponent(StandaloneComponent), + MockModule({ ngModule: TargetModule, providers: [] }), + ], + }).compileComponents(); + }); + + it('creates StandaloneComponent', () => { + expect(() => MockRender(StandaloneComponent)).not.toThrow(); + + const targetService = ngMocks.findInstance(TargetService); + expect(isMockOf(targetService, TargetService)).toEqual(true); + + const asyncPipe = ngMocks.findInstance(AsyncPipe); + expect(isMockOf(asyncPipe, AsyncPipe)).toEqual(false); + }); + + it('creates TargetComponent', () => { + expect(() => MockRender(TargetComponent)).not.toThrow(); + + const decimalPipe = ngMocks.findInstance(DecimalPipe); + expect(isMockOf(decimalPipe, DecimalPipe)).toEqual(false); + + const targetService = ngMocks.findInstance(TargetService); + expect(isMockOf(targetService, TargetService)).toEqual(true); + + const asyncPipe = ngMocks.findInstance(AsyncPipe); + expect(isMockOf(asyncPipe, AsyncPipe)).toEqual(false); + }); +}); diff --git a/tests/issue-4344/with-providers.spec.ts b/tests/issue-4344/with-providers.spec.ts new file mode 100644 index 0000000000..801166cd4e --- /dev/null +++ b/tests/issue-4344/with-providers.spec.ts @@ -0,0 +1,107 @@ +import { + AsyncPipe, + CommonModule, + DecimalPipe, +} from '@angular/common'; +import { + Component, + Injectable, + NgModule, + VERSION, +} from '@angular/core'; +import { TestBed } from '@angular/core/testing'; + +import { + isMockOf, + MockComponent, + MockModule, + MockRender, + ngMocks, +} from 'ng-mocks'; + +@Injectable() +class TargetService {} + +@Component({ + selector: 'target', + template: '{{ 1 | number }}', +}) +class TargetComponent { + constructor( + public readonly service: TargetService, + public readonly pipe: AsyncPipe, + ) {} +} +@NgModule({ + declarations: [TargetComponent], + imports: [{ ngModule: CommonModule, providers: [] }], + exports: [DecimalPipe, TargetComponent], + providers: [TargetService, AsyncPipe], +}) +class TargetModule {} + +@Component( + { + selector: 'standalone', + template: '{{ 1 | number }}', + standalone: true, + imports: [TargetModule], + providers: [AsyncPipe], + } as never /* TODO: remove after upgrade to a14 */, +) +class StandaloneComponent { + constructor( + public readonly service: TargetService, + public readonly pipe: AsyncPipe, + ) {} +} + +ngMocks.globalKeep(TargetComponent); +ngMocks.globalMock(TargetModule); + +// @see https://github.com/help-me-mom/ng-mocks/issues/4344 +// exporting AsyncPipe from CommonModule which is kept, +// causes an issue, because ng-mocks mocks AsyncPipe, whereas it shouldn't. +// That happens because a previously checked CommonModule doesn't expose its guts anymore. +describe('issue-4344:with-providers', () => { + if (Number.parseInt(VERSION.major, 10) < 14) { + it('needs >=a14', () => { + expect(true).toBeTruthy(); + }); + + return; + } + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + MockModule({ ngModule: CommonModule, providers: [] }), + MockComponent(StandaloneComponent), + MockModule({ ngModule: TargetModule, providers: [] }), + ], + }).compileComponents(); + }); + + it('creates StandaloneComponent', () => { + expect(() => MockRender(StandaloneComponent)).not.toThrow(); + + const targetService = ngMocks.findInstance(TargetService); + expect(isMockOf(targetService, TargetService)).toEqual(true); + + const asyncPipe = ngMocks.findInstance(AsyncPipe); + expect(isMockOf(asyncPipe, AsyncPipe)).toEqual(false); + }); + + it('creates TargetComponent', () => { + expect(() => MockRender(TargetComponent)).not.toThrow(); + + const decimalPipe = ngMocks.findInstance(DecimalPipe); + expect(isMockOf(decimalPipe, DecimalPipe)).toEqual(false); + + const targetService = ngMocks.findInstance(TargetService); + expect(isMockOf(targetService, TargetService)).toEqual(true); + + const asyncPipe = ngMocks.findInstance(AsyncPipe); + expect(isMockOf(asyncPipe, AsyncPipe)).toEqual(false); + }); +});