1 |
|
2 | import { getGlobalInterceptors, getInterceptors, getInterceptorsForToken } from './ineeda-interceptors';
|
3 | import { IneedaInterceptor, IneedaInterceptorFunction, IneedaInterceptorOrToken, IneedaInterceptorToken, IneedaKey, IneedaProxy, NOOP } from './ineeda-types';
|
4 |
|
5 |
|
6 | const DEFAULT_PROPERTY_DESCRIPTION: PropertyDescriptor = { configurable: true, enumerable: true, writable: true };
|
7 |
|
8 | export 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 | }
|