]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Add support for common object methods to Proxies (#8452)
authorJukka Kurkela <jukka.kurkela@gmail.com>
Thu, 18 Feb 2021 15:04:46 +0000 (17:04 +0200)
committerGitHub <noreply@github.com>
Thu, 18 Feb 2021 15:04:46 +0000 (10:04 -0500)
src/helpers/helpers.config.js
test/specs/helpers.config.tests.js

index 2f61a7915973c23d3065ac18dc45927916864911..65c415b80b15bc3bc72933300b7b0fc4ac110060 100644 (file)
@@ -15,19 +15,46 @@ export function _createResolver(scopes, prefixes = ['']) {
     override: (scope) => _createResolver([scope].concat(scopes), prefixes),
   };
   return new Proxy(cache, {
+    /**
+     * A trap for getting property values.
+     */
     get(target, prop) {
       return _cached(target, prop,
         () => _resolveWithPrefixes(prop, prefixes, scopes));
     },
 
-    ownKeys(target) {
-      return getKeysFromAllScopes(target);
-    },
-
+    /**
+     * A trap for Object.getOwnPropertyDescriptor.
+     * Also used by Object.hasOwnProperty.
+     */
     getOwnPropertyDescriptor(target, 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) {
       scopes[0][prop] = value;
       return delete target[prop];
@@ -54,19 +81,46 @@ export function _attachContext(proxy, context, subProxy) {
     override: (scope) => _attachContext(proxy.override(scope), context, subProxy)
   };
   return new Proxy(cache, {
+    /**
+     * A trap for getting property values.
+     */
     get(target, prop, receiver) {
       return _cached(target, prop,
         () => _resolveWithContext(target, prop, receiver));
     },
 
-    ownKeys() {
-      return Reflect.ownKeys(proxy);
+    /**
+     * A trap for Object.getOwnPropertyDescriptor.
+     * Also used by Object.hasOwnProperty.
+     */
+    getOwnPropertyDescriptor(target, prop) {
+      return Reflect.getOwnPropertyDescriptor(proxy, prop);
     },
 
-    getOwnPropertyDescriptor(target, prop) {
-      return Reflect.getOwnPropertyDescriptor(proxy._scopes[0], 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() {
+      return Reflect.ownKeys(proxy);
     },
 
+    /**
+     * A trap for setting property values.
+     */
     set(target, prop, value) {
       proxy[prop] = value;
       return delete target[prop];
index 37213dc5ed47c1d0aa7d1bdbc34d5a661c780add..2075af5e973d3096d3a6117f5d709309bff7a340 100644 (file)
@@ -88,6 +88,41 @@ describe('Chart.helpers.config', function() {
         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() {
@@ -249,6 +284,41 @@ describe('Chart.helpers.config', function() {
       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() {
       it('should default to true', function() {
         const options = {