Add support for common object methods to Proxies (#8452)

This commit is contained in:
Jukka Kurkela 2021-02-18 17:04:46 +02:00 committed by GitHub
parent ba99c4185c
commit 5411be10a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 132 additions and 8 deletions

View File

@ -15,19 +15,46 @@ export function _createResolver(scopes, prefixes = ['']) {
override: (scope) => _createResolver([scope].concat(scopes), prefixes), override: (scope) => _createResolver([scope].concat(scopes), prefixes),
}; };
return new Proxy(cache, { return new Proxy(cache, {
/**
* A trap for getting property values.
*/
get(target, prop) { get(target, prop) {
return _cached(target, prop, return _cached(target, prop,
() => _resolveWithPrefixes(prop, prefixes, scopes)); () => _resolveWithPrefixes(prop, prefixes, scopes));
}, },
ownKeys(target) { /**
return getKeysFromAllScopes(target); * A trap for Object.getOwnPropertyDescriptor.
}, * Also used by Object.hasOwnProperty.
*/
getOwnPropertyDescriptor(target, prop) { getOwnPropertyDescriptor(target, prop) {
return Reflect.getOwnPropertyDescriptor(target._scopes[0], prop); return Reflect.getOwnPropertyDescriptor(target._scopes[0], prop);
}, },
/**
* A trap for Object.getPrototypeOf.
*/
getPrototypeOf() {
return Reflect.getPrototypeOf(scopes[0]);
},
/**
* A trap for the in operator.
*/
has(target, prop) {
return getKeysFromAllScopes(target).includes(prop);
},
/**
* A trap for Object.getOwnPropertyNames and Object.getOwnPropertySymbols.
*/
ownKeys(target) {
return getKeysFromAllScopes(target);
},
/**
* A trap for setting property values.
*/
set(target, prop, value) { set(target, prop, value) {
scopes[0][prop] = value; scopes[0][prop] = value;
return delete target[prop]; return delete target[prop];
@ -54,19 +81,46 @@ export function _attachContext(proxy, context, subProxy) {
override: (scope) => _attachContext(proxy.override(scope), context, subProxy) override: (scope) => _attachContext(proxy.override(scope), context, subProxy)
}; };
return new Proxy(cache, { return new Proxy(cache, {
/**
* A trap for getting property values.
*/
get(target, prop, receiver) { get(target, prop, receiver) {
return _cached(target, prop, return _cached(target, prop,
() => _resolveWithContext(target, prop, receiver)); () => _resolveWithContext(target, prop, receiver));
}, },
/**
* A trap for Object.getOwnPropertyDescriptor.
* Also used by Object.hasOwnProperty.
*/
getOwnPropertyDescriptor(target, prop) {
return Reflect.getOwnPropertyDescriptor(proxy, prop);
},
/**
* A trap for Object.getPrototypeOf.
*/
getPrototypeOf() {
return Reflect.getPrototypeOf(proxy);
},
/**
* A trap for the in operator.
*/
has(target, prop) {
return Reflect.has(proxy, prop);
},
/**
* A trap for Object.getOwnPropertyNames and Object.getOwnPropertySymbols.
*/
ownKeys() { ownKeys() {
return Reflect.ownKeys(proxy); return Reflect.ownKeys(proxy);
}, },
getOwnPropertyDescriptor(target, prop) { /**
return Reflect.getOwnPropertyDescriptor(proxy._scopes[0], prop); * A trap for setting property values.
}, */
set(target, prop, value) { set(target, prop, value) {
proxy[prop] = value; proxy[prop] = value;
return delete target[prop]; return delete target[prop];

View File

@ -88,6 +88,41 @@ describe('Chart.helpers.config', function() {
option3: 'defaults3' option3: 'defaults3'
}); });
}); });
it('should support common object methods', function() {
const defaults = {
option1: 'defaults'
};
class Options {
constructor() {
this.option2 = 'options';
}
get getter() {
return 'options getter';
}
}
const options = new Options();
const resolver = _createResolver([options, defaults]);
expect(Object.prototype.hasOwnProperty.call(resolver, 'option2')).toBeTrue();
expect(Object.prototype.hasOwnProperty.call(resolver, 'option1')).toBeFalse();
expect(Object.prototype.hasOwnProperty.call(resolver, 'getter')).toBeFalse();
expect(Object.prototype.hasOwnProperty.call(resolver, 'nonexistent')).toBeFalse();
expect(Object.keys(resolver)).toEqual(['option2']);
expect(Object.getOwnPropertyNames(resolver)).toEqual(['option2', 'option1']);
expect('option2' in resolver).toBeTrue();
expect('option1' in resolver).toBeTrue();
expect('getter' in resolver).toBeFalse();
expect('nonexistent' in resolver).toBeFalse();
expect(resolver instanceof Options).toBeTrue();
expect(resolver.getter).toEqual('options getter');
});
}); });
describe('_attachContext', function() { describe('_attachContext', function() {
@ -249,6 +284,41 @@ describe('Chart.helpers.config', function() {
expect(opts.fn).toEqual(1); expect(opts.fn).toEqual(1);
}); });
it('should support common object methods', function() {
const defaults = {
option1: 'defaults'
};
class Options {
constructor() {
this.option2 = () => 'options';
}
get getter() {
return 'options getter';
}
}
const options = new Options();
const resolver = _createResolver([options, defaults]);
const opts = _attachContext(resolver, {index: 1});
expect(Object.prototype.hasOwnProperty.call(opts, 'option2')).toBeTrue();
expect(Object.prototype.hasOwnProperty.call(opts, 'option1')).toBeFalse();
expect(Object.prototype.hasOwnProperty.call(opts, 'getter')).toBeFalse();
expect(Object.prototype.hasOwnProperty.call(opts, 'nonexistent')).toBeFalse();
expect(Object.keys(opts)).toEqual(['option2']);
expect(Object.getOwnPropertyNames(opts)).toEqual(['option2', 'option1']);
expect('option2' in opts).toBeTrue();
expect('option1' in opts).toBeTrue();
expect('getter' in opts).toBeFalse();
expect('nonexistent' in opts).toBeFalse();
expect(opts instanceof Options).toBeTrue();
expect(opts.getter).toEqual('options getter');
});
describe('_indexable and _scriptable', function() { describe('_indexable and _scriptable', function() {
it('should default to true', function() { it('should default to true', function() {
const options = { const options = {