UNPKG

4 kBPlain TextView Raw
1// Dependencies:
2import { getGlobalInterceptors, getInterceptors, getInterceptorsForToken } from './ineeda-interceptors';
3import { IneedaInterceptor, IneedaInterceptorFunction, IneedaInterceptorOrToken, IneedaInterceptorToken, IneedaKey, IneedaProxy, NOOP } from './ineeda-types';
4
5// Constants:
6const DEFAULT_PROPERTY_DESCRIPTION: PropertyDescriptor = { configurable: true, enumerable: true, writable: true };
7
8export function createProxy <T, K extends IneedaKey<T>> (valuesExternal: Partial<T>, key?: IneedaKey<T>): T & IneedaProxy<T> {
9 valuesExternal = valuesExternal || <Partial<T>>{};
10 let valuesInternal: IneedaProxy<T> = { hasOwnProperty, intercept, reset, toJSON, toString };
11
12 let interceptors: Array<IneedaInterceptorFunction<T, keyof T>>;
13 let intercepted: Array<IneedaKey<T>> = [];
14
15 reset();
16 let proxyBase = key ? NOOP : {};
17 return new Proxy(<any>proxyBase, { apply, get, getOwnPropertyDescriptor, ownKeys, set });
18
19 function apply (): void {
20 throw new Error(`
21 "${key}" is not implemented.
22 `);
23 }
24
25 function get <K extends keyof T> (target: T, key: IneedaKey<T>): any {
26 if (_isInternalKey(key)) {
27 return valuesInternal[key];
28 }
29 if (_isExternalKey(key)) {
30 return _runInterceptors(target, key, valuesExternal[key]);
31 }
32 if (_isObjectKey(key) || _isSymbol(key)) {
33 return {}[key];
34 }
35 if (_isFunctionKey(key)) {
36 return NOOP[key];
37 }
38
39 return _runInterceptors(target, <K>key, createProxy<K, IneedaKey<K>>(null, key));
40 }
41
42 function getOwnPropertyDescriptor (target: T, key: keyof T): PropertyDescriptor {
43 let descriptor = Object.getOwnPropertyDescriptor(target, key) || DEFAULT_PROPERTY_DESCRIPTION;
44 descriptor.value = get(target, key);
45 return descriptor;
46 }
47
48 function hasOwnProperty (): boolean {
49 return true;
50 }
51
52 function intercept (interceptorOrToken: IneedaInterceptorOrToken<T>): T {
53 if (_hasInterceptorForToken(interceptorOrToken)) {
54 interceptors = interceptors.concat(getInterceptorsForToken<T>(interceptorOrToken));
55 } else {
56 interceptors = interceptors.concat(getInterceptors<T>(<IneedaInterceptor<T>>interceptorOrToken));
57 }
58 return this;
59 }
60
61 function ownKeys (): Array<string> {
62 return ['prototype'].concat(Object.keys(valuesExternal));
63 }
64
65 function reset (): T {
66 interceptors = getGlobalInterceptors();
67 return this;
68 }
69
70 function set (target: T, key: keyof T, value: any): boolean {
71 valuesExternal[key] = value;
72 return true;
73 }
74
75 function toJSON (): Partial<T> {
76 return valuesExternal;
77 }
78
79 function toString (): string {
80 return '[object IneedaMock]';
81 }
82
83 function _hasInterceptorForToken (token: IneedaInterceptorOrToken<T>): token is IneedaInterceptorToken {
84 return !!getInterceptorsForToken<T>(token);
85 }
86
87 function _isFunctionKey (key: IneedaKey<T>): key is keyof Function {
88 return key in NOOP;
89 }
90
91 function _isExternalKey (key: IneedaKey<T>): key is keyof T {
92 return Object.hasOwnProperty.call(valuesExternal, key);
93 }
94
95 function _isInternalKey (key: IneedaKey<T>): key is keyof IneedaProxy<T> {
96 return Object.hasOwnProperty.call(valuesInternal, key);
97 }
98
99 function _isObjectKey (key: IneedaKey<T>): key is keyof Object {
100 return key in {};
101 }
102
103 function _isSymbol (key: PropertyKey): boolean {
104 return typeof key === 'symbol' || key === 'inspect';
105 }
106
107 function _runInterceptors <K extends keyof T> (target: T, key: K, value: any): any {
108 if (!intercepted.includes(key)) {
109 valuesExternal[key] = interceptors.reduce((p, n) => {
110 return n(p, key, valuesExternal, target);
111 }, value);
112 intercepted.push(key);
113 }
114 return valuesExternal[key];
115 }
116}