From fc91c7e9c0495941716a636503abf615c7cce463 Mon Sep 17 00:00:00 2001
From: mucaho <mucaho@gmail.com>
Date: Wed, 17 May 2017 14:54:55 +0200
Subject: [PATCH] Setup event callbacks before init

Setup event callbacks defined in the events object before calling
the init method in component and system definition.
This allows triggering bound callbacks in the init method.
---
 src/core/core.js           |  8 ++---
 src/core/systems.js        |  9 +++---
 tests/unit/core/core.js    | 63 ++++++++++++++++++++++++++++++++++++++
 tests/unit/core/systems.js | 63 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 134 insertions(+), 9 deletions(-)

diff --git a/src/core/core.js b/src/core/core.js
index a4d8e0e1..74d7081f 100644
--- a/src/core/core.js
+++ b/src/core/core.js
@@ -323,10 +323,6 @@ Crafty.fn = Crafty.prototype = {
                     Object.defineProperty(this, propertyName, props[propertyName]);
                 }
             }
-            // Call constructor function
-            if (comp && "init" in comp) {
-                comp.init.call(this);
-            }
             // Bind events
             if (comp && "events" in comp){
                 var auto = comp.events;
@@ -335,6 +331,10 @@ Crafty.fn = Crafty.prototype = {
                     this.bind(eventName, fn);
                 }
             }
+            // Call constructor function
+            if (comp && "init" in comp) {
+                comp.init.call(this);
+            }
         }
 
         this.trigger("NewComponent", comps);
diff --git a/src/core/systems.js b/src/core/systems.js
index 4bfcefaf..c28341e6 100644
--- a/src/core/systems.js
+++ b/src/core/systems.js
@@ -115,11 +115,6 @@ Crafty.CraftySystem = (function() {
                 Object.defineProperty(this, propertyName, props[propertyName]);
             }
         }
-
-        // Run any instantiation code
-        if (typeof this.init === "function") {
-            this.init(name);
-        }
         // If an events object is provided, bind the listed event handlers
         if ("events" in template) {
             var auto = template.events;
@@ -128,6 +123,10 @@ Crafty.CraftySystem = (function() {
                 this.bind(eventName, fn);
             }
         }
+        // Run any instantiation code
+        if (typeof this.init === "function") {
+            this.init(name);
+        }
     };
 })();
 
diff --git a/tests/unit/core/core.js b/tests/unit/core/core.js
index f7e0e019..74f837d2 100644
--- a/tests/unit/core/core.js
+++ b/tests/unit/core/core.js
@@ -122,6 +122,69 @@
     _.ok(propList.indexOf("_foo") === -1, "Property _foo is not enumerable");
   });
 
+  test("component - order of handling special members", function(_) {
+    _.expect(5 * 5 - 1);
+
+    Crafty.c("MemberOrderTest", {
+      // 1st: basic prop should be added to entity
+      foo: 1,
+      // 2nd: properties should be defined on entity
+      properties: {
+        bar: {
+          get: function() {
+            if (!this.getCalled) {
+              _.strictEqual(this.foo, 1);
+              // can't check this.bar here - infinite recursion
+              _.strictEqual(this.baz, undefined);
+              _.strictEqual(this.quux, undefined);
+              _.strictEqual(this.quuz, undefined);
+              this.getCalled = true;
+            }
+            return 2;
+          }
+        }
+      },
+      // 3rd: events should be bound on entity
+      events: {
+        "CustomEvent": function() {
+          _.strictEqual(this.foo, 1);
+          _.strictEqual(this.bar, 2);
+          _.strictEqual(this.baz, undefined);
+          _.strictEqual(this.quux, undefined);
+          _.strictEqual(this.quuz, undefined);
+          this.baz = 3;
+        }
+      },
+      // 4th: init method should be called on entity
+      init: function() {
+        this.trigger("CustomEvent");
+
+        _.strictEqual(this.foo, 1);
+        _.strictEqual(this.bar, 2);
+        _.strictEqual(this.baz, 3);
+        _.strictEqual(this.quux, undefined);
+        _.strictEqual(this.quuz, undefined);
+        this.quux = 4;
+      },
+      // 5th: remove method should be called on entity
+      remove: function() {
+        _.strictEqual(this.foo, 1);
+        _.strictEqual(this.bar, 2);
+        _.strictEqual(this.baz, 3);
+        _.strictEqual(this.quux, 4);
+        _.strictEqual(this.quuz, undefined);
+        this.quuz = 5;
+      }
+    });
+    var e = Crafty.e().addComponent("MemberOrderTest").removeComponent("MemberOrderTest");
+
+    _.strictEqual(e.foo, 1);
+    _.strictEqual(e.bar, 2);
+    _.strictEqual(e.baz, 3);
+    _.strictEqual(e.quux, 4);
+    _.strictEqual(e.quuz, 5);
+  });
+
   test("overwrite component definition", function(_) {
     Crafty.c('MyCompDef', { a: 0 });
     var e = Crafty.e('MyCompDef');
diff --git a/tests/unit/core/systems.js b/tests/unit/core/systems.js
index 8e4e3627..ddb51afa 100644
--- a/tests/unit/core/systems.js
+++ b/tests/unit/core/systems.js
@@ -266,5 +266,68 @@
     _.strictEqual(Crafty.s('MySystemDef').b, 1);
   });
 
+  test("order of handling special members", function(_) {
+    _.expect(5 * 5 - 1);
+
+    Crafty.s("MemberOrderTest", {
+      // 1st: basic prop should be added to entity
+      foo: 1,
+      // 2nd: properties should be defined on entity
+      properties: {
+        bar: {
+          get: function() {
+            if (!this.getCalled) {
+              _.strictEqual(this.foo, 1);
+              // can't check this.bar here - infinite recursion
+              _.strictEqual(this.baz, undefined);
+              _.strictEqual(this.quux, undefined);
+              _.strictEqual(this.quuz, undefined);
+              this.getCalled = true;
+            }
+            return 2;
+          }
+        }
+      },
+      // 3rd: events should be bound on entity
+      events: {
+        "CustomEvent": function() {
+          _.strictEqual(this.foo, 1);
+          _.strictEqual(this.bar, 2);
+          _.strictEqual(this.baz, undefined);
+          _.strictEqual(this.quux, undefined);
+          _.strictEqual(this.quuz, undefined);
+          this.baz = 3;
+        }
+      },
+      // 4th: init method should be called on entity
+      init: function() {
+        this.trigger("CustomEvent");
+
+        _.strictEqual(this.foo, 1);
+        _.strictEqual(this.bar, 2);
+        _.strictEqual(this.baz, 3);
+        _.strictEqual(this.quux, undefined);
+        _.strictEqual(this.quuz, undefined);
+        this.quux = 4;
+      },
+      // 5th: remove method should be called on entity
+      remove: function() {
+        _.strictEqual(this.foo, 1);
+        _.strictEqual(this.bar, 2);
+        _.strictEqual(this.baz, 3);
+        _.strictEqual(this.quux, 4);
+        _.strictEqual(this.quuz, undefined);
+        this.quuz = 5;
+      }
+    });
+    var s = Crafty.s("MemberOrderTest");
+    s.destroy();
+
+    _.strictEqual(s.foo, 1);
+    _.strictEqual(s.bar, 2);
+    _.strictEqual(s.baz, 3);
+    _.strictEqual(s.quux, 4);
+    _.strictEqual(s.quuz, 5);
+  });
 })();