// Dependencies: import { getGlobalInterceptors, getInterceptors, getInterceptorsForToken } from './ineeda-interceptors'; import { IneedaInterceptor, IneedaInterceptorFunction, IneedaInterceptorOrToken, IneedaInterceptorToken, IneedaKey, IneedaProxy, NOOP } from './ineeda-types'; // Constants: const DEFAULT_PROPERTY_DESCRIPTION: PropertyDescriptor = { configurable: true, enumerable: true, writable: true }; export function createProxy > (valuesExternal: Partial, key?: IneedaKey): T & IneedaProxy { valuesExternal = valuesExternal || >{}; let valuesInternal: IneedaProxy = { hasOwnProperty, intercept, reset, toJSON, toString }; let interceptors: Array>; let intercepted: Array> = []; reset(); let proxyBase = key ? NOOP : {}; return new Proxy(proxyBase, { apply, get, getOwnPropertyDescriptor, ownKeys, set }); function apply (): void { throw new Error(` "${key}" is not implemented. `); } function get (target: T, key: IneedaKey): any { if (_isInternalKey(key)) { return valuesInternal[key]; } if (_isExternalKey(key)) { return _runInterceptors(target, key, valuesExternal[key]); } if (_isObjectKey(key) || _isSymbol(key)) { return {}[key]; } if (_isFunctionKey(key)) { return NOOP[key]; } return _runInterceptors(target, key, createProxy>(null, key)); } function getOwnPropertyDescriptor (target: T, key: keyof T): PropertyDescriptor { let descriptor = Object.getOwnPropertyDescriptor(target, key) || DEFAULT_PROPERTY_DESCRIPTION; descriptor.value = get(target, key); return descriptor; } function hasOwnProperty (): boolean { return true; } function intercept (interceptorOrToken: IneedaInterceptorOrToken): T { if (_hasInterceptorForToken(interceptorOrToken)) { interceptors = interceptors.concat(getInterceptorsForToken(interceptorOrToken)); } else { interceptors = interceptors.concat(getInterceptors(>interceptorOrToken)); } return this; } function ownKeys (): Array { return ['prototype'].concat(Object.keys(valuesExternal)); } function reset (): T { interceptors = getGlobalInterceptors(); return this; } function set (target: T, key: keyof T, value: any): boolean { valuesExternal[key] = value; return true; } function toJSON (): Partial { return valuesExternal; } function toString (): string { return '[object IneedaMock]'; } function _hasInterceptorForToken (token: IneedaInterceptorOrToken): token is IneedaInterceptorToken { return !!getInterceptorsForToken(token); } function _isFunctionKey (key: IneedaKey): key is keyof Function { return key in NOOP; } function _isExternalKey (key: IneedaKey): key is keyof T { return Object.hasOwnProperty.call(valuesExternal, key); } function _isInternalKey (key: IneedaKey): key is keyof IneedaProxy { return Object.hasOwnProperty.call(valuesInternal, key); } function _isObjectKey (key: IneedaKey): key is keyof Object { return key in {}; } function _isSymbol (key: PropertyKey): boolean { return typeof key === 'symbol' || key === 'inspect'; } function _runInterceptors (target: T, key: K, value: any): any { if (!intercepted.includes(key)) { valuesExternal[key] = interceptors.reduce((p, n) => { return n(p, key, valuesExternal, target); }, value); intercepted.push(key); } return valuesExternal[key]; } }