[gjs] lang: Lang.Class.implements()



commit e5ea75736addcec28e217e3c268220b1fedc628e
Author: Philip Chimento <philip endlessm com>
Date:   Fri Jun 12 17:42:43 2015 -0700

    lang: Lang.Class.implements()
    
    This function does the work of g_type_is_a() and G_IS_...() for
    interfaces. It allows us to make better assertions in our tests and fills
    a need in GJS since there is no such thing as the instanceof operator for
    interfaces.
    
    Note. implements is a reserved word in ES6 but reserved words are allowed
    as property names, and we use reserved words elsewhere in GJS as property
    names as well.
    
    (Collaboration by Philip Chimento <philip endlessm com> and Roberto
    Goizueta <goizueta endlessm com>)
    
    https://bugzilla.gnome.org/show_bug.cgi?id=751343

 installed-tests/js/testGObjectClass.js     |    2 +-
 installed-tests/js/testGObjectInterface.js |   54 +++++++++++++++++++++------
 installed-tests/js/testInterface.js        |   48 +++++++++++++++++++-----
 modules/lang.js                            |   17 +++++++++
 modules/overrides/GObject.js               |   11 ++++++
 5 files changed, 109 insertions(+), 23 deletions(-)
---
diff --git a/installed-tests/js/testGObjectClass.js b/installed-tests/js/testGObjectClass.js
index fb45acc..3cb368a 100644
--- a/installed-tests/js/testGObjectClass.js
+++ b/installed-tests/js/testGObjectClass.js
@@ -279,7 +279,7 @@ function testInterface() {
     instance.init(new Gio.Cancellable);
     JSUnit.assertEquals(true, instance.inited);
 
-    // JSUnit.assertTrue(instance instanceof Gio.Initable)
+    JSUnit.assertTrue(instance.constructor.implements(Gio.Initable));
 }
 
 function testDerived() {
diff --git a/installed-tests/js/testGObjectInterface.js b/installed-tests/js/testGObjectInterface.js
index 6fce052..8ed04fd 100644
--- a/installed-tests/js/testGObjectInterface.js
+++ b/installed-tests/js/testGObjectInterface.js
@@ -117,12 +117,13 @@ const ImplementationOfTwoInterfaces = new Lang.Class({
 });
 
 function testGObjectClassCanImplementInterface() {
-    // Test considered passing if no exception thrown
-    new GObjectImplementingLangInterface();
+    // Test will fail if the constructor throws an exception
+    let obj = new GObjectImplementingLangInterface();
+    JSUnit.assertTrue(obj.constructor.implements(AnInterface));
 }
 
 function testGObjectCanImplementInterfacesFromJSAndC() {
-    // Test considered passing if no exception thrown
+    // Test will fail if the constructor throws an exception
     const ObjectImplementingLangInterfaceAndCInterface = new Lang.Class({
         Name: 'ObjectImplementingLangInterfaceAndCInterface',
         Extends: GObject.Object,
@@ -132,7 +133,9 @@ function testGObjectCanImplementInterfacesFromJSAndC() {
             this.parent(props);
         }
     });
-    new ObjectImplementingLangInterfaceAndCInterface();
+    let obj = new ObjectImplementingLangInterfaceAndCInterface();
+    JSUnit.assertTrue(obj.constructor.implements(AnInterface));
+    JSUnit.assertTrue(obj.constructor.implements(Gio.Initable));
 }
 
 function testGObjectInterfaceIsInstanceOfInterfaces() {
@@ -149,8 +152,9 @@ function testGObjectInterfaceTypeName() {
 }
 
 function testGObjectCanImplementInterface() {
-    // Test considered passing if no exception thrown
-    new GObjectImplementingGObjectInterface();
+    // Test will fail if the constructor throws an exception
+    let obj = new GObjectImplementingGObjectInterface();
+    JSUnit.assertTrue(obj.constructor.implements(AGObjectInterface));
 }
 
 function testGObjectImplementingInterfaceHasCorrectClassObject() {
@@ -162,7 +166,7 @@ function testGObjectImplementingInterfaceHasCorrectClassObject() {
 }
 
 function testGObjectCanImplementBothGObjectAndNonGObjectInterfaces() {
-    // Test considered passing if no exception thrown
+    // Test will fail if the constructor throws an exception
     const GObjectImplementingBothKindsOfInterface = new Lang.Class({
         Name: 'GObjectImplementingBothKindsOfInterface',
         Extends: GObject.Object,
@@ -178,7 +182,9 @@ function testGObjectCanImplementBothGObjectAndNonGObjectInterfaces() {
         required: function () {},
         requiredG: function () {}
     });
-    new GObjectImplementingBothKindsOfInterface();
+    let obj = new GObjectImplementingBothKindsOfInterface();
+    JSUnit.assertTrue(obj.constructor.implements(AnInterface));
+    JSUnit.assertTrue(obj.constructor.implements(AGObjectInterface));
 }
 
 function testGObjectCanImplementRequiredFunction() {
@@ -200,8 +206,9 @@ function testGObjectMustImplementRequiredFunction () {
 }
 
 function testGObjectDoesntHaveToImplementOptionalFunction() {
-    // Test considered passing if no exception thrown
-    new MinimalImplementationOfAGObjectInterface();
+    // Test will fail if the constructor throws an exception
+    let obj = new MinimalImplementationOfAGObjectInterface();
+    JSUnit.assertTrue(obj.constructor.implements(AGObjectInterface));
 }
 
 function testGObjectCanDeferToInterfaceOptionalFunction() {
@@ -215,8 +222,10 @@ function testGObjectCanChainUpToInterface() {
 }
 
 function testGObjectInterfaceCanRequireOtherInterface() {
-    // Test considered passing if no exception thrown
-    new ImplementationOfTwoInterfaces();
+    // Test will fail if the constructor throws an exception
+    let obj = new ImplementationOfTwoInterfaces();
+    JSUnit.assertTrue(obj.constructor.implements(AGObjectInterface));
+    JSUnit.assertTrue(obj.constructor.implements(InterfaceRequiringGObjectInterface));
 }
 
 function testGObjectInterfaceCanChainUpToOtherInterface() {
@@ -335,4 +344,25 @@ function testInterfaceIsOfCorrectTypeForMetaclass() {
     JSUnit.assertTrue(MyMetaInterface instanceof GObject.Interface);
 }
 
+function testSubclassImplementsTheSameInterfaceAsItsParent() {
+    const SubObject = new Lang.Class({
+        Name: 'SubObject',
+        Extends: GObjectImplementingGObjectInterface
+    });
+    let obj = new SubObject();
+    JSUnit.assertTrue(obj.constructor.implements(AGObjectInterface));
+    JSUnit.assertEquals('foobar', obj.interface_prop);  // override not needed
+}
+
+function testSubclassCanReimplementTheSameInterfaceAsItsParent() {
+    const SubImplementer = new Lang.Class({
+        Name: 'SubImplementer',
+        Extends: GObjectImplementingGObjectInterface,
+        Implements: [ AGObjectInterface ]
+    });
+    let obj = new SubImplementer();
+    JSUnit.assertTrue(obj.constructor.implements(AGObjectInterface));
+    JSUnit.assertEquals('foobar', obj.interface_prop);  // override not needed
+}
+
 JSUnit.gjstestRun(this, JSUnit.setUp, JSUnit.tearDown);
diff --git a/installed-tests/js/testInterface.js b/installed-tests/js/testInterface.js
index 8c55f3b..c8b130f 100644
--- a/installed-tests/js/testInterface.js
+++ b/installed-tests/js/testInterface.js
@@ -104,8 +104,9 @@ function testInterfaceCannotBeInstantiated() {
 }
 
 function testObjectCanImplementInterface() {
-    // Test considered passing if no exception thrown
-    new ObjectImplementingAnInterface();
+    // Test will fail if the constructor throws an exception
+    let obj = new ObjectImplementingAnInterface();
+    JSUnit.assertTrue(obj.constructor.implements(AnInterface));
 }
 
 function testObjectImplementingInterfaceHasCorrectConstructor() {
@@ -127,8 +128,9 @@ function testClassMustImplementRequiredFunction() {
 }
 
 function testClassDoesntHaveToImplementOptionalFunction() {
-    // Test considered passing if no exception thrown
-    new MinimalImplementationOfAnInterface();
+    // Test will fail if the constructor throws an exception
+    let obj = new MinimalImplementationOfAnInterface();
+    JSUnit.assertTrue(obj.constructor.implements(AnInterface));
 }
 
 function testObjectCanDeferToInterfaceOptionalFunction() {
@@ -181,8 +183,10 @@ function testObjectCanOverrideInterfaceSetter() {
 }
 
 function testInterfaceCanRequireOtherInterface() {
-    // Test considered passing if no exception thrown
-    new ImplementationOfTwoInterfaces();
+    // Test will fail if the constructor throws an exception
+    let obj = new ImplementationOfTwoInterfaces();
+    JSUnit.assertTrue(obj.constructor.implements(AnInterface));
+    JSUnit.assertTrue(obj.constructor.implements(InterfaceRequiringOtherInterface));
 }
 
 function testRequiresCanBeEmpty() {
@@ -240,23 +244,28 @@ function testClassMustImplementRequiredInterfacesInCorrectOrder() {
 }
 
 function testInterfacesCanBeImplementedOnAParentClass() {
-    // Test considered passing if no exception thrown
+    // Test will fail if the constructor throws an exception
     const ObjectInheritingFromInterfaceImplementation = new Lang.Class({
         Name: 'ObjectInheritingFromInterfaceImplementation',
         Extends: ObjectImplementingAnInterface,
         Implements: [ InterfaceRequiringOtherInterface ],
     });
-    new ObjectInheritingFromInterfaceImplementation();
+    let obj = new ObjectInheritingFromInterfaceImplementation();
+    JSUnit.assertTrue(obj.constructor.implements(AnInterface));
+    JSUnit.assertTrue(obj.constructor.implements(InterfaceRequiringOtherInterface));
 }
 
 function testInterfacesCanRequireBeingImplementedOnASubclass() {
-    // Test considered passing if no exception thrown
+    // Test will fail if the constructor throws an exception
     const ObjectImplementingInterfaceRequiringParentObject = new Lang.Class({
         Name: 'ObjectImplementingInterfaceRequiringParentObject',
         Extends: ObjectImplementingAnInterface,
         Implements: [ InterfaceRequiringOtherInterface, InterfaceRequiringClassAndInterface ]
     });
-    new ObjectImplementingInterfaceRequiringParentObject();
+    let obj = new ObjectImplementingInterfaceRequiringParentObject();
+    JSUnit.assertTrue(obj.constructor.implements(AnInterface));
+    JSUnit.assertTrue(obj.constructor.implements(InterfaceRequiringOtherInterface));
+    JSUnit.assertTrue(obj.constructor.implements(InterfaceRequiringClassAndInterface));
 }
 
 function testObjectsMustSubclassIfRequired() {
@@ -272,4 +281,23 @@ function testInterfaceMethodsCanCallOtherInterfaceMethods() {
     JSUnit.assertEquals('interface private method', obj.usesThis());
 }
 
+function testSubclassImplementsTheSameInterfaceAsItsParent() {
+    const SubObject = new Lang.Class({
+        Name: 'SubObject',
+        Extends: ObjectImplementingAnInterface
+    });
+    let obj = new SubObject();
+    JSUnit.assertTrue(obj.constructor.implements(AnInterface));
+}
+
+function testSubclassCanReimplementTheSameInterfaceAsItsParent() {
+    const SubImplementer = new Lang.Class({
+        Name: 'SubImplementer',
+        Extends: ObjectImplementingAnInterface,
+        Implements: [ AnInterface ]
+    });
+    let obj = new SubImplementer();
+    JSUnit.assertTrue(obj.constructor.implements(AnInterface));
+}
+
 JSUnit.gjstestRun(this, JSUnit.setUp, JSUnit.tearDown);
diff --git a/modules/lang.js b/modules/lang.js
index 0f64807..5179fd9 100644
--- a/modules/lang.js
+++ b/modules/lang.js
@@ -227,6 +227,9 @@ Class.prototype._construct = function(params) {
     newClass._init.apply(newClass, arguments);
 
     let interfaces = params.Implements || [];
+    // If the parent already implements an interface, then we do too
+    if (parent instanceof Class)
+        interfaces = interfaces.filter((iface) => !parent.implements(iface));
 
     Object.defineProperties(newClass.prototype, {
         '__metaclass__': { writable: false,
@@ -246,6 +249,20 @@ Class.prototype._construct = function(params) {
     return newClass;
 };
 
+/**
+ * Check whether this class conforms to the interface "iface".
+ * @param {object} iface a Lang.Interface
+ * @returns: whether this class implements iface
+ * @type: boolean
+ */
+Class.prototype.implements = function (iface) {
+    if (_interfacePresent(iface, this.prototype))
+        return true;
+    if (this.__super__ instanceof Class)
+        return this.__super__.implements(iface);
+    return false;
+};
+
 Class.prototype._init = function(params) {
     let name = params.Name;
 
diff --git a/modules/overrides/GObject.js b/modules/overrides/GObject.js
index 7e9e9d5..8c9dfda 100644
--- a/modules/overrides/GObject.js
+++ b/modules/overrides/GObject.js
@@ -134,6 +134,8 @@ const GObjectMeta = new Lang.Class({
             throw new TypeError('GObject.Class used with invalid base class (is ' + parent + ')');
 
         let interfaces = params.Implements || [];
+        if (parent instanceof Lang.Class)
+            interfaces = interfaces.filter((iface) => !parent.implements(iface));
         let gobjectInterfaces = _getGObjectInterfaces(interfaces);
 
         let propertiesArray = _propertiesAsArray(params.Properties);
@@ -166,6 +168,15 @@ const GObjectMeta = new Lang.Class({
         });
 
         return newClass;
+    },
+
+    // Overrides Lang.Class.implements()
+    implements: function (iface) {
+        if (iface instanceof GObject.Interface) {
+            return GObject.type_is_a(this.$gtype, iface.$gtype);
+        } else {
+            return this.parent(iface);
+        }
     }
 });
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]