Skip to content

Commit

Permalink
Merge pull request #2123 from satanTime/issues/1168
Browse files Browse the repository at this point in the history
fix(core): better error messages #1168
  • Loading branch information
satanTime committed Mar 26, 2022
2 parents df4a781 + cad1efb commit 19b1c79
Show file tree
Hide file tree
Showing 18 changed files with 337 additions and 24 deletions.
3 changes: 2 additions & 1 deletion libs/ng-mocks/src/lib/common/core.helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { getTestBed } from '@angular/core/testing';
import coreDefineProperty from './core.define-property';
import coreReflectJit from './core.reflect.jit';
import { AnyType, Type } from './core.types';
import funcGetName from './func.get-name';

export const getTestBedInjection = <I>(token: AnyType<I> | InjectionToken<I>): I | undefined => {
const testBed: any = getTestBed();
Expand Down Expand Up @@ -105,7 +106,7 @@ const extendClassicClass = <I extends object>(base: AnyType<I>): Type<I> => {

export const extendClass = <I extends object>(base: AnyType<I>): Type<I> => {
const child: Type<I> = extendClassicClass(base);
coreDefineProperty(child, 'name', `MockMiddleware${base.name}`, true);
coreDefineProperty(child, 'name', `MockMiddleware${funcGetName(base)}`, true);

const parameters = coreReflectJit().parameters(base);
if (parameters.length) {
Expand Down
5 changes: 3 additions & 2 deletions libs/ng-mocks/src/lib/common/decorate.mock.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import coreDefineProperty from './core.define-property';
import { AnyType } from './core.types';
import funcGetName from './func.get-name';
import { ngMocksMockConfig } from './mock';
import ngMocksUniverse from './ng-mocks-universe';

export default function (mock: AnyType<any>, source: AnyType<any>, configInput: ngMocksMockConfig = {}): void {
coreDefineProperty(mock, 'mockOf', source);
coreDefineProperty(mock, 'nameConstructor', mock.name);
coreDefineProperty(mock, 'name', `MockOf${source.name}`, true);
coreDefineProperty(mock, 'nameConstructor', funcGetName(mock));
coreDefineProperty(mock, 'name', `MockOf${funcGetName(source)}`, true);
const config = ngMocksUniverse.getConfigMock().has(source)
? {
...configInput,
Expand Down
7 changes: 5 additions & 2 deletions libs/ng-mocks/src/lib/common/error.jest-mock.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import funcGetName from './func.get-name';
import funcIsJestMock from './func.is-jest-mock';

export default (def: any): void => {
if (funcIsJestMock(def)) {
throw new Error(
[
`ng-mocks got ${def.name} which has been already mocked by jest.mock().`,
`ng-mocks got ${funcGetName(def)} which has been already mocked by jest.mock().`,
'It is not possible to produce correct mocks for it, because jest.mock() removes Angular decorators.',
`To fix this, please avoid jest.mock() on the file which exports ${def.name} or add jest.dontMock() on it.`,
`To fix this, please avoid jest.mock() on the file which exports ${funcGetName(
def,
)} or add jest.dontMock() on it.`,
'The same should be done for all related dependencies.',
].join(' '),
);
Expand Down
4 changes: 3 additions & 1 deletion libs/ng-mocks/src/lib/common/error.missing-decorators.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// tslint:disable strict-type-predicates

import funcGetName from './func.get-name';

export default (def: any): void => {
throw new Error(
[
`${def.name} declaration has been passed into ng-mocks without Angular decorators.`,
`${funcGetName(def)} declaration has been passed into ng-mocks without Angular decorators.`,
'Therefore, it cannot be properly handled.',
'Highly likely,',
typeof jest === 'undefined' ? '' : /* istanbul ignore next */ 'jest.mock() has been used on its file, or',
Expand Down
5 changes: 3 additions & 2 deletions libs/ng-mocks/src/lib/common/func.get-mocked-ng-def-of.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import { MockedPipe } from '../mock-pipe/types';
import coreInjector from './core.injector';
import { NG_MOCKS } from './core.tokens';
import { AnyType, Type } from './core.types';
import funcGetName from './func.get-name';
import { isMockedNgDefOf } from './func.is-mocked-ng-def-of';
import ngMocksUniverse from './ng-mocks-universe';

const getMock = (declaration: any, source: any, mocks?: Map<any, any>) => {
if (mocks && !mocks.has(source)) {
throw new Error(`There is no mock for ${source.name}`);
throw new Error(`There is no mock for ${funcGetName(source)}`);
}
let mock = mocks ? mocks.get(source) : undefined;
if (mock === source) {
Expand Down Expand Up @@ -75,5 +76,5 @@ export function getMockedNgDefOf(declaration: any, type?: any): any {
return mock;
}

throw new Error(`There is no mock for ${source.name}`);
throw new Error(`There is no mock for ${funcGetName(source)}`);
}
7 changes: 7 additions & 0 deletions libs/ng-mocks/src/lib/common/func.get-name.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import funcGetName from './func.get-name';

describe('func.get-name', () => {
it('detects unknown', () => {
expect(funcGetName(false)).toEqual('unknown');
});
});
16 changes: 16 additions & 0 deletions libs/ng-mocks/src/lib/common/func.get-name.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export default (value: any): string => {
if (typeof value === 'function' && value.name) {
return value.name;
}
if (typeof value === 'function') {
return 'arrow-function';
}
if (typeof value === 'object' && value && value.ngMetadataName === 'InjectionToken') {
return value._desc;
}
if (typeof value === 'object' && value && typeof value.constructor === 'function') {
return value.constructor.name;
}

return 'unknown';
};
43 changes: 43 additions & 0 deletions libs/ng-mocks/src/lib/common/func.import-exists.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,48 @@
import funcGetName from './func.get-name';
import { isNgDef } from './func.is-ng-def';

export default (value: any, funcName: string) => {
if (value === undefined || value === null) {
throw new Error(`An empty parameter has been passed into ${funcName}. Please check that its import is correct.`);
}

if (funcName === 'MockPipe' && isNgDef(value, 'p')) {
return;
}
if (funcName === 'MockDirective' && isNgDef(value, 'd')) {
return;
}
if (funcName === 'MockComponent' && isNgDef(value, 'c')) {
return;
}
if (funcName === 'MockModule' && isNgDef(value, 'm')) {
return;
}

const type = isNgDef(value, 'p')
? 'pipe'
: isNgDef(value, 'd')
? 'directive'
: isNgDef(value, 'c')
? 'component'
: isNgDef(value, 'm')
? 'module'
: isNgDef(value, 'i')
? 'service'
: isNgDef(value, 't')
? 'token'
: '';

if (type && funcName === 'MockPipe') {
throw new Error(`${funcName} accepts pipes, whereas ${funcGetName(value)} is a ${type}.`);
}
if (type && funcName === 'MockDirective') {
throw new Error(`${funcName} accepts directives, whereas ${funcGetName(value)} is a ${type}.`);
}
if (type && funcName === 'MockComponent') {
throw new Error(`${funcName} accepts components, whereas ${funcGetName(value)} is a ${type}.`);
}
if (type && funcName === 'MockModule') {
throw new Error(`${funcName} accepts modules, whereas ${funcGetName(value)} is a ${type}.`);
}
};
3 changes: 2 additions & 1 deletion libs/ng-mocks/src/lib/common/ng-mocks-universe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { IMockBuilderConfig } from '../mock-builder/types';

import coreConfig from './core.config';
import { AnyType } from './core.types';
import funcGetName from './func.get-name';

// istanbul ignore next
const getGlobal = (): any => window || global;
Expand Down Expand Up @@ -70,7 +71,7 @@ const getDefaults = (def: any): [] | ['mock' | 'keep' | 'replace' | 'exclude', a
}

{
const defValue = typeof def === 'function' ? ngMocksUniverse.getDefaults().get(`@${def.name}`) : undefined;
const defValue = typeof def === 'function' ? ngMocksUniverse.getDefaults().get(`@${funcGetName(def)}`) : undefined;
if (defValue) {
return defValue;
}
Expand Down
3 changes: 2 additions & 1 deletion libs/ng-mocks/src/lib/mock-declaration/mock-declaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { Type } from '../common/core.types';
import errorJestMock from '../common/error.jest-mock';
import funcGetName from '../common/func.get-name';
import { isNgDef } from '../common/func.is-ng-def';
import { MockComponent } from '../mock-component/mock-component';
import { MockedComponent } from '../mock-component/types';
Expand Down Expand Up @@ -33,7 +34,7 @@ export function MockDeclaration<T>(
throw new Error(
[
'MockDeclaration does not know how to mock',
typeof declaration === 'function' ? (declaration as any).name : declaration,
typeof declaration === 'function' ? funcGetName(declaration) : declaration,
].join(' '),
);
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import funcGetName from '../common/func.get-name';
import ngMocksUniverse from '../common/ng-mocks-universe';

export default (checkReset: Array<[any, any, any?]>) => {
Expand All @@ -7,7 +8,7 @@ export default (checkReset: Array<[any, any, any?]>) => {
while (checkReset.length) {
const [declaration, config] = checkReset.pop() || /* istanbul ignore next */ [];
if (config === ngMocksUniverse.configInstance.get(declaration)) {
showError.push(typeof declaration === 'function' ? declaration.name : declaration);
showError.push(typeof declaration === 'function' ? funcGetName(declaration) : declaration);
}
}

Expand Down
3 changes: 2 additions & 1 deletion libs/ng-mocks/src/lib/mock-module/mock-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { extendClass } from '../common/core.helpers';
import coreReflectModuleResolve from '../common/core.reflect.module-resolve';
import { Type } from '../common/core.types';
import decorateMock from '../common/decorate.mock';
import funcGetName from '../common/func.get-name';
import funcImportExists from '../common/func.import-exists';
import { isMockNgDef } from '../common/func.is-mock-ng-def';
import { isNgDef } from '../common/func.is-ng-def';
Expand All @@ -22,7 +23,7 @@ const flagReplace = (resolution?: string): boolean =>
resolution === 'replace' && !ngMocksUniverse.flags.has('skipMock');

const flagNever = (ngModule?: any): boolean =>
coreConfig.neverMockModule.indexOf(ngModule.name) !== -1 && !ngMocksUniverse.flags.has('skipMock');
coreConfig.neverMockModule.indexOf(funcGetName(ngModule)) !== -1 && !ngMocksUniverse.flags.has('skipMock');

const preprocessToggleFlag = (ngModule: Type<any>): boolean => {
let toggleSkipMockFlag = false;
Expand Down
3 changes: 2 additions & 1 deletion libs/ng-mocks/src/lib/mock-pipe/mock-pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { extendClass } from '../common/core.helpers';
import coreReflectPipeResolve from '../common/core.reflect.pipe-resolve';
import { Type } from '../common/core.types';
import decorateMock from '../common/decorate.mock';
import funcGetName from '../common/func.get-name';
import funcImportExists from '../common/func.import-exists';
import { isMockNgDef } from '../common/func.is-mock-ng-def';
import { Mock } from '../common/mock';
Expand All @@ -28,7 +29,7 @@ const getMockClass = (pipe: Type<any>, transform?: PipeTransform['transform']):
instance.transform = transform;
}
if (!instance.transform) {
helperMockService.mock(instance, 'transform', `${instance.constructor.name}.transform`);
helperMockService.mock(instance, 'transform', `${funcGetName(instance)}.transform`);
}
},
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import funcGetName from '../common/func.get-name';

import helperMockService from './helper.mock-service';
import { MockedFunction } from './types';

export default (service: any): { [key in keyof any]: MockedFunction } => {
const mockName = service.constructor.name;
const mockName = funcGetName(service);
const value: any = {};

const methods = helperMockService.extractMethodsFromPrototype(service);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import funcGetName from '../common/func.get-name';

const sanitizerMethods = [
'sanitize',
'bypassSecurityTrustHtml',
Expand All @@ -14,7 +16,7 @@ const extraMethods: Record<string, undefined | string[]> = {

const getOwnPropertyNames = (prototype: any): string[] => {
const result: string[] = Object.getOwnPropertyNames(prototype);
for (const method of extraMethods[prototype.constructor.name] ?? []) {
for (const method of extraMethods[funcGetName(prototype)] ?? []) {
result.push(method);
}

Expand Down
10 changes: 3 additions & 7 deletions libs/ng-mocks/src/lib/mock-service/helper.mock.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import funcGetName from '../common/func.get-name';

import helperMockService from './helper.mock-service';
import { MockedFunction } from './types';

// istanbul ignore next
const createName = (name: string, mockName?: string, instance?: any, accessType?: string) =>
`${
mockName
? mockName
: typeof instance.prototype === 'function'
? instance.prototype.name
: typeof instance.constructor === 'function'
? instance.constructor.name
: 'unknown'
mockName ? mockName : typeof instance.prototype === 'function' ? instance.prototype.name : funcGetName(instance)
}.${name}${accessType || ''}`;

const generateMockDef = (def: any, mock: any, accessType?: string): PropertyDescriptor => ({
Expand Down
4 changes: 2 additions & 2 deletions libs/ng-mocks/src/lib/mock-service/mock-service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AnyType } from '../common/core.types';
import funcGetName from '../common/func.get-name';
import mockHelperStub from '../mock-helper/mock-helper.stub';

import checkIsClass from './check.is-class';
Expand All @@ -12,8 +13,7 @@ const mockVariableMap: Array<
[checkIsClass, (service: any) => helperMockService.createMockFromPrototype(service.prototype)],
[
checkIsFunc,
(service: any, prefix: string) =>
helperMockService.mockFunction(`func:${prefix || service.name || 'arrow-function'}`),
(service: any, prefix: string) => helperMockService.mockFunction(`func:${prefix || funcGetName(service)}`),
],
[def => Array.isArray(def), () => []],
[
Expand Down
Loading

0 comments on commit 19b1c79

Please sign in to comment.