Skip to content

Commit

Permalink
fix: overrides as functions are properly cloned #455
Browse files Browse the repository at this point in the history
  • Loading branch information
satanTime committed May 1, 2021
1 parent 33fb288 commit 9310d34
Show file tree
Hide file tree
Showing 13 changed files with 490 additions and 39 deletions.
19 changes: 8 additions & 11 deletions libs/ng-mocks/src/lib/common/core.define-property.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import helperMockService from '../mock-service/helper.mock-service';

export default (instance: any, property: keyof any, value: any, enumerable = false) => {
// istanbul ignore else
if (Object.defineProperty) {
Object.defineProperty(instance, property, {
configurable: true,
enumerable,
value,
writable: true,
});
} else {
instance[property] = value;
}
helperMockService.definePropertyDescriptor(instance, property, {
configurable: true,
enumerable,
value,
writable: true,
});
};
7 changes: 1 addition & 6 deletions libs/ng-mocks/src/lib/common/mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,7 @@ const applyPrototype = (instance: Mock, prototype: AnyType<any>) => {
...helperMockService.extractPropertiesFromPrototype(prototype),
]) {
const descriptor = helperMockService.extractPropertyDescriptor(prototype, prop);
// istanbul ignore next
if (!descriptor) {
continue;
}
descriptor.configurable = true;
Object.defineProperty(instance, prop, descriptor);
helperMockService.definePropertyDescriptor(instance, prop, descriptor);
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import helperUseFactory from '../../mock-service/helper.use-factory';
import mockProvider from '../../mock-service/mock-provider';
import { IMockBuilderConfigMock } from '../types';

const createInstance = (existing: any, instance: any, config: IMockBuilderConfigMock, isFunc: boolean): any => {
const params = isFunc ? { transform: instance } : instance;
const createInstance = (existing: any, instance: any, config: IMockBuilderConfigMock, isPipeFunc: boolean): any => {
const params = isPipeFunc ? { transform: instance } : instance;
if (config.precise) {
return params;
}
Expand All @@ -18,10 +18,10 @@ export default (def: any, defValue: Map<any, any>): void => {
if (isNgDef(def, 'i') && defValue.has(def)) {
const config: IMockBuilderConfigMock = ngMocksUniverse.config.get(def) || {};
const instance = defValue.get(def);
const isFunc = isNgDef(def, 'p') && typeof instance === 'function';
const isPipeFunc = isNgDef(def, 'p') && typeof instance === 'function';
ngMocksUniverse.builtProviders.set(
def,
helperUseFactory(def, undefined, existing => createInstance(existing, instance, config, isFunc)),
helperUseFactory(def, undefined, existing => createInstance(existing, instance, config, isPipeFunc)),
);
} else if (isNgDef(def, 'i')) {
ngMocksUniverse.builtProviders.set(def, mockProvider(def));
Expand Down
24 changes: 16 additions & 8 deletions libs/ng-mocks/src/lib/mock-helper/mock-helper.stub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,22 @@ export default <T = MockedFunction>(instance: any, override: any, style?: 'get'
if (typeof override === 'string') {
return helperMockService.mock(instance, override, style);
}
for (const key of Object.getOwnPropertyNames(override)) {
const def = Object.getOwnPropertyDescriptor(override, key);
// istanbul ignore else
if (def) {
def.configurable = true;
Object.defineProperty(instance, key, def);
}

// if someone is giving us a function, then we should swap instance and overrides.
// so in the end the function can be called, but it also has all desired properties.
let correctInstance = instance;
let applyOverrides = override;
const skipProps = ['__zone_symbol__unconfigurables'];
if (typeof override === 'function') {
correctInstance = helperMockService.createClone(override);
applyOverrides = instance;
skipProps.push(...Object.getOwnPropertyNames(correctInstance));
}

for (const key of Object.getOwnPropertyNames(applyOverrides)) {
const desc = skipProps.indexOf(key) === -1 ? Object.getOwnPropertyDescriptor(applyOverrides, key) : undefined;
helperMockService.definePropertyDescriptor(correctInstance, key, desc);
}

return instance;
return correctInstance;
};
22 changes: 22 additions & 0 deletions libs/ng-mocks/src/lib/mock-service/helper.create-clone.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import helperDefinePropertyDescriptor from './helper.define-property-descriptor';
import helperExtractMethodsFromPrototype from './helper.extract-methods-from-prototype';
import helperExtractPropertiesFromPrototype from './helper.extract-properties-from-prototype';
import helperExtractPropertyDescriptor from './helper.extract-property-descriptor';

export default (service: any): any => {
const instance = function () {
// tslint:disable-next-line:ban-ts-ignore
// @ts-ignore
return service.apply(this, arguments);
};

for (const prop of [
...helperExtractMethodsFromPrototype(service),
...helperExtractPropertiesFromPrototype(service),
]) {
const desc = helperExtractPropertyDescriptor(service, prop);
helperDefinePropertyDescriptor(instance, prop, desc);
}

return instance;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import helperExtractPropertyDescriptor from './helper.extract-property-descriptor';

export default (instance: any, prop: keyof any, desc?: PropertyDescriptor): boolean => {
// istanbul ignore next
if (!desc) {
return false;
}

// istanbul ignore else
if (Object.defineProperty) {
const sourceDesc = helperExtractPropertyDescriptor(instance, prop);
if (sourceDesc?.configurable === false) {
return false;
}

Object.defineProperty(instance, prop, {
...desc,
configurable: true,
...(desc.writable === false ? { writable: true } : {}),
});
} else {
instance[prop] = desc.value;
}

return true;
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export default <T>(service: T, prop: string): PropertyDescriptor | undefined => {
export default <T>(service: T, prop: keyof any): PropertyDescriptor | undefined => {
let prototype = service;
while (prototype && Object.getPrototypeOf(prototype) !== null) {
const descriptor = Object.getOwnPropertyDescriptor(prototype, prop);
Expand Down
6 changes: 6 additions & 0 deletions libs/ng-mocks/src/lib/mock-service/helper.mock-service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import helperCreateClone from './helper.create-clone';
import helperCreateMockFromPrototype from './helper.create-mock-from-prototype';
import helperDefinePropertyDescriptor from './helper.define-property-descriptor';
import helperExtractMethodsFromPrototype from './helper.extract-methods-from-prototype';
import helperExtractPropertiesFromPrototype from './helper.extract-properties-from-prototype';
import helperExtractPropertyDescriptor from './helper.extract-property-descriptor';
Expand All @@ -20,7 +22,9 @@ getGlobal().ngMockshelperMockService = getGlobal().ngMockshelperMockService || {
getGlobal().ngMockshelperMockService.mockFunction.customMockFunction = func;
},

createClone: helperCreateClone,
createMockFromPrototype: helperCreateMockFromPrototype,
definePropertyDescriptor: helperDefinePropertyDescriptor,
extractMethodsFromPrototype: helperExtractMethodsFromPrototype,
extractPropertiesFromPrototype: helperExtractPropertiesFromPrototype,
extractPropertyDescriptor: helperExtractPropertyDescriptor,
Expand All @@ -31,7 +35,9 @@ getGlobal().ngMockshelperMockService = getGlobal().ngMockshelperMockService || {
};

export default ((): {
createClone: typeof helperCreateClone;
createMockFromPrototype: typeof helperCreateMockFromPrototype;
definePropertyDescriptor: typeof helperDefinePropertyDescriptor;
extractMethodsFromPrototype: typeof helperExtractMethodsFromPrototype;
extractPropertiesFromPrototype: typeof helperExtractPropertiesFromPrototype;
extractPropertyDescriptor: typeof helperExtractPropertyDescriptor;
Expand Down
2 changes: 1 addition & 1 deletion libs/ng-mocks/src/lib/mock-service/helper.use-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const applyCallback = (def: any, inst: any, callbacks: any[], injector?: Injecto
continue;
}

mockHelperStub(instance, override);
instance = mockHelperStub(instance, override);
}

return instance;
Expand Down
14 changes: 6 additions & 8 deletions libs/ng-mocks/src/lib/mock-service/mock-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,23 @@ import funcGetProvider from '../common/func.get-provider';
import { isNgInjectionToken } from '../common/func.is-ng-injection-token';
import ngMocksUniverse from '../common/ng-mocks-universe';

import helperDefinePropertyDescriptor from './helper.define-property-descriptor';
import helperExtractPropertyDescriptor from './helper.extract-property-descriptor';
import helperUseFactory from './helper.use-factory';
import { MockService } from './mock-service';

const { neverMockProvidedFunction, neverMockToken } = coreConfig;

const applyMissedClassProperties = (instance: any, useClass: Type<any>) => {
const applyMissingClassProperties = (instance: any, useClass: Type<any>) => {
const existing = Object.getOwnPropertyNames(instance);
const child = MockService(useClass);

for (const name of Object.getOwnPropertyNames(child)) {
if (existing.indexOf(name) !== -1) {
continue;
}
const def = Object.getOwnPropertyDescriptor(child, name);
// istanbul ignore else
if (def) {
def.configurable = true;
Object.defineProperty(instance, name, def);
}
const def = helperExtractPropertyDescriptor(child, name);
helperDefinePropertyDescriptor(instance, name, def);
}
};

Expand All @@ -34,7 +32,7 @@ const createFactoryProvider = (provider: any, provide: any) =>
// Magic below adds missed properties to the instance to
// fulfill missed abstract methods.
if (provide !== provider && Object.keys(provider).indexOf('useClass') !== -1) {
applyMissedClassProperties(instance, provider.useClass);
applyMissingClassProperties(instance, provider.useClass);
}

return instance;
Expand Down
Loading

0 comments on commit 9310d34

Please sign in to comment.