typemoq
Version:
A simple mocking library for TypeScript
204 lines (167 loc) • 7.26 kB
text/typescript
import * as _ from "lodash";
import * as common from "../Common/_all";
import * as error from "../Error/_all";
import { Consts } from "../Consts";
import { IProxy } from "./IProxy";
import { ICallContext } from "./ICallContext";
import { ICallInterceptor } from "./ICallInterceptor";
import { PropertyInfo, MethodInfo, MethodInvocation, MethodGetterInvocation, MethodSetterInvocation, ValueGetterInvocation, ValueSetterInvocation } from "./Invocation";
export class ProxyES5<T> implements IProxy {
readonly ___id = Consts.IPROXY_ID_VALUE;
private constructor(target: T, interceptor: ICallInterceptor) {
this.check(target);
let that = this;
let props = common.PropertyRetriever.getOwnAndPrototypeEnumerablesAndNonenumerables(target);
_.each(props, (prop: { name: string; desc: common.PropDescriptor }) => {
if (_.isFunction(prop.desc.value)) {
let propDesc: common.PropDescriptor = {
configurable: prop.desc.configurable,
enumerable: prop.desc.enumerable,
writable: prop.desc.writable
};
this.defineMethodProxy(that, interceptor, target, prop.name, propDesc);
}
else {
let propDesc: common.PropDescriptor = {
configurable: prop.desc.configurable,
enumerable: prop.desc.enumerable
};
if (prop.desc.value !== undefined)
this.defineValuePropertyProxy(that, interceptor, target, prop.name, prop.desc.value, propDesc);
else
this.defineGetSetPropertyProxy(that, interceptor, target, prop.name, prop.desc.get, prop.desc.set, propDesc);
}
});
}
static of<U>(target: U, interceptor: ICallInterceptor): ProxyES5<U> {
ProxyES5.check(target);
let result: any;
if (_.isFunction(target)) {
let funcName = common.Utils.functionName(target);
result = ProxyES5.methodProxyValue(undefined, interceptor, target, funcName, null);
}
else {
result = new ProxyES5(target, interceptor);
}
return result;
}
static isProxy(obj: any): boolean {
if (!_.isNil(obj) &&
!_.isUndefined(obj[Consts.IPROXY_ID_NAME]) && obj[Consts.IPROXY_ID_NAME] === Consts.IPROXY_ID_VALUE)
return true;
else
return false;
}
private static check<U>(target: U): void {
ProxyES5.checkNotNullOrUndefined(target);
// allow only primitive objects and functions
let ok = false;
if (_.isFunction(target) ||
(_.isObject(target) && !ProxyES5.isPrimitiveObject(target)))
ok = true;
if (!ok)
throw new error.MockException(error.MockExceptionReason.InvalidArg,
target, `'${target}'; proxy argument should be a function or a non primitive object`);
}
private check<U>(target: U): void {
ProxyES5.checkNotNullOrUndefined(target);
// allow only non primitive objects
let ok = false;
if (!_.isFunction(target) &&
(_.isObject(target) && !ProxyES5.isPrimitiveObject(target)))
ok = true;
if (!ok)
throw new error.MockException(error.MockExceptionReason.InvalidArg,
target, `'${target}'; proxy argument should be a non primitive object`);
}
private static checkNotNullOrUndefined<U>(instance: U): void {
if (_.isNil(instance))
throw new error.MockException(error.MockExceptionReason.InvalidArg,
instance, `'${instance}'; proxy argument is required`);
}
private static isPrimitiveObject(obj: Object): boolean {
let result = false;
if (_.isFunction(obj) ||
_.isArray(obj) ||
_.isDate(obj) ||
_.isNull(obj))
result = true;
return result;
}
private defineMethodProxy(
that: Object,
interceptor: ICallInterceptor,
target: T,
propName: string,
propDesc: common.PropDescriptor = { configurable: true, enumerable: true, writable: false }) {
propDesc.value = ProxyES5.methodProxyValue(that, interceptor, target, propName, propDesc);
this.defineProperty(that, propName, propDesc);
}
private static methodProxyValue<U>(
that: Object,
interceptor: ICallInterceptor,
target: U,
propName: string,
propDesc: common.PropDescriptor): () => any {
function proxy() {
let method = new MethodInfo(target, propName, propDesc);
let invocation: ICallContext = new MethodInvocation(that, method, arguments);
interceptor.intercept(invocation);
return invocation.returnValue;
}
return proxy;
}
private defineValuePropertyProxy(
that: Object,
interceptor: ICallInterceptor,
target: T,
propName: string,
propValue: any,
propDesc: common.PropDescriptor = { configurable: true, enumerable: true }) {
function getProxy(): any {
let method = new PropertyInfo(target, propName);
let invocation: ICallContext = new ValueGetterInvocation(method, propValue);
interceptor.intercept(invocation);
return invocation.returnValue;
}
propDesc.get = getProxy;
function setProxy(v: any): void {
let method = new PropertyInfo(target, propName);
let invocation: ICallContext = new ValueSetterInvocation(method, arguments);
interceptor.intercept(invocation);
}
propDesc.set = setProxy;
this.defineProperty(that, propName, propDesc);
}
private defineGetSetPropertyProxy(
that: Object,
interceptor: ICallInterceptor,
target: T,
propName: string,
get?: () => any,
set?: (v: any) => void,
propDesc: common.PropDescriptor = { configurable: true, enumerable: true }) {
function getProxy(): any {
let method = new PropertyInfo(target, propName);
let invocation: ICallContext = new MethodGetterInvocation(method, get);
interceptor.intercept(invocation);
return invocation.returnValue;
}
propDesc.get = getProxy;
function setProxy(v: any): void {
let method = new PropertyInfo(target, propName);
let invocation: ICallContext = new MethodSetterInvocation(method, set, arguments);
interceptor.intercept(invocation);
}
propDesc.set = setProxy;
this.defineProperty(that, propName, propDesc);
}
private defineProperty(obj: Object, name: string, desc: common.PropDescriptor) {
try {
Object.defineProperty(obj, name, desc);
}
catch (e) {
console.log(e.message);
}
}
}