Skip to content

Commit 5411be1

Browse files
authored
Add support for common object methods to Proxies (#8452)
1 parent ba99c41 commit 5411be1

File tree

2 files changed

+132
-8
lines changed

2 files changed

+132
-8
lines changed

src/helpers/helpers.config.js

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,46 @@ export function _createResolver(scopes, prefixes = ['']) {
1515
override: (scope) => _createResolver([scope].concat(scopes), prefixes),
1616
};
1717
return new Proxy(cache, {
18+
/**
19+
* A trap for getting property values.
20+
*/
1821
get(target, prop) {
1922
return _cached(target, prop,
2023
() => _resolveWithPrefixes(prop, prefixes, scopes));
2124
},
2225

23-
ownKeys(target) {
24-
return getKeysFromAllScopes(target);
25-
},
26-
26+
/**
27+
* A trap for Object.getOwnPropertyDescriptor.
28+
* Also used by Object.hasOwnProperty.
29+
*/
2730
getOwnPropertyDescriptor(target, prop) {
2831
return Reflect.getOwnPropertyDescriptor(target._scopes[0], prop);
2932
},
3033

34+
/**
35+
* A trap for Object.getPrototypeOf.
36+
*/
37+
getPrototypeOf() {
38+
return Reflect.getPrototypeOf(scopes[0]);
39+
},
40+
41+
/**
42+
* A trap for the in operator.
43+
*/
44+
has(target, prop) {
45+
return getKeysFromAllScopes(target).includes(prop);
46+
},
47+
48+
/**
49+
* A trap for Object.getOwnPropertyNames and Object.getOwnPropertySymbols.
50+
*/
51+
ownKeys(target) {
52+
return getKeysFromAllScopes(target);
53+
},
54+
55+
/**
56+
* A trap for setting property values.
57+
*/
3158
set(target, prop, value) {
3259
scopes[0][prop] = value;
3360
return delete target[prop];
@@ -54,19 +81,46 @@ export function _attachContext(proxy, context, subProxy) {
5481
override: (scope) => _attachContext(proxy.override(scope), context, subProxy)
5582
};
5683
return new Proxy(cache, {
84+
/**
85+
* A trap for getting property values.
86+
*/
5787
get(target, prop, receiver) {
5888
return _cached(target, prop,
5989
() => _resolveWithContext(target, prop, receiver));
6090
},
6191

62-
ownKeys() {
63-
return Reflect.ownKeys(proxy);
92+
/**
93+
* A trap for Object.getOwnPropertyDescriptor.
94+
* Also used by Object.hasOwnProperty.
95+
*/
96+
getOwnPropertyDescriptor(target, prop) {
97+
return Reflect.getOwnPropertyDescriptor(proxy, prop);
6498
},
6599

66-
getOwnPropertyDescriptor(target, prop) {
67-
return Reflect.getOwnPropertyDescriptor(proxy._scopes[0], prop);
100+
/**
101+
* A trap for Object.getPrototypeOf.
102+
*/
103+
getPrototypeOf() {
104+
return Reflect.getPrototypeOf(proxy);
105+
},
106+
107+
/**
108+
* A trap for the in operator.
109+
*/
110+
has(target, prop) {
111+
return Reflect.has(proxy, prop);
112+
},
113+
114+
/**
115+
* A trap for Object.getOwnPropertyNames and Object.getOwnPropertySymbols.
116+
*/
117+
ownKeys() {
118+
return Reflect.ownKeys(proxy);
68119
},
69120

121+
/**
122+
* A trap for setting property values.
123+
*/
70124
set(target, prop, value) {
71125
proxy[prop] = value;
72126
return delete target[prop];

test/specs/helpers.config.tests.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,41 @@ describe('Chart.helpers.config', function() {
8888
option3: 'defaults3'
8989
});
9090
});
91+
92+
it('should support common object methods', function() {
93+
const defaults = {
94+
option1: 'defaults'
95+
};
96+
class Options {
97+
constructor() {
98+
this.option2 = 'options';
99+
}
100+
get getter() {
101+
return 'options getter';
102+
}
103+
}
104+
const options = new Options();
105+
106+
const resolver = _createResolver([options, defaults]);
107+
108+
expect(Object.prototype.hasOwnProperty.call(resolver, 'option2')).toBeTrue();
109+
110+
expect(Object.prototype.hasOwnProperty.call(resolver, 'option1')).toBeFalse();
111+
expect(Object.prototype.hasOwnProperty.call(resolver, 'getter')).toBeFalse();
112+
expect(Object.prototype.hasOwnProperty.call(resolver, 'nonexistent')).toBeFalse();
113+
114+
expect(Object.keys(resolver)).toEqual(['option2']);
115+
expect(Object.getOwnPropertyNames(resolver)).toEqual(['option2', 'option1']);
116+
117+
expect('option2' in resolver).toBeTrue();
118+
expect('option1' in resolver).toBeTrue();
119+
expect('getter' in resolver).toBeFalse();
120+
expect('nonexistent' in resolver).toBeFalse();
121+
122+
expect(resolver instanceof Options).toBeTrue();
123+
124+
expect(resolver.getter).toEqual('options getter');
125+
});
91126
});
92127

93128
describe('_attachContext', function() {
@@ -249,6 +284,41 @@ describe('Chart.helpers.config', function() {
249284
expect(opts.fn).toEqual(1);
250285
});
251286

287+
it('should support common object methods', function() {
288+
const defaults = {
289+
option1: 'defaults'
290+
};
291+
class Options {
292+
constructor() {
293+
this.option2 = () => 'options';
294+
}
295+
get getter() {
296+
return 'options getter';
297+
}
298+
}
299+
const options = new Options();
300+
const resolver = _createResolver([options, defaults]);
301+
const opts = _attachContext(resolver, {index: 1});
302+
303+
expect(Object.prototype.hasOwnProperty.call(opts, 'option2')).toBeTrue();
304+
305+
expect(Object.prototype.hasOwnProperty.call(opts, 'option1')).toBeFalse();
306+
expect(Object.prototype.hasOwnProperty.call(opts, 'getter')).toBeFalse();
307+
expect(Object.prototype.hasOwnProperty.call(opts, 'nonexistent')).toBeFalse();
308+
309+
expect(Object.keys(opts)).toEqual(['option2']);
310+
expect(Object.getOwnPropertyNames(opts)).toEqual(['option2', 'option1']);
311+
312+
expect('option2' in opts).toBeTrue();
313+
expect('option1' in opts).toBeTrue();
314+
expect('getter' in opts).toBeFalse();
315+
expect('nonexistent' in opts).toBeFalse();
316+
317+
expect(opts instanceof Options).toBeTrue();
318+
319+
expect(opts.getter).toEqual('options getter');
320+
});
321+
252322
describe('_indexable and _scriptable', function() {
253323
it('should default to true', function() {
254324
const options = {

0 commit comments

Comments
 (0)