diff --git a/packages/ember-htmlbars/tests/integration/component_lifecycle_test.js b/packages/ember-htmlbars/tests/integration/component_lifecycle_test.js index 15b44bf7655..322a32e7064 100644 --- a/packages/ember-htmlbars/tests/integration/component_lifecycle_test.js +++ b/packages/ember-htmlbars/tests/integration/component_lifecycle_test.js @@ -25,7 +25,7 @@ if (isEnabled('ember-htmlbars-component-generation')) { } styles.forEach(style => { - function invoke(name, hash) { + function invoke(name, hash = {}) { if (style.name === 'curly') { let attrs = Object.keys(hash).map(k => `${k}=${val(hash[k])}`).join(' '); return `{{${name} ${attrs}}}`; @@ -87,6 +87,7 @@ styles.forEach(style => { this.label = label; components[label] = this; this._super.apply(this, arguments); + pushHook(label, 'init'); }, didInitAttrs(options) { @@ -147,9 +148,9 @@ styles.forEach(style => { let bottomAttrs = { website: 'tomdale.net' }; deepEqual(hooks, [ - hook('top', 'didInitAttrs', { attrs: topAttrs }), hook('top', 'didReceiveAttrs', { newAttrs: topAttrs }), hook('top', 'willRender'), - hook('middle', 'didInitAttrs', { attrs: middleAttrs }), hook('middle', 'didReceiveAttrs', { newAttrs: middleAttrs }), hook('middle', 'willRender'), - hook('bottom', 'didInitAttrs', { attrs: bottomAttrs }), hook('bottom', 'didReceiveAttrs', { newAttrs: bottomAttrs }), hook('bottom', 'willRender'), + hook('top', 'init'), hook('top', 'didInitAttrs', { attrs: topAttrs }), hook('top', 'didReceiveAttrs', { newAttrs: topAttrs }), hook('top', 'willRender'), + hook('middle', 'init'), hook('middle', 'didInitAttrs', { attrs: middleAttrs }), hook('middle', 'didReceiveAttrs', { newAttrs: middleAttrs }), hook('middle', 'willRender'), + hook('bottom', 'init'), hook('bottom', 'didInitAttrs', { attrs: bottomAttrs }), hook('bottom', 'didReceiveAttrs', { newAttrs: bottomAttrs }), hook('bottom', 'willRender'), hook('bottom', 'didInsertElement'), hook('bottom', 'didRender'), hook('middle', 'didInsertElement'), hook('middle', 'didRender'), hook('top', 'didInsertElement'), hook('top', 'didRender') @@ -238,6 +239,7 @@ styles.forEach(style => { this.label = label; components[label] = this; this._super.apply(this, arguments); + pushHook(label, 'init'); }, didInitAttrs(options) { @@ -298,9 +300,9 @@ styles.forEach(style => { let bottomAttrs = { twitterMiddle: '@tomdale' }; deepEqual(hooks, [ - hook('top', 'didInitAttrs', { attrs: topAttrs }), hook('top', 'didReceiveAttrs', { newAttrs: topAttrs }), hook('top', 'willRender'), - hook('middle', 'didInitAttrs', { attrs: middleAttrs }), hook('middle', 'didReceiveAttrs', { newAttrs: middleAttrs }), hook('middle', 'willRender'), - hook('bottom', 'didInitAttrs', { attrs: bottomAttrs }), hook('bottom', 'didReceiveAttrs', { newAttrs: bottomAttrs }), hook('bottom', 'willRender'), + hook('top', 'init'), hook('top', 'didInitAttrs', { attrs: topAttrs }), hook('top', 'didReceiveAttrs', { newAttrs: topAttrs }), hook('top', 'willRender'), + hook('middle', 'init'), hook('middle', 'didInitAttrs', { attrs: middleAttrs }), hook('middle', 'didReceiveAttrs', { newAttrs: middleAttrs }), hook('middle', 'willRender'), + hook('bottom', 'init'), hook('bottom', 'didInitAttrs', { attrs: bottomAttrs }), hook('bottom', 'didReceiveAttrs', { newAttrs: bottomAttrs }), hook('bottom', 'willRender'), hook('bottom', 'didInsertElement'), hook('bottom', 'didRender'), hook('middle', 'didInsertElement'), hook('middle', 'didRender'), hook('top', 'didInsertElement'), hook('top', 'didRender') @@ -384,6 +386,30 @@ styles.forEach(style => { component.destroy(); }); }); + + QUnit.test('properties set during `init` are availabe in `didReceiveAttrs`', function(assert) { + assert.expect(1); + + registry.register('component:the-thing', style.class.extend({ + init() { + this._super(...arguments); + this.propertySetInInit = 'init fired!'; + }, + + didReceiveAttrs() { + this._super(...arguments); + + assert.equal(this.propertySetInInit, 'init fired!', 'init has already finished before didReceiveAttrs'); + } + })); + + view = EmberView.extend({ + template: compile(invoke('the-thing')), + container: container + }).create(); + + runAppend(view); + }); }); // TODO: Write a test that involves deep mutability: the component plucks something diff --git a/packages/ember-runtime/lib/system/core_object.js b/packages/ember-runtime/lib/system/core_object.js index 37ef0084272..e6016a4c809 100644 --- a/packages/ember-runtime/lib/system/core_object.js +++ b/packages/ember-runtime/lib/system/core_object.js @@ -46,7 +46,9 @@ import { K } from 'ember-metal/core'; import { validatePropertyInjections } from 'ember-runtime/inject'; +import { symbol } from 'ember-metal/utils'; +export let POST_INIT = symbol('POST_INIT'); var schedule = run.schedule; var applyMixin = Mixin._apply; var finishPartial = Mixin.finishPartial; @@ -191,6 +193,8 @@ function makeCtor() { this.init.apply(this, args); } + this[POST_INIT](); + m.proto = proto; finishChains(this); sendEvent(this, 'init'); @@ -265,6 +269,9 @@ CoreObject.PrototypeMixin = Mixin.create({ @public */ init() {}, + + [POST_INIT]: function() { }, + __defineNonEnumerable(property) { Object.defineProperty(this, property.name, property.descriptor); //this[property.name] = property.descriptor.value; diff --git a/packages/ember-views/lib/mixins/view_support.js b/packages/ember-views/lib/mixins/view_support.js index ae8f869bc2d..97cc171bf50 100644 --- a/packages/ember-views/lib/mixins/view_support.js +++ b/packages/ember-views/lib/mixins/view_support.js @@ -6,6 +6,7 @@ import { addObserver, removeObserver } from 'ember-metal/observer'; import { guidFor } from 'ember-metal/utils'; import { computed } from 'ember-metal/computed'; import { Mixin } from 'ember-metal/mixin'; +import { POST_INIT } from 'ember-runtime/system/core_object'; import jQuery from 'ember-views/system/jquery'; @@ -609,7 +610,6 @@ export default Mixin.create({ this.scheduledRevalidation = false; this._super(...arguments); - this.renderer.componentInitAttrs(this, this.attrs || {}); assert( 'Using a custom `.render` function is no longer supported.', @@ -617,6 +617,20 @@ export default Mixin.create({ ); }, + /* + This is a special hook implemented in CoreObject, that allows Views/Components + to have a way to ensure that `init` fires before `didInitAttrs` / `didReceiveAttrs` + (so that `this._super` in init does not trigger `didReceiveAttrs` before the classes + own `init` is finished). + + @method __postInitInitialization + @private + */ + [POST_INIT]: function() { + this._super(...arguments); + this.renderer.componentInitAttrs(this, this.attrs || {}); + }, + __defineNonEnumerable(property) { this[property.name] = property.descriptor.value; },