diff --git a/packages/@ember/-internals/meta/index.ts b/packages/@ember/-internals/meta/index.ts index 11ebd60739c..4558449e401 100644 --- a/packages/@ember/-internals/meta/index.ts +++ b/packages/@ember/-internals/meta/index.ts @@ -1,10 +1 @@ -export { - counters, - deleteMeta, - Meta, - meta, - MetaCounters, - peekMeta, - setMeta, - UNDEFINED, -} from './lib/meta'; +export { counters, Meta, meta, MetaCounters, peekMeta, setMeta, UNDEFINED } from './lib/meta'; diff --git a/packages/@ember/-internals/meta/lib/meta.ts b/packages/@ember/-internals/meta/lib/meta.ts index c632ef159ce..91e1a2b335a 100644 --- a/packages/@ember/-internals/meta/lib/meta.ts +++ b/packages/@ember/-internals/meta/lib/meta.ts @@ -156,6 +156,10 @@ export class Meta { } destroy() { + if (DEBUG) { + counters!.deleteCalls++; + } + if (this.isMetaDestroyed()) { return; } @@ -647,34 +651,6 @@ export function peekMeta(obj: object): Meta | null { return null; } -/** - Tears down the meta on an object so that it can be garbage collected. - Multiple calls will have no effect. - - @method deleteMeta - @for Ember - @param {Object} obj the object to destroy - @return {void} - @private -*/ -export function deleteMeta(obj: object) { - assert('Cannot call `deleteMeta` on null', obj !== null); - assert('Cannot call `deleteMeta` on undefined', obj !== undefined); - assert( - `Cannot call \`deleteMeta\` on ${typeof obj}`, - typeof obj === 'object' || typeof obj === 'function' - ); - - if (DEBUG) { - counters!.deleteCalls++; - } - - let meta = peekMeta(obj); - if (meta !== null) { - meta.destroy(); - } -} - /** Retrieves the meta hash for an object. If `writable` is true ensures the hash is writable for this object as well. diff --git a/packages/@ember/-internals/metal/index.ts b/packages/@ember/-internals/metal/index.ts index 15f6b1410ea..d05f08556e6 100644 --- a/packages/@ember/-internals/metal/index.ts +++ b/packages/@ember/-internals/metal/index.ts @@ -50,7 +50,15 @@ export { default as getProperties } from './lib/get_properties'; export { default as setProperties } from './lib/set_properties'; export { default as expandProperties } from './lib/expand_properties'; -export { addObserver, activateObserver, removeObserver, flushAsyncObservers } from './lib/observer'; +export { destroy } from './lib/destroy'; +export { + ASYNC_OBSERVERS, + SYNC_OBSERVERS, + addObserver, + activateObserver, + removeObserver, + flushAsyncObservers, +} from './lib/observer'; export { Mixin, aliasMethod, mixin, observer, applyMixin } from './lib/mixin'; export { default as inject, DEBUG_INJECTION_FUNCTIONS } from './lib/injected_property'; export { tagForProperty, tagForObject, markObjectAsDirty, CUSTOM_TAG_FOR } from './lib/tags'; diff --git a/packages/@ember/-internals/metal/lib/destroy.ts b/packages/@ember/-internals/metal/lib/destroy.ts new file mode 100644 index 00000000000..71e7011aaf0 --- /dev/null +++ b/packages/@ember/-internals/metal/lib/destroy.ts @@ -0,0 +1,37 @@ +import { Meta, peekMeta } from '@ember/-internals/meta/lib/meta'; +import { assert } from '@ember/debug'; +import { schedule } from '@ember/runloop'; +import { destroyObservers } from './observer'; + +/** + Enqueues finalization on an object so that it can be garbage collected. + Multiple calls will have no effect. + + @method destroy + @for Ember + @param {Object} obj the object to destroy + @return {boolean} true if the object went from not destroying to destroying. + @private +*/ +export function destroy(obj: object): boolean { + assert('Cannot call `destroy` on null', obj !== null); + assert('Cannot call `destroy` on undefined', obj !== undefined); + assert( + `Cannot call \`destroy\` on ${typeof obj}`, + typeof obj === 'object' || typeof obj === 'function' + ); + + const m = peekMeta(obj); + if (m === null || m.isSourceDestroying()) { + return false; + } + m.setSourceDestroying(); + destroyObservers(obj); + schedule('destroy', m, finalize); + return true; +} + +function finalize(this: Meta) { + this.setSourceDestroyed(); + this.destroy(); +} diff --git a/packages/@ember/-internals/metal/lib/events.ts b/packages/@ember/-internals/metal/lib/events.ts index 4fbfb161938..cfbeb8c5cdf 100644 --- a/packages/@ember/-internals/metal/lib/events.ts +++ b/packages/@ember/-internals/metal/lib/events.ts @@ -124,8 +124,7 @@ export function sendEvent( ) { if (actions === undefined) { let meta = _meta === undefined ? peekMeta(obj) : _meta; - actions = - typeof meta === 'object' && meta !== null ? meta.matchingListeners(eventName) : undefined; + actions = meta !== null ? meta.matchingListeners(eventName) : undefined; } if (actions === undefined || actions.length === 0) { diff --git a/packages/@ember/-internals/metal/lib/observer.ts b/packages/@ember/-internals/metal/lib/observer.ts index ba9f4ec8e2f..183e596fcaa 100644 --- a/packages/@ember/-internals/metal/lib/observer.ts +++ b/packages/@ember/-internals/metal/lib/observer.ts @@ -15,8 +15,8 @@ interface ActiveObserver { } const SYNC_DEFAULT = !ENV._DEFAULT_ASYNC_OBSERVERS; -const SYNC_OBSERVERS: Map> = new Map(); -const ASYNC_OBSERVERS: Map> = new Map(); +export const SYNC_OBSERVERS: Map> = new Map(); +export const ASYNC_OBSERVERS: Map> = new Map(); /** @module @ember/object @@ -153,15 +153,16 @@ export function revalidateObservers(target: object) { let lastKnownRevision = 0; export function flushAsyncObservers(shouldSchedule = true) { - if (lastKnownRevision === value(CURRENT_TAG)) { + let currentRevision = value(CURRENT_TAG); + if (lastKnownRevision === currentRevision) { return; } - - lastKnownRevision = value(CURRENT_TAG); + lastKnownRevision = currentRevision; ASYNC_OBSERVERS.forEach((activeObservers, target) => { let meta = peekMeta(target); + // if observer target is destroyed remove observers if (meta && (meta.isSourceDestroying() || meta.isMetaDestroyed())) { ASYNC_OBSERVERS.delete(target); return; @@ -171,7 +172,7 @@ export function flushAsyncObservers(shouldSchedule = true) { if (!validate(observer.tag, observer.lastRevision)) { let sendObserver = () => { try { - sendEvent(target, eventName, [target, observer.path]); + sendEvent(target, eventName, [target, observer.path], undefined, meta); } finally { observer.tag = combine(getChainTagsForKey(target, observer.path)); observer.lastRevision = value(observer.tag); @@ -205,7 +206,7 @@ export function flushSyncObservers() { if (!observer.suspended && !validate(observer.tag, observer.lastRevision)) { try { observer.suspended = true; - sendEvent(target, eventName, [target, observer.path]); + sendEvent(target, eventName, [target, observer.path], undefined, meta); } finally { observer.tag = combine(getChainTagsForKey(target, observer.path)); observer.lastRevision = value(observer.tag); @@ -229,3 +230,8 @@ export function setObserverSuspended(target: object, property: string, suspended observer.suspended = suspended; } } + +export function destroyObservers(target: object) { + if (SYNC_OBSERVERS.size > 0) SYNC_OBSERVERS.delete(target); + if (ASYNC_OBSERVERS.size > 0) ASYNC_OBSERVERS.delete(target); +} diff --git a/packages/@ember/-internals/metal/tests/accessors/get_test.js b/packages/@ember/-internals/metal/tests/accessors/get_test.js index 27d8a35e661..54957b1a5e7 100644 --- a/packages/@ember/-internals/metal/tests/accessors/get_test.js +++ b/packages/@ember/-internals/metal/tests/accessors/get_test.js @@ -1,5 +1,6 @@ import { ENV } from '@ember/-internals/environment'; import { Object as EmberObject } from '@ember/-internals/runtime'; +import { destroy } from '@ember/-internals/metal'; import { get, set, getWithDefault, Mixin, observer, computed } from '../..'; import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; import { run } from '@ember/runloop'; @@ -196,6 +197,8 @@ moduleFor( 'foo', 'should return the set value, not false' ); + + run(() => destroy(baseObject)); } } ); @@ -332,6 +335,8 @@ moduleFor( 'foo', 'should return the set value, not false' ); + + run(() => destroy(baseObject)); } ['@test should respect prototypical inheritance when subclasses override CPs'](assert) { diff --git a/packages/@ember/-internals/metal/tests/alias_test.js b/packages/@ember/-internals/metal/tests/alias_test.js index 940f035d0f8..3484dc77563 100644 --- a/packages/@ember/-internals/metal/tests/alias_test.js +++ b/packages/@ember/-internals/metal/tests/alias_test.js @@ -2,6 +2,7 @@ import { alias, computed, defineProperty, + destroy, get, set, addObserver, @@ -27,7 +28,11 @@ moduleFor( } afterEach() { - obj = null; + if (obj !== undefined) { + destroy(obj); + obj = undefined; + return runLoopSettled(); + } } ['@test should proxy get to alt key'](assert) { diff --git a/packages/@ember/-internals/metal/tests/computed_test.js b/packages/@ember/-internals/metal/tests/computed_test.js index 7fb40ae896e..80cfc57fc80 100644 --- a/packages/@ember/-internals/metal/tests/computed_test.js +++ b/packages/@ember/-internals/metal/tests/computed_test.js @@ -1,8 +1,9 @@ import { Object as EmberObject } from '@ember/-internals/runtime'; import { computed, - getCachedValueFor, defineProperty, + destroy, + getCachedValueFor, isClassicDecorator, isComputed, get, @@ -12,11 +13,20 @@ import { import { meta as metaFor } from '@ember/-internals/meta'; import { moduleFor, AbstractTestCase, runLoopSettled } from 'internal-test-helpers'; -let obj, count; +let obj, objA, objB, count, func; + +class ComputedTestCase extends AbstractTestCase { + afterEach() { + let destroyables = [obj, objA, objB].filter(Boolean); + obj = objA = objB = count = func = undefined; + destroyables.forEach(destroy); + return runLoopSettled(); + } +} moduleFor( 'computed', - class extends AbstractTestCase { + class extends ComputedTestCase { ['@test isComputed is true for computed property on a factory'](assert) { let Obj = EmberObject.extend({ foo: computed(function() {}), @@ -28,7 +38,7 @@ moduleFor( } ['@test isComputed is true for computed property on an instance'](assert) { - let obj = EmberObject.extend({ + obj = EmberObject.extend({ foo: computed(function() {}), }).create(); @@ -41,14 +51,14 @@ moduleFor( ['@test computed properties assert the presence of a getter or setter function']() { expectAssertion(function() { - let obj = {}; + obj = {}; defineProperty(obj, 'someProp', computed('nogetternorsetter', {})); }, 'Computed properties must receive a getter or a setter, you passed none.'); } ['@test computed properties check for the presence of a function or configuration object']() { expectAssertion(function() { - let obj = {}; + obj = {}; defineProperty(obj, 'someProp', computed('nolastargument')); }, 'Attempted to use @computed on someProp, but it did not have a getter or a setter. You must either pass a get a function or getter/setter to @computed directly (e.g. `@computed({ get() { ... } })`) or apply @computed directly to a getter/setter'); } @@ -56,7 +66,7 @@ moduleFor( // non valid properties are stripped away in the process of creating a computed property descriptor ['@test computed properties defined with an object only allow `get` and `set` keys']() { expectAssertion(function() { - let obj = EmberObject.extend({ + obj = EmberObject.extend({ someProp: computed({ get() {}, set() {}, @@ -69,7 +79,7 @@ moduleFor( } ['@test computed property can be accessed without `get`'](assert) { - let obj = {}; + obj = {}; let count = 0; defineProperty( obj, @@ -85,7 +95,7 @@ moduleFor( } ['@test defining computed property should invoke property on get'](assert) { - let obj = {}; + obj = {}; let count = 0; defineProperty( obj, @@ -120,7 +130,7 @@ moduleFor( } ['@test can override volatile computed property'](assert) { - let obj = {}; + obj = {}; expectDeprecation(() => { defineProperty(obj, 'foo', computed(function() {}).volatile()); @@ -134,7 +144,7 @@ moduleFor( } ['@test defining computed property should invoke property on set'](assert) { - let obj = {}; + obj = {}; let count = 0; defineProperty( obj, @@ -172,22 +182,22 @@ moduleFor( ['@test defining a computed property with a dependent key more than one level deep beyond @each is not supported']() { expectNoWarning(() => { - let obj = {}; + obj = {}; defineProperty(obj, 'someProp', computed('todos', () => {})); }); expectNoWarning(() => { - let obj = {}; + obj = {}; defineProperty(obj, 'someProp', computed('todos.@each.owner', () => {})); }); expectWarning(() => { - let obj = {}; + obj = {}; defineProperty(obj, 'someProp', computed('todos.@each.owner.name', () => {})); }, /You used the key "todos\.@each\.owner\.name" which is invalid\. /); expectWarning(() => { - let obj = {}; + obj = {}; defineProperty(obj, 'someProp', computed('todos.@each.owner.@each.name', () => {})); }, /You used the key "todos\.@each\.owner\.@each\.name" which is invalid\. /); @@ -207,7 +217,7 @@ moduleFor( ); expectDeprecation(() => { - let obj = { + obj = { todos: [], }; defineProperty(obj, 'someProp', computed('todos.@each.owner.name', () => {})); @@ -218,10 +228,9 @@ moduleFor( } ); -let objA, objB; moduleFor( 'computed should inherit through prototype', - class extends AbstractTestCase { + class extends ComputedTestCase { beforeEach() { objA = { __foo: 'FOO' }; defineProperty( @@ -242,10 +251,6 @@ moduleFor( objB.__foo = 'FOO'; // make a copy; } - afterEach() { - objA = objB = null; - } - ['@test using get() and set()'](assert) { assert.equal(get(objA, 'foo'), 'FOO', 'should get FOO from A'); assert.equal(get(objB, 'foo'), 'FOO', 'should get FOO from B'); @@ -267,7 +272,7 @@ moduleFor( moduleFor( 'redefining computed property to normal', - class extends AbstractTestCase { + class extends ComputedTestCase { beforeEach() { objA = { __foo: 'FOO' }; defineProperty( @@ -288,10 +293,6 @@ moduleFor( defineProperty(objB, 'foo'); // make this just a normal property. } - afterEach() { - objA = objB = null; - } - ['@test using get() and set()'](assert) { assert.equal(get(objA, 'foo'), 'FOO', 'should get FOO from A'); assert.equal(get(objB, 'foo'), undefined, 'should get undefined from B'); @@ -313,7 +314,7 @@ moduleFor( moduleFor( 'redefining computed property to another property', - class extends AbstractTestCase { + class extends ComputedTestCase { beforeEach() { objA = { __foo: 'FOO' }; defineProperty( @@ -347,10 +348,6 @@ moduleFor( ); } - afterEach() { - objA = objB = null; - } - ['@test using get() and set()'](assert) { assert.equal(get(objA, 'foo'), 'FOO', 'should get FOO from A'); assert.equal(get(objB, 'foo'), 'FOO', 'should get FOO from B'); @@ -397,7 +394,7 @@ moduleFor( moduleFor( 'computed - cacheable', - class extends AbstractTestCase { + class extends ComputedTestCase { beforeEach() { obj = {}; count = 0; @@ -408,9 +405,6 @@ moduleFor( defineProperty(obj, 'foo', computed({ get: func, set: func })); } - afterEach() { - obj = count = null; - } ['@test cacheable should cache'](assert) { assert.equal(get(obj, 'foo'), 'bar 1', 'first get'); assert.equal(get(obj, 'foo'), 'bar 1', 'second get'); @@ -467,7 +461,7 @@ moduleFor( ['@test setting a cached computed property passes the old value as the third argument']( assert ) { - let obj = { + obj = { foo: 0, }; @@ -503,7 +497,7 @@ moduleFor( moduleFor( 'computed - dependentkey', - class extends AbstractTestCase { + class extends ComputedTestCase { beforeEach() { obj = { bar: 'baz' }; count = 0; @@ -522,10 +516,6 @@ moduleFor( ); } - afterEach() { - obj = count = null; - } - ['@test circular keys should not blow up'](assert) { let func = function() { count++; @@ -649,11 +639,9 @@ moduleFor( // CHAINED DEPENDENT KEYS // -let func; - moduleFor( 'computed - dependentkey with chained properties', - class extends AbstractTestCase { + class extends ComputedTestCase { beforeEach() { obj = { foo: { @@ -672,10 +660,6 @@ moduleFor( }; } - afterEach() { - obj = count = func = null; - } - ['@test depending on simple chain'](assert) { // assign computed property defineProperty(obj, 'prop', computed('foo.bar.baz.biff', func)); @@ -821,9 +805,9 @@ moduleFor( moduleFor( 'computed edge cases', - class extends AbstractTestCase { + class extends ComputedTestCase { ['@test adding a computed property should show up in key iteration'](assert) { - let obj = {}; + obj = {}; defineProperty(obj, 'foo', computed(function() {})); let found = []; @@ -840,7 +824,7 @@ moduleFor( ["@test when setting a value after it had been retrieved empty don't pass function UNDEFINED as oldValue"]( assert ) { - let obj = {}; + obj = {}; let oldValueIsNoFunction = true; defineProperty( @@ -867,9 +851,9 @@ moduleFor( moduleFor( 'computed - setter', - class extends AbstractTestCase { + class extends ComputedTestCase { async ['@test setting a watched computed property'](assert) { - let obj = { + obj = { firstName: 'Yehuda', lastName: 'Katz', }; @@ -921,7 +905,7 @@ moduleFor( } async ['@test setting a cached computed property that modifies the value you give it'](assert) { - let obj = { + obj = { foo: 0, }; @@ -968,9 +952,9 @@ moduleFor( moduleFor( 'computed - default setter', - class extends AbstractTestCase { + class extends ComputedTestCase { async ["@test when setting a value on a computed property that doesn't handle sets"](assert) { - let obj = {}; + obj = {}; let observerFired = false; defineProperty( @@ -999,7 +983,7 @@ moduleFor( moduleFor( 'computed - readOnly', - class extends AbstractTestCase { + class extends ComputedTestCase { ['@test is chainable'](assert) { let cp = computed(function() {}); let readOnlyCp = cp.readOnly(); @@ -1009,7 +993,7 @@ moduleFor( ['@test throws assertion if called over a CP with a setter defined with the new syntax']() { expectAssertion(() => { - let obj = {}; + obj = {}; defineProperty( obj, 'someProp', @@ -1022,7 +1006,7 @@ moduleFor( } ['@test protects against setting'](assert) { - let obj = {}; + obj = {}; defineProperty( obj, @@ -1056,7 +1040,7 @@ class LazyObject { } static create() { - let obj = new LazyObject(); + obj = new LazyObject(); // ensure a tag exists for the value computed get(obj, 'value'); @@ -1067,7 +1051,7 @@ class LazyObject { moduleFor( 'computed - lazy dependencies', - class extends AbstractTestCase { + class extends ComputedTestCase { '@test computed properties with lazy dependencies work as expected'(assert) { let calledCount = 0; let lazyObject = LazyObject.create(); @@ -1084,7 +1068,7 @@ moduleFor( } } - let obj = new ObjectWithLazyDep(); + obj = new ObjectWithLazyDep(); // Get someProp and setup the lazy dependency assert.equal(obj.someProp, 1, 'called the first time'); @@ -1127,7 +1111,7 @@ moduleFor( } } - let obj = new ObjectWithLazyDep(); + obj = new ObjectWithLazyDep(); assert.equal(obj.someProp, 1, 'called the first time'); assert.equal(obj.someProp, 1, 'returned cached value the second time'); @@ -1161,7 +1145,7 @@ moduleFor( } } - let obj = new ObjectWithLazyDep(); + obj = new ObjectWithLazyDep(); set(lazyObject, 'value', 456); @@ -1181,7 +1165,7 @@ moduleFor( moduleFor( 'computed - observer interop', - class extends AbstractTestCase { + class extends ComputedTestCase { async '@test observers that do not consume computed properties still work'(assert) { assert.expect(2); @@ -1194,32 +1178,32 @@ moduleFor( } } - let foo = new Foo(); + obj = new Foo(); addObserver( - foo, + obj, 'otherProp', - foo, + obj, () => assert.ok(true, 'otherProp observer called when it was changed'), false ); addObserver( - foo, + obj, 'someProp', - foo, + obj, () => assert.ok(false, 'someProp observer called when it was not changed'), false ); - set(foo, 'otherProp', 456); + set(obj, 'otherProp', 456); await runLoopSettled(); - assert.equal(get(foo, 'someProp'), 456, ''); + assert.equal(get(obj, 'someProp'), 456, ''); - addObserver(foo, 'anotherProp', foo, () => {}, false); - set(foo, 'anotherProp', 123); + addObserver(obj, 'anotherProp', obj, () => {}, false); + set(obj, 'anotherProp', 123); await runLoopSettled(); } diff --git a/packages/@ember/-internals/metal/tests/mixin/observer_test.js b/packages/@ember/-internals/metal/tests/mixin/observer_test.js index ae12a083a3b..36c79d16c98 100644 --- a/packages/@ember/-internals/metal/tests/mixin/observer_test.js +++ b/packages/@ember/-internals/metal/tests/mixin/observer_test.js @@ -1,9 +1,19 @@ -import { set, get, observer, mixin, Mixin } from '../..'; +import { set, get, destroy, observer, mixin, Mixin } from '../..'; import { moduleFor, AbstractTestCase, runLoopSettled } from 'internal-test-helpers'; +let obj; + moduleFor( 'Mixin observer', class extends AbstractTestCase { + afterEach() { + if (obj !== undefined) { + destroy(obj); + obj = undefined; + return runLoopSettled(); + } + } + async ['@test global observer helper'](assert) { let MyMixin = Mixin.create({ count: 0, @@ -13,7 +23,7 @@ moduleFor( }), }); - let obj = mixin({}, MyMixin); + obj = mixin({}, MyMixin); assert.equal(get(obj, 'count'), 0, 'should not invoke observer immediately'); set(obj, 'bar', 'BAZ'); @@ -31,7 +41,7 @@ moduleFor( }), }); - let obj = mixin({}, MyMixin); + obj = mixin({}, MyMixin); assert.equal(get(obj, 'count'), 0, 'should not invoke observer immediately'); set(obj, 'bar', 'BAZ'); @@ -41,6 +51,9 @@ moduleFor( await runLoopSettled(); assert.equal(get(obj, 'count'), 2, 'should invoke observer after change'); + + destroy(obj); + await runLoopSettled(); } async ['@test replacing observer should remove old observer'](assert) { @@ -58,7 +71,7 @@ moduleFor( }), }); - let obj = mixin({}, MyMixin, Mixin2); + obj = mixin({}, MyMixin, Mixin2); assert.equal(get(obj, 'count'), 0, 'should not invoke observer immediately'); set(obj, 'bar', 'BAZ'); @@ -83,7 +96,7 @@ moduleFor( }), }); - let obj = mixin({}, MyMixin); + obj = mixin({}, MyMixin); assert.equal(get(obj, 'count'), 0, 'should not invoke observer immediately'); set(obj2, 'baz', 'BAZ'); @@ -103,7 +116,7 @@ moduleFor( bar: obj2, }); - let obj = mixin({}, MyMixin); + obj = mixin({}, MyMixin); assert.equal(get(obj, 'count'), 0, 'should not invoke observer immediately'); set(obj2, 'baz', 'BAZ'); @@ -124,7 +137,7 @@ moduleFor( let MyMixin2 = Mixin.create({ bar: obj2 }); - let obj = mixin({}, MyMixin); + obj = mixin({}, MyMixin); assert.equal(get(obj, 'count'), 0, 'should not invoke observer immediately'); MyMixin2.apply(obj); @@ -146,7 +159,7 @@ moduleFor( }), }); - let obj = mixin({ bar: obj2 }, MyMixin); + obj = mixin({ bar: obj2 }, MyMixin); assert.equal(get(obj, 'count'), 0, 'should not invoke observer immediately'); set(obj2, 'baz', 'BAZ'); @@ -166,7 +179,7 @@ moduleFor( }), }); - let obj = mixin({}, MyMixin2, MyMixin); + obj = mixin({}, MyMixin2, MyMixin); assert.equal(get(obj, 'count'), 0, 'should not invoke observer immediately'); set(obj2, 'baz', 'BAZ'); @@ -186,7 +199,7 @@ moduleFor( }), }); - let obj = mixin({}, MyMixin, MyMixin2); + obj = mixin({}, MyMixin, MyMixin2); assert.equal(get(obj, 'count'), 0, 'should not invoke observer immediately'); set(obj2, 'baz', 'BAZ'); @@ -208,7 +221,7 @@ moduleFor( }), }); - let obj = mixin({ bar: obj2 }, MyMixin, MyMixin2); + obj = mixin({ bar: obj2 }, MyMixin, MyMixin2); assert.equal(get(obj, 'count'), 0, 'should not invoke observer immediately'); set(obj2, 'baz', 'BAZ'); diff --git a/packages/@ember/-internals/metal/tests/observer_test.js b/packages/@ember/-internals/metal/tests/observer_test.js index 8f21ca2988b..aa6544802a9 100644 --- a/packages/@ember/-internals/metal/tests/observer_test.js +++ b/packages/@ember/-internals/metal/tests/observer_test.js @@ -1,5 +1,6 @@ import { ENV } from '@ember/-internals/environment'; import { + destroy, addObserver, removeObserver, notifyPropertyChange, @@ -19,6 +20,8 @@ import { FUNCTION_PROTOTYPE_EXTENSIONS } from '@ember/deprecated-features'; function K() {} +let obj; + // .......................................................... // ADD OBSERVER // @@ -26,6 +29,14 @@ function K() {} moduleFor( 'addObserver', class extends AbstractTestCase { + afterEach() { + if (obj !== undefined) { + destroy(obj); + obj = undefined; + return runLoopSettled(); + } + } + ['@test observer should assert to invalid input']() { expectAssertion(() => { observer(() => {}); @@ -61,7 +72,7 @@ moduleFor( } async ['@test observer should fire when property is modified'](assert) { - let obj = {}; + obj = {}; let count = 0; addObserver(obj, 'foo', function() { @@ -76,7 +87,7 @@ moduleFor( } async ['@test observer should fire when dependent property is modified'](assert) { - let obj = { bar: 'bar' }; + obj = { bar: 'bar' }; defineProperty( obj, 'foo', @@ -101,7 +112,7 @@ moduleFor( // https://github.com/emberjs/ember.js/issues/18246 async ['@test observer should fire when computed property is modified'](assert) { - let obj = { bar: 'bar' }; + obj = { bar: 'bar' }; defineProperty( obj, 'foo', @@ -134,7 +145,7 @@ moduleFor( assert ) { let observerCount = 0; - let obj = {}; + obj = {}; defineProperty( obj, @@ -169,7 +180,7 @@ moduleFor( assert ) { if (!FUNCTION_PROTOTYPE_EXTENSIONS && ENV.EXTEND_PROTOTYPES.Function) { - let obj = {}; + obj = {}; let count = 0; expectDeprecation(() => { @@ -203,7 +214,7 @@ moduleFor( assert ) { if (!FUNCTION_PROTOTYPE_EXTENSIONS && ENV.EXTEND_PROTOTYPES.Function) { - let obj = { baz: 'Initial' }; + obj = { baz: 'Initial' }; let count = 0; defineProperty( @@ -253,7 +264,7 @@ moduleFor( async ['@test observers watching multiple properties via brace expansion should fire when the properties change']( assert ) { - let obj = {}; + obj = {}; let count = 0; mixin(obj, { @@ -281,7 +292,7 @@ moduleFor( async ['@test observers watching multiple properties via brace expansion should fire when dependent properties change']( assert ) { - let obj = { baz: 'Initial' }; + obj = { baz: 'Initial' }; let count = 0; defineProperty( @@ -362,10 +373,15 @@ moduleFor( assert.equal(count2, 1, 'observer2 fired'); assert.equal(count3, 1, 'observer3 fired'); assert.equal(count4, 0, 'observer4 did not fire'); + + destroy(obj1); + destroy(obj2); + destroy(obj3); + destroy(obj4); } async ['@test deferring property change notifications'](assert) { - let obj = { foo: 'foo' }; + obj = { foo: 'foo' }; let fooCount = 0; addObserver(obj, 'foo', function() { @@ -385,8 +401,7 @@ moduleFor( } async ['@test addObserver should respect targets with methods'](assert) { - let observed = { foo: 'foo' }; - + let observed = (obj = { foo: 'foo' }); let target1 = { count: 0, @@ -424,7 +439,7 @@ moduleFor( } async ['@test addObserver should allow multiple objects to observe a property'](assert) { - let observed = { foo: 'foo' }; + let observed = (obj = { foo: 'foo' }); let target1 = { count: 0, @@ -461,8 +476,16 @@ moduleFor( moduleFor( 'removeObserver', class extends AbstractTestCase { + afterEach() { + if (obj !== undefined) { + destroy(obj); + obj = undefined; + return runLoopSettled(); + } + } + async ['@test removing observer should stop firing'](assert) { - let obj = {}; + obj = {}; let count = 0; function F() { count++; @@ -495,7 +518,7 @@ moduleFor( }), }); - let obj = {}; + obj = {}; MyMixin.apply(obj); set(obj, 'bar', 'HI!'); @@ -557,11 +580,21 @@ moduleFor( // CHAINED OBSERVERS // -let obj, count; +let count; moduleFor( 'addObserver - dependentkey with chained properties', class extends AbstractTestCase { + afterEach() { + if (obj !== undefined) { + destroy(obj); + obj = undefined; + } + obj = undefined; + count = 0; + return runLoopSettled(); + } + beforeEach() { obj = { foo: { @@ -585,10 +618,6 @@ moduleFor( count = 0; } - afterEach() { - obj = count = null; - } - async ['@test depending on a chain with a computed property'](assert) { defineProperty( obj, @@ -727,8 +756,18 @@ moduleFor( moduleFor( 'props/observer_test - setting identical values', class extends AbstractTestCase { + afterEach() { + if (obj !== undefined) { + destroy(obj); + obj = undefined; + } + obj = undefined; + count = 0; + return runLoopSettled(); + } + async ['@test setting simple prop should not trigger'](assert) { - let obj = { foo: 'bar' }; + obj = { foo: 'bar' }; let count = 0; addObserver(obj, 'foo', function() { @@ -757,7 +796,7 @@ moduleFor( async ['@test setting a cached computed property whose value has changed should trigger']( assert ) { - let obj = {}; + obj = {}; defineProperty( obj, @@ -801,69 +840,77 @@ moduleFor( moduleFor( 'Keys behavior with observers', class extends AbstractTestCase { + afterEach() { + if (obj !== undefined) { + destroy(obj); + obj = undefined; + return runLoopSettled(); + } + } + ['@test should not leak properties on the prototype'](assert) { function Beer() {} Beer.prototype.type = 'ipa'; - let beer = new Beer(); + obj = new Beer(); - addObserver(beer, 'type', K); - assert.deepEqual(Object.keys(beer), []); - removeObserver(beer, 'type', K); + addObserver(obj, 'type', K); + assert.deepEqual(Object.keys(obj), []); + removeObserver(obj, 'type', K); } ['@test observing a non existent property'](assert) { function Beer() {} Beer.prototype.type = 'ipa'; - let beer = new Beer(); + obj = new Beer(); - addObserver(beer, 'brand', K); + addObserver(obj, 'brand', K); - assert.deepEqual(Object.keys(beer), []); + assert.deepEqual(Object.keys(obj), []); - set(beer, 'brand', 'Corona'); - assert.deepEqual(Object.keys(beer), ['brand']); + set(obj, 'brand', 'Corona'); + assert.deepEqual(Object.keys(obj), ['brand']); - removeObserver(beer, 'brand', K); + removeObserver(obj, 'brand', K); } ['@test with observers switched on and off'](assert) { function Beer() {} Beer.prototype.type = 'ipa'; - let beer = new Beer(); + obj = new Beer(); - addObserver(beer, 'type', K); - removeObserver(beer, 'type', K); + addObserver(obj, 'type', K); + removeObserver(obj, 'type', K); - assert.deepEqual(Object.keys(beer), []); + assert.deepEqual(Object.keys(obj), []); } ['@test observers switched on and off with setter in between'](assert) { function Beer() {} Beer.prototype.type = 'ipa'; - let beer = new Beer(); + obj = new Beer(); - addObserver(beer, 'type', K); - set(beer, 'type', 'ale'); - removeObserver(beer, 'type', K); + addObserver(obj, 'type', K); + set(obj, 'type', 'ale'); + removeObserver(obj, 'type', K); - assert.deepEqual(Object.keys(beer), ['type']); + assert.deepEqual(Object.keys(obj), ['type']); } ['@test observer switched on and off and then setter'](assert) { function Beer() {} Beer.prototype.type = 'ipa'; - let beer = new Beer(); + obj = new Beer(); - addObserver(beer, 'type', K); - removeObserver(beer, 'type', K); - set(beer, 'type', 'ale'); + addObserver(obj, 'type', K); + removeObserver(obj, 'type', K); + set(obj, 'type', 'ale'); - assert.deepEqual(Object.keys(beer), ['type']); + assert.deepEqual(Object.keys(obj), ['type']); } ['@test observers switched on and off with setter in between (observed property is not shadowing)']( @@ -871,9 +918,9 @@ moduleFor( ) { function Beer() {} - let beer = new Beer(); - set(beer, 'type', 'ale'); - assert.deepEqual(Object.keys(beer), ['type'], 'only set'); + obj = new Beer(); + set(obj, 'type', 'ale'); + assert.deepEqual(Object.keys(obj), ['type'], 'only set'); let otherBeer = new Beer(); addObserver(otherBeer, 'type', K); @@ -883,8 +930,8 @@ moduleFor( let yetAnotherBeer = new Beer(); addObserver(yetAnotherBeer, 'type', K); set(yetAnotherBeer, 'type', 'ale'); - addObserver(beer, 'type', K); - removeObserver(beer, 'type', K); + addObserver(obj, 'type', K); + removeObserver(obj, 'type', K); assert.deepEqual( Object.keys(yetAnotherBeer), ['type'], @@ -893,9 +940,13 @@ moduleFor( let itsMyLastBeer = new Beer(); set(itsMyLastBeer, 'type', 'ale'); - addObserver(beer, 'type', K); - removeObserver(beer, 'type', K); + addObserver(obj, 'type', K); + removeObserver(obj, 'type', K); assert.deepEqual(Object.keys(itsMyLastBeer), ['type'], 'set -> removeObserver'); + + destroy(otherBeer); + destroy(yetAnotherBeer); + destroy(itsMyLastBeer); } ['@test observers switched on and off with setter in between (observed property is shadowing one on the prototype)']( @@ -904,9 +955,9 @@ moduleFor( function Beer() {} Beer.prototype.type = 'ipa'; - let beer = new Beer(); - set(beer, 'type', 'ale'); - assert.deepEqual(Object.keys(beer), ['type'], 'after set'); + obj = new Beer(); + set(obj, 'type', 'ale'); + assert.deepEqual(Object.keys(obj), ['type'], 'after set'); let otherBeer = new Beer(); addObserver(otherBeer, 'type', K); @@ -916,8 +967,8 @@ moduleFor( let yetAnotherBeer = new Beer(); addObserver(yetAnotherBeer, 'type', K); set(yetAnotherBeer, 'type', 'ale'); - addObserver(beer, 'type', K); - removeObserver(beer, 'type', K); + addObserver(obj, 'type', K); + removeObserver(obj, 'type', K); assert.deepEqual( Object.keys(yetAnotherBeer), ['type'], @@ -926,9 +977,13 @@ moduleFor( let itsMyLastBeer = new Beer(); set(itsMyLastBeer, 'type', 'ale'); - addObserver(beer, 'type', K); - removeObserver(beer, 'type', K); + addObserver(obj, 'type', K); + removeObserver(obj, 'type', K); assert.deepEqual(Object.keys(itsMyLastBeer), ['type'], 'set -> removeObserver'); + + destroy(otherBeer); + destroy(yetAnotherBeer); + destroy(itsMyLastBeer); } } ); diff --git a/packages/@ember/-internals/metal/tests/performance_test.js b/packages/@ember/-internals/metal/tests/performance_test.js index 0a591a25202..0a6020714f4 100644 --- a/packages/@ember/-internals/metal/tests/performance_test.js +++ b/packages/@ember/-internals/metal/tests/performance_test.js @@ -2,6 +2,7 @@ import { set, get, computed, + destroy, defineProperty, notifyPropertyChange, beginPropertyChanges, @@ -10,6 +11,8 @@ import { } from '..'; import { moduleFor, AbstractTestCase, runLoopSettled } from 'internal-test-helpers'; +let obj; + /* This test file is designed to capture performance regressions related to deferred computation. Things like run loops, computed properties, and bindings @@ -20,10 +23,18 @@ import { moduleFor, AbstractTestCase, runLoopSettled } from 'internal-test-helpe moduleFor( 'Computed Properties - Number of times evaluated', class extends AbstractTestCase { + afterEach() { + if (obj !== undefined) { + destroy(obj); + obj = undefined; + } + return runLoopSettled(); + } + async ['@test computed properties that depend on multiple properties should run only once per run loop']( assert ) { - let obj = { a: 'a', b: 'b', c: 'c' }; + obj = { a: 'a', b: 'b', c: 'c' }; let cpCount = 0; let obsCount = 0; @@ -61,21 +72,21 @@ moduleFor( ['@test computed properties are not executed if they are the last segment of an observer chain pain']( assert ) { - let foo = { bar: { baz: {} } }; + obj = { bar: { baz: {} } }; let count = 0; defineProperty( - foo.bar.baz, + obj.bar.baz, 'bam', computed(function() { count++; }) ); - addObserver(foo, 'bar.baz.bam', function() {}); + addObserver(obj, 'bar.baz.bam', function() {}); - notifyPropertyChange(get(foo, 'bar.baz'), 'bam'); + notifyPropertyChange(get(obj, 'bar.baz'), 'bam'); assert.equal(count, 0, 'should not have recomputed property'); } diff --git a/packages/@ember/-internals/runtime/lib/system/core_object.js b/packages/@ember/-internals/runtime/lib/system/core_object.js index e54d345a9a6..ccb5f83e6b6 100644 --- a/packages/@ember/-internals/runtime/lib/system/core_object.js +++ b/packages/@ember/-internals/runtime/lib/system/core_object.js @@ -15,7 +15,7 @@ import { isInternalSymbol, } from '@ember/-internals/utils'; import { schedule } from '@ember/runloop'; -import { meta, peekMeta, deleteMeta } from '@ember/-internals/meta'; +import { meta, peekMeta } from '@ember/-internals/meta'; import { PROXY_CONTENT, sendEvent, @@ -24,6 +24,7 @@ import { applyMixin, defineProperty, descriptorForProperty, + destroy, classToString, isClassicDecorator, DEBUG_INJECTION_FUNCTIONS, @@ -542,16 +543,10 @@ class CoreObject { @public */ destroy() { - let m = peekMeta(this); - if (m.isSourceDestroying()) { + if (destroy(this)) { + schedule('actions', this, this.willDestroy); return; } - - m.setSourceDestroying(); - - schedule('actions', this, this.willDestroy); - schedule('destroy', this, this._scheduledDestroy, m); - return this; } @@ -563,21 +558,6 @@ class CoreObject { */ willDestroy() {} - /** - Invoked by the run loop to actually destroy the object. This is - scheduled for execution by the `destroy` method. - - @private - @method _scheduledDestroy - */ - _scheduledDestroy(m) { - if (m.isSourceDestroyed()) { - return; - } - deleteMeta(this); - m.setSourceDestroyed(); - } - /** Returns a string representation which attempts to provide more information than Javascript's `toString` typically does, in a generic way for all Ember diff --git a/packages/@ember/-internals/runtime/tests/ext/function_test.js b/packages/@ember/-internals/runtime/tests/ext/function_test.js index 47b2975414c..7ac5729f286 100644 --- a/packages/@ember/-internals/runtime/tests/ext/function_test.js +++ b/packages/@ember/-internals/runtime/tests/ext/function_test.js @@ -1,5 +1,5 @@ import { ENV } from '@ember/-internals/environment'; -import { Mixin, mixin, get, set } from '@ember/-internals/metal'; +import { Mixin, mixin, get, set, destroy } from '@ember/-internals/metal'; import EmberObject from '../../lib/system/object'; import Evented from '../../lib/mixins/evented'; import { moduleFor, AbstractTestCase, runLoopSettled } from 'internal-test-helpers'; @@ -38,6 +38,8 @@ moduleFor( await runLoopSettled(); assert.equal(get(obj, 'count'), 2, 'should invoke observer after change'); + + destroy(obj); } } ); @@ -100,6 +102,8 @@ moduleFor( await runLoopSettled(); assert.equal(get(obj, 'count'), 2, 'should invoke observer and listener'); + + destroy(obj); } } ); diff --git a/packages/@ember/-internals/runtime/tests/legacy_1x/mixins/observable/chained_test.js b/packages/@ember/-internals/runtime/tests/legacy_1x/mixins/observable/chained_test.js index fcc31dce395..3f3fc3d2d9c 100644 --- a/packages/@ember/-internals/runtime/tests/legacy_1x/mixins/observable/chained_test.js +++ b/packages/@ember/-internals/runtime/tests/legacy_1x/mixins/observable/chained_test.js @@ -68,6 +68,13 @@ moduleFor( 0, 'observer did not fire after removing changing property on a removed object' ); + + family.destroy(); + momma.destroy(); + child1.destroy(); + child2.destroy(); + child3.destroy(); + child4.destroy(); } } ); diff --git a/packages/@ember/-internals/runtime/tests/legacy_1x/mixins/observable/observable_test.js b/packages/@ember/-internals/runtime/tests/legacy_1x/mixins/observable/observable_test.js index 217a036a628..d464e571b35 100644 --- a/packages/@ember/-internals/runtime/tests/legacy_1x/mixins/observable/observable_test.js +++ b/packages/@ember/-internals/runtime/tests/legacy_1x/mixins/observable/observable_test.js @@ -32,18 +32,32 @@ import { moduleFor, AbstractTestCase, runLoopSettled } from 'internal-test-helpe // Ember.Observable Tests // ======================================================================== -let object, ObjectC, ObjectD, objectA, objectB, lookup; +let object, objectA, objectB, objectC, objectD, objectE, objectF, lookup; const ObservableObject = EmberObject.extend(Observable); const originalLookup = context.lookup; +class ObservableTestCase extends AbstractTestCase { + afterEach() { + let destroyables = [object, objectA, objectB, objectC, objectD, objectE, objectF].filter( + obj => obj && obj.destroy + ); + + object = objectA = objectC = objectD = objectE = objectF = undefined; + context.lookup = originalLookup; + lookup = undefined; + destroyables.forEach(obj => obj.destroy()); + return runLoopSettled(); + } +} + // .......................................................... // GET() // moduleFor( 'object.get()', - class extends AbstractTestCase { + class extends ObservableTestCase { beforeEach() { object = ObservableObject.extend(Observable, { computed: computed(function() { @@ -92,7 +106,7 @@ moduleFor( // moduleFor( 'Ember.get()', - class extends AbstractTestCase { + class extends ObservableTestCase { beforeEach() { objectA = ObservableObject.extend({ computed: computed(function() { @@ -158,7 +172,7 @@ moduleFor( moduleFor( 'Ember.get() with paths', - class extends AbstractTestCase { + class extends ObservableTestCase { ['@test should return a property at a given path relative to the passed object'](assert) { let foo = ObservableObject.create({ bar: ObservableObject.extend({ @@ -191,7 +205,7 @@ moduleFor( moduleFor( 'object.set()', - class extends AbstractTestCase { + class extends ObservableTestCase { beforeEach() { object = ObservableObject.extend({ computed: computed({ @@ -280,7 +294,7 @@ moduleFor( moduleFor( 'Computed properties', - class extends AbstractTestCase { + class extends ObservableTestCase { beforeEach() { lookup = context.lookup = {}; @@ -375,9 +389,6 @@ moduleFor( }); }); } - afterEach() { - context.lookup = originalLookup; - } ['@test getting values should call function return value'](assert) { // get each property twice. Verify return. @@ -641,7 +652,7 @@ moduleFor( moduleFor( 'Observable objects & object properties ', - class extends AbstractTestCase { + class extends ObservableTestCase { beforeEach() { object = ObservableObject.extend({ getEach() { @@ -759,13 +770,13 @@ moduleFor( moduleFor( 'object.addObserver()', - class extends AbstractTestCase { + class extends ObservableTestCase { beforeEach() { - ObjectC = ObservableObject.create({ - objectE: ObservableObject.create({ - propertyVal: 'chainedProperty', - }), - + objectE = ObservableObject.create({ + propertyVal: 'chainedProperty', + }); + objectC = ObservableObject.create({ + objectE, normal: 'value', normal1: 'zeroValue', normal2: 'dependentValue', @@ -786,38 +797,39 @@ moduleFor( } async ['@test should register an observer for a property'](assert) { - ObjectC.addObserver('normal', ObjectC, 'action'); - ObjectC.set('normal', 'newValue'); + objectC.addObserver('normal', objectC, 'action'); + objectC.set('normal', 'newValue'); await runLoopSettled(); - assert.equal(ObjectC.normal1, 'newZeroValue'); + assert.equal(objectC.normal1, 'newZeroValue'); } async ['@test should register an observer for a property - Special case of chained property']( assert ) { - ObjectC.addObserver('objectE.propertyVal', ObjectC, 'chainedObserver'); - ObjectC.objectE.set('propertyVal', 'chainedPropertyValue'); + objectC.addObserver('objectE.propertyVal', objectC, 'chainedObserver'); + objectC.objectE.set('propertyVal', 'chainedPropertyValue'); await runLoopSettled(); - assert.equal('chainedPropertyObserved', ObjectC.normal2); - ObjectC.normal2 = 'dependentValue'; - ObjectC.set('objectE', ''); + assert.equal('chainedPropertyObserved', objectC.normal2); + objectC.normal2 = 'dependentValue'; + objectC.set('objectE', ''); await runLoopSettled(); - assert.equal('chainedPropertyObserved', ObjectC.normal2); + assert.equal('chainedPropertyObserved', objectC.normal2); } } ); moduleFor( 'object.removeObserver()', - class extends AbstractTestCase { + class extends ObservableTestCase { beforeEach() { - ObjectD = ObservableObject.create({ - objectF: ObservableObject.create({ - propertyVal: 'chainedProperty', - }), + objectF = ObservableObject.create({ + propertyVal: 'chainedProperty', + }); + objectD = ObservableObject.create({ + objectF, normal: 'value', normal1: 'zeroValue', @@ -852,39 +864,39 @@ moduleFor( } async ['@test should unregister an observer for a property'](assert) { - ObjectD.addObserver('normal', ObjectD, 'addAction'); - ObjectD.set('normal', 'newValue'); + objectD.addObserver('normal', objectD, 'addAction'); + objectD.set('normal', 'newValue'); await runLoopSettled(); - assert.equal(ObjectD.normal1, 'newZeroValue'); + assert.equal(objectD.normal1, 'newZeroValue'); - ObjectD.set('normal1', 'zeroValue'); + objectD.set('normal1', 'zeroValue'); await runLoopSettled(); - ObjectD.removeObserver('normal', ObjectD, 'addAction'); - ObjectD.set('normal', 'newValue'); - assert.equal(ObjectD.normal1, 'zeroValue'); + objectD.removeObserver('normal', objectD, 'addAction'); + objectD.set('normal', 'newValue'); + assert.equal(objectD.normal1, 'zeroValue'); } async ["@test should unregister an observer for a property - special case when key has a '.' in it."]( assert ) { - ObjectD.addObserver('objectF.propertyVal', ObjectD, 'removeChainedObserver'); - ObjectD.objectF.set('propertyVal', 'chainedPropertyValue'); + objectD.addObserver('objectF.propertyVal', objectD, 'removeChainedObserver'); + objectD.objectF.set('propertyVal', 'chainedPropertyValue'); await runLoopSettled(); - ObjectD.removeObserver('objectF.propertyVal', ObjectD, 'removeChainedObserver'); - ObjectD.normal2 = 'dependentValue'; + objectD.removeObserver('objectF.propertyVal', objectD, 'removeChainedObserver'); + objectD.normal2 = 'dependentValue'; - ObjectD.objectF.set('propertyVal', 'removedPropertyValue'); + objectD.objectF.set('propertyVal', 'removedPropertyValue'); await runLoopSettled(); - assert.equal('dependentValue', ObjectD.normal2); + assert.equal('dependentValue', objectD.normal2); - ObjectD.set('objectF', ''); + objectD.set('objectF', ''); await runLoopSettled(); - assert.equal('dependentValue', ObjectD.normal2); + assert.equal('dependentValue', objectD.normal2); } async ['@test removing an observer inside of an observer shouldn’t cause any problems']( @@ -894,11 +906,11 @@ moduleFor( // observers in the middle of observer notification. let encounteredError = false; try { - ObjectD.addObserver('observableValue', null, 'observer1'); - ObjectD.addObserver('observableValue', null, 'observer2'); - ObjectD.addObserver('observableValue', null, 'observer3'); + objectD.addObserver('observableValue', null, 'observer1'); + objectD.addObserver('observableValue', null, 'observer2'); + objectD.addObserver('observableValue', null, 'observer3'); - ObjectD.set('observableValue', 'hi world'); + objectD.set('observableValue', 'hi world'); await runLoopSettled(); } catch (e) { diff --git a/packages/@ember/-internals/runtime/tests/mixins/array_test.js b/packages/@ember/-internals/runtime/tests/mixins/array_test.js index e8f695f3324..500ce933c67 100644 --- a/packages/@ember/-internals/runtime/tests/mixins/array_test.js +++ b/packages/@ember/-internals/runtime/tests/mixins/array_test.js @@ -118,6 +118,11 @@ moduleFor( assert.equal(obj._count, 1, 'should have invoked'); } + + afterEach() { + obj.destroy(); + obj = undefined; + } } ); @@ -141,7 +146,8 @@ moduleFor( } afterEach() { - obj = null; + obj.destroy(); + obj = undefined; } async ['@test should notify observers when call with no params'](assert) { @@ -272,6 +278,7 @@ moduleFor( } afterEach() { + ary.destroy(); ary = null; } @@ -376,6 +383,8 @@ moduleFor( await runLoopSettled(); assert.equal(count, 2, 'observers should be called twice'); + + obj.destroy(); } } ); diff --git a/packages/@ember/-internals/runtime/tests/mixins/observable_test.js b/packages/@ember/-internals/runtime/tests/mixins/observable_test.js index c2434a07bdd..7672bc43344 100644 --- a/packages/@ember/-internals/runtime/tests/mixins/observable_test.js +++ b/packages/@ember/-internals/runtime/tests/mixins/observable_test.js @@ -78,6 +78,8 @@ moduleFor( await runLoopSettled(); assert.equal(firstNameChangedCount, 1, 'firstName should have fired once'); + + obj.destroy(); } ['@test should be able to retrieve cached values of computed properties without invoking the computed property']( diff --git a/packages/@ember/-internals/runtime/tests/mixins/promise_proxy_test.js b/packages/@ember/-internals/runtime/tests/mixins/promise_proxy_test.js index 27eface1852..a5ffd6f0c48 100644 --- a/packages/@ember/-internals/runtime/tests/mixins/promise_proxy_test.js +++ b/packages/@ember/-internals/runtime/tests/mixins/promise_proxy_test.js @@ -7,7 +7,7 @@ import { onerrorDefault } from '../../lib/ext/rsvp'; import * as RSVP from 'rsvp'; import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; -let ObjectPromiseProxy; +let ObjectPromiseProxy, proxy; moduleFor( 'Ember.PromiseProxy - ObjectProxy', @@ -18,6 +18,8 @@ moduleFor( afterEach() { RSVP.on('error', onerrorDefault); + if (proxy) proxy.destroy(); + proxy = undefined; } ['@test present on ember namespace'](assert) { @@ -25,7 +27,7 @@ moduleFor( } ['@test no promise, invoking then should raise'](assert) { - let proxy = ObjectPromiseProxy.create(); + proxy = ObjectPromiseProxy.create(); assert.throws(function() { proxy.then( @@ -47,7 +49,7 @@ moduleFor( let deferred = RSVP.defer(); - let proxy = ObjectPromiseProxy.create({ + proxy = ObjectPromiseProxy.create({ promise: deferred.promise, }); @@ -153,7 +155,7 @@ moduleFor( ['@test rejection'](assert) { let reason = new Error('failure'); let deferred = RSVP.defer(); - let proxy = ObjectPromiseProxy.create({ + proxy = ObjectPromiseProxy.create({ promise: deferred.promise, }); @@ -253,7 +255,7 @@ moduleFor( // https://github.com/emberjs/ember.js/issues/15694 ['@test rejection without specifying reason'](assert) { let deferred = RSVP.defer(); - let proxy = ObjectPromiseProxy.create({ + proxy = ObjectPromiseProxy.create({ promise: deferred.promise, }); @@ -326,7 +328,7 @@ moduleFor( let expectedReason = new Error('failure'); let deferred = RSVP.defer(); - let proxy = ObjectPromiseProxy.create({ + proxy = ObjectPromiseProxy.create({ promise: deferred.promise, }); @@ -353,7 +355,7 @@ moduleFor( ['@test should work with promise inheritance'](assert) { class PromiseSubclass extends RSVP.Promise {} - let proxy = ObjectPromiseProxy.create({ + proxy = ObjectPromiseProxy.create({ promise: new PromiseSubclass(() => {}), }); @@ -363,7 +365,7 @@ moduleFor( ['@test should reset isFulfilled and isRejected when promise is reset'](assert) { let deferred = EmberRSVP.defer(); - let proxy = ObjectPromiseProxy.create({ + proxy = ObjectPromiseProxy.create({ promise: deferred.promise, }); @@ -462,7 +464,7 @@ moduleFor( ['@test should have content when isFulfilled is set'](assert) { let deferred = EmberRSVP.defer(); - let proxy = ObjectPromiseProxy.create({ + proxy = ObjectPromiseProxy.create({ promise: deferred.promise, }); @@ -475,7 +477,7 @@ moduleFor( let error = new Error('Y U REJECT?!?'); let deferred = EmberRSVP.defer(); - let proxy = ObjectPromiseProxy.create({ + proxy = ObjectPromiseProxy.create({ promise: deferred.promise, }); @@ -491,7 +493,7 @@ moduleFor( ['@test should not error if promise is resolved after proxy has been destroyed'](assert) { let deferred = EmberRSVP.defer(); - let proxy = ObjectPromiseProxy.create({ + proxy = ObjectPromiseProxy.create({ promise: deferred.promise, }); @@ -510,7 +512,7 @@ moduleFor( ['@test should not error if promise is rejected after proxy has been destroyed'](assert) { let deferred = EmberRSVP.defer(); - let proxy = ObjectPromiseProxy.create({ + proxy = ObjectPromiseProxy.create({ promise: deferred.promise, }); @@ -534,7 +536,7 @@ moduleFor( let receivedValue; let didResolveCount = 0; - let proxy = ObjectPromiseProxy.create({ + proxy = ObjectPromiseProxy.create({ promise: deferred.promise, }); @@ -566,7 +568,7 @@ moduleFor( let receivedReason; let didRejectCount = 0; - let proxy = ObjectPromiseProxy.create({ + proxy = ObjectPromiseProxy.create({ promise: deferred.promise, }); diff --git a/packages/@ember/-internals/runtime/tests/mutable-array/addObject-test.js b/packages/@ember/-internals/runtime/tests/mutable-array/addObject-test.js index 47bb152536f..774b13ea17e 100644 --- a/packages/@ember/-internals/runtime/tests/mutable-array/addObject-test.js +++ b/packages/@ember/-internals/runtime/tests/mutable-array/addObject-test.js @@ -42,6 +42,8 @@ class AddObjectTest extends AbstractTestCase { 'should NOT have notified firstObject once' ); } + + obj.destroy(); } async '@test [A,B,C].addObject(A) => [A,B,C] + NO notify'() { @@ -76,6 +78,8 @@ class AddObjectTest extends AbstractTestCase { 'should NOT have notified lastObject once' ); } + + obj.destroy(); } } diff --git a/packages/@ember/-internals/runtime/tests/mutable-array/clear-test.js b/packages/@ember/-internals/runtime/tests/mutable-array/clear-test.js index fde8c592365..1777df83171 100644 --- a/packages/@ember/-internals/runtime/tests/mutable-array/clear-test.js +++ b/packages/@ember/-internals/runtime/tests/mutable-array/clear-test.js @@ -32,6 +32,8 @@ class ClearTests extends AbstractTestCase { false, 'should NOT have notified lastObject once' ); + + obj.destroy(); } async '@test [X].clear() => [] + notify'() { @@ -64,6 +66,8 @@ class ClearTests extends AbstractTestCase { 1, 'should have notified lastObject once' ); + + obj.destroy(); } } diff --git a/packages/@ember/-internals/runtime/tests/mutable-array/insertAt-test.js b/packages/@ember/-internals/runtime/tests/mutable-array/insertAt-test.js index 7d07645c32d..86023cf246a 100644 --- a/packages/@ember/-internals/runtime/tests/mutable-array/insertAt-test.js +++ b/packages/@ember/-internals/runtime/tests/mutable-array/insertAt-test.js @@ -39,6 +39,8 @@ class InsertAtTests extends AbstractTestCase { 1, 'should have notified lastObject did change once' ); + + obj.destroy(); } '@test [].insertAt(200,X) => OUT_OF_RANGE_EXCEPTION exception'() { @@ -78,6 +80,8 @@ class InsertAtTests extends AbstractTestCase { false, 'should NOT have notified lastObject' ); + + obj.destroy(); } async '@test [A].insertAt(1, X) => [A,X] + notify'() { @@ -111,6 +115,8 @@ class InsertAtTests extends AbstractTestCase { false, 'should NOT have notified firstObject' ); + + obj.destroy(); } '@test [A].insertAt(200,X) => OUT_OF_RANGE exception'() { @@ -150,6 +156,8 @@ class InsertAtTests extends AbstractTestCase { false, 'should NOT have notified lastObject' ); + + obj.destroy(); } async '@test [A,B,C].insertAt(1,X) => [A,X,B,C] + notify'() { @@ -194,6 +202,8 @@ class InsertAtTests extends AbstractTestCase { false, 'should NOT have notified lastObject' ); + + obj.destroy(); } async '@test [A,B,C].insertAt(3,X) => [A,B,C,X] + notify'() { @@ -227,6 +237,8 @@ class InsertAtTests extends AbstractTestCase { false, 'should NOT have notified firstObject' ); + + obj.destroy(); } } diff --git a/packages/@ember/-internals/runtime/tests/mutable-array/popObject-test.js b/packages/@ember/-internals/runtime/tests/mutable-array/popObject-test.js index ed69e1750a4..d5af6ebeba2 100644 --- a/packages/@ember/-internals/runtime/tests/mutable-array/popObject-test.js +++ b/packages/@ember/-internals/runtime/tests/mutable-array/popObject-test.js @@ -29,6 +29,8 @@ class PopObjectTests extends AbstractTestCase { false, 'should NOT have notified lastObject' ); + + obj.destroy(); } async '@test [X].popObject() => [] + notify'() { @@ -61,6 +63,8 @@ class PopObjectTests extends AbstractTestCase { 1, 'should have notified lastObject once' ); + + obj.destroy(); } async '@test [A,B,C].popObject() => [A,B] + notify'() { @@ -94,6 +98,8 @@ class PopObjectTests extends AbstractTestCase { false, 'should NOT have notified firstObject' ); + + obj.destroy(); } } diff --git a/packages/@ember/-internals/runtime/tests/mutable-array/pushObject-test.js b/packages/@ember/-internals/runtime/tests/mutable-array/pushObject-test.js index 5fc74a0c414..430693bf82a 100644 --- a/packages/@ember/-internals/runtime/tests/mutable-array/pushObject-test.js +++ b/packages/@ember/-internals/runtime/tests/mutable-array/pushObject-test.js @@ -39,6 +39,8 @@ class PushObjectTests extends AbstractTestCase { 1, 'should have notified lastObject once' ); + + obj.destroy(); } async '@test [A,B,C].pushObject(X) => [A,B,C,X] + notify'() { @@ -72,6 +74,8 @@ class PushObjectTests extends AbstractTestCase { false, 'should NOT have notified firstObject' ); + + obj.destroy(); } async '@test [A,B,C,C].pushObject(A) => [A,B,C,C] + notify'() { @@ -101,6 +105,8 @@ class PushObjectTests extends AbstractTestCase { 'should NOT have notified firstObject' ); this.assert.equal(observer.validate('lastObject'), true, 'should have notified lastObject'); + + obj.destroy(); } } diff --git a/packages/@ember/-internals/runtime/tests/mutable-array/removeAt-test.js b/packages/@ember/-internals/runtime/tests/mutable-array/removeAt-test.js index 1ff6d189eb5..752ab2335d3 100644 --- a/packages/@ember/-internals/runtime/tests/mutable-array/removeAt-test.js +++ b/packages/@ember/-internals/runtime/tests/mutable-array/removeAt-test.js @@ -33,6 +33,8 @@ class RemoveAtTests extends AbstractTestCase { 1, 'should have notified lastObject once' ); + + obj.destroy(); } '@test removeAt([], 200) => OUT_OF_RANGE_EXCEPTION exception'() { @@ -70,6 +72,8 @@ class RemoveAtTests extends AbstractTestCase { false, 'should NOT have notified lastObject' ); + + obj.destroy(); } async '@test removeAt([A,B], 1) => [A] + notify'() { @@ -102,6 +106,8 @@ class RemoveAtTests extends AbstractTestCase { false, 'should NOT have notified firstObject once' ); + + obj.destroy(); } async '@test removeAt([A,B,C], 1) => [A,C] + notify'() { @@ -134,6 +140,8 @@ class RemoveAtTests extends AbstractTestCase { false, 'should NOT have notified lastObject once' ); + + obj.destroy(); } async '@test removeAt([A,B,C,D], 1,2) => [A,D] + notify'() { @@ -166,6 +174,8 @@ class RemoveAtTests extends AbstractTestCase { false, 'should NOT have notified lastObject once' ); + + obj.destroy(); } async '@test [A,B,C,D].removeAt(1,2) => [A,D] + notify'() { @@ -199,6 +209,8 @@ class RemoveAtTests extends AbstractTestCase { false, 'should NOT have notified lastObject once' ); + + obj.destroy(); } } diff --git a/packages/@ember/-internals/runtime/tests/mutable-array/removeObject-test.js b/packages/@ember/-internals/runtime/tests/mutable-array/removeObject-test.js index a4c91a6f622..e67d6a20fe0 100644 --- a/packages/@ember/-internals/runtime/tests/mutable-array/removeObject-test.js +++ b/packages/@ember/-internals/runtime/tests/mutable-array/removeObject-test.js @@ -8,6 +8,8 @@ class RemoveObjectTests extends AbstractTestCase { let obj = this.newObject(before); this.assert.equal(obj.removeObject(before[1]), obj, 'should return receiver'); + + obj.destroy(); } async '@test [A,B,C].removeObject(B) => [A,C] + notify'() { @@ -42,6 +44,8 @@ class RemoveObjectTests extends AbstractTestCase { 'should NOT have notified lastObject once' ); } + + obj.destroy(); } async '@test [A,B,C].removeObject(D) => [A,B,C]'() { @@ -77,6 +81,8 @@ class RemoveObjectTests extends AbstractTestCase { 'should NOT have notified lastObject once' ); } + + obj.destroy(); } } diff --git a/packages/@ember/-internals/runtime/tests/mutable-array/removeObjects-test.js b/packages/@ember/-internals/runtime/tests/mutable-array/removeObjects-test.js index 7e18699fe97..d010061e824 100644 --- a/packages/@ember/-internals/runtime/tests/mutable-array/removeObjects-test.js +++ b/packages/@ember/-internals/runtime/tests/mutable-array/removeObjects-test.js @@ -1,4 +1,4 @@ -import { get } from '@ember/-internals/metal'; +import { destroy, get } from '@ember/-internals/metal'; import { AbstractTestCase, runLoopSettled } from 'internal-test-helpers'; import { runArrayTests, newFixture, newObjectsFixture } from '../helpers/array'; import { A as emberA } from '../../lib/mixins/array'; @@ -42,6 +42,8 @@ class RemoveObjectsTests extends AbstractTestCase { 'should NOT have notified lastObject' ); } + + destroy(obj); } async '@test [{A},{B},{C}].removeObjects([{B}]) => [{A},{C}] + notify'() { @@ -75,6 +77,8 @@ class RemoveObjectsTests extends AbstractTestCase { 'should NOT have notified lastObject' ); } + + destroy(obj); } async '@test [A,B,C].removeObjects([A,B]) => [C] + notify'() { @@ -104,6 +108,8 @@ class RemoveObjectsTests extends AbstractTestCase { 'should NOT have notified lastObject' ); } + + destroy(obj); } async '@test [{A},{B},{C}].removeObjects([{A},{B}]) => [{C}] + notify'() { @@ -133,6 +139,8 @@ class RemoveObjectsTests extends AbstractTestCase { 'should NOT have notified lastObject' ); } + + destroy(obj); } async '@test [A,B,C].removeObjects([A,B,C]) => [] + notify'() { @@ -158,6 +166,8 @@ class RemoveObjectsTests extends AbstractTestCase { this.assert.equal(observer.timesCalled('firstObject'), 1, 'should have notified firstObject'); this.assert.equal(observer.timesCalled('lastObject'), 1, 'should have notified lastObject'); } + + destroy(obj); } async '@test [{A},{B},{C}].removeObjects([{A},{B},{C}]) => [] + notify'() { @@ -183,6 +193,8 @@ class RemoveObjectsTests extends AbstractTestCase { this.assert.equal(observer.timesCalled('firstObject'), 1, 'should have notified firstObject'); this.assert.equal(observer.validate('lastObject'), 1, 'should have notified lastObject'); } + + destroy(obj); } async '@test [A,B,C].removeObjects([D]) => [A,B,C]'() { @@ -217,6 +229,8 @@ class RemoveObjectsTests extends AbstractTestCase { 'should NOT have notified lastObject' ); } + + destroy(obj); } } diff --git a/packages/@ember/-internals/runtime/tests/mutable-array/replace-test.js b/packages/@ember/-internals/runtime/tests/mutable-array/replace-test.js index 3d0370ab309..db21599d8e3 100644 --- a/packages/@ember/-internals/runtime/tests/mutable-array/replace-test.js +++ b/packages/@ember/-internals/runtime/tests/mutable-array/replace-test.js @@ -29,6 +29,8 @@ class ReplaceTests extends AbstractTestCase { 1, 'should have notified lastObject once' ); + + obj.destroy(); } async '@test [].replace(0,0,"X") => ["X"] + avoid calling objectAt and notifying fistObject/lastObject when not in cache'() { @@ -61,6 +63,8 @@ class ReplaceTests extends AbstractTestCase { false, 'should NOT have notified lastObject since not cached' ); + + obj.destroy(); } async '@test [A,B,C,D].replace(1,2,X) => [A,X,D] + notify'() { @@ -94,6 +98,8 @@ class ReplaceTests extends AbstractTestCase { false, 'should NOT have notified lastObject once' ); + + obj.destroy(); } async '@test [A,B,C,D].replace(1,2,[X,Y]) => [A,X,Y,D] + notify'() { @@ -127,6 +133,8 @@ class ReplaceTests extends AbstractTestCase { false, 'should NOT have notified lastObject once' ); + + obj.destroy(); } async '@test [A,B].replace(1,0,[X,Y]) => [A,X,Y,B] + notify'() { @@ -160,6 +168,8 @@ class ReplaceTests extends AbstractTestCase { false, 'should NOT have notified lastObject once' ); + + obj.destroy(); } async '@test [A,B,C,D].replace(2,2) => [A,B] + notify'() { @@ -192,6 +202,8 @@ class ReplaceTests extends AbstractTestCase { false, 'should NOT have notified firstObject once' ); + + obj.destroy(); } async '@test [A,B,C,D].replace(-1,1) => [A,B,C] + notify'() { @@ -224,6 +236,8 @@ class ReplaceTests extends AbstractTestCase { false, 'should NOT have notified firstObject once' ); + + obj.destroy(); } async '@test Adding object should notify array observer'() { @@ -239,6 +253,8 @@ class ReplaceTests extends AbstractTestCase { this.assert.deepEqual(observer._before, [obj, 2, 2, 1], 'before'); this.assert.deepEqual(observer._after, [obj, 2, 2, 1], 'after'); + + obj.destroy(); } } diff --git a/packages/@ember/-internals/runtime/tests/mutable-array/reverseObjects-test.js b/packages/@ember/-internals/runtime/tests/mutable-array/reverseObjects-test.js index 0a82e05d3bd..0b979253254 100644 --- a/packages/@ember/-internals/runtime/tests/mutable-array/reverseObjects-test.js +++ b/packages/@ember/-internals/runtime/tests/mutable-array/reverseObjects-test.js @@ -32,6 +32,8 @@ class ReverseObjectsTests extends AbstractTestCase { 1, 'should have notified lastObject once' ); + + obj.destroy(); } } diff --git a/packages/@ember/-internals/runtime/tests/mutable-array/setObjects-test.js b/packages/@ember/-internals/runtime/tests/mutable-array/setObjects-test.js index 3229c83ce07..8a611ac5c1e 100644 --- a/packages/@ember/-internals/runtime/tests/mutable-array/setObjects-test.js +++ b/packages/@ember/-internals/runtime/tests/mutable-array/setObjects-test.js @@ -32,6 +32,8 @@ class SetObjectsTests extends AbstractTestCase { 1, 'should have notified lastObject once' ); + + obj.destroy(); } async '@test [A,B,C].setObjects([D, E, F, G]) = > [D, E, F, G] + notify'() { @@ -63,6 +65,8 @@ class SetObjectsTests extends AbstractTestCase { 1, 'should have notified lastObject once' ); + + obj.destroy(); } } diff --git a/packages/@ember/-internals/runtime/tests/mutable-array/shiftObject-test.js b/packages/@ember/-internals/runtime/tests/mutable-array/shiftObject-test.js index 95560280345..8761fdd508f 100644 --- a/packages/@ember/-internals/runtime/tests/mutable-array/shiftObject-test.js +++ b/packages/@ember/-internals/runtime/tests/mutable-array/shiftObject-test.js @@ -45,6 +45,8 @@ class ShiftObjectTests extends AbstractTestCase { false, 'should NOT have notified lastObject once' ); + + obj.destroy(); } async '@test [X].shiftObject() => [] + notify'() { @@ -76,6 +78,8 @@ class ShiftObjectTests extends AbstractTestCase { 1, 'should have notified lastObject once' ); + + obj.destroy(); } async '@test [A,B,C].shiftObject() => [B,C] + notify'() { @@ -108,6 +112,8 @@ class ShiftObjectTests extends AbstractTestCase { false, 'should NOT have notified lastObject once' ); + + obj.destroy(); } } diff --git a/packages/@ember/-internals/runtime/tests/mutable-array/unshiftObject-test.js b/packages/@ember/-internals/runtime/tests/mutable-array/unshiftObject-test.js index 988865fa65e..9457c7807f6 100644 --- a/packages/@ember/-internals/runtime/tests/mutable-array/unshiftObject-test.js +++ b/packages/@ember/-internals/runtime/tests/mutable-array/unshiftObject-test.js @@ -40,6 +40,8 @@ class UnshiftObjectTests extends AbstractTestCase { 1, 'should have notified lastObject once' ); + + obj.destroy(); } async '@test [A,B,C].unshiftObject(X) => [X,A,B,C] + notify'() { @@ -73,6 +75,8 @@ class UnshiftObjectTests extends AbstractTestCase { false, 'should NOT have notified lastObject' ); + + obj.destroy(); } async '@test [A,B,C].unshiftObject(A) => [A,A,B,C] + notify'() { @@ -102,6 +106,8 @@ class UnshiftObjectTests extends AbstractTestCase { false, 'should NOT have notified lastObject' ); + + obj.destroy(); } } diff --git a/packages/@ember/-internals/runtime/tests/mutable-array/unshiftObjects-test.js b/packages/@ember/-internals/runtime/tests/mutable-array/unshiftObjects-test.js index b0074d0a12e..613e3ecfac7 100644 --- a/packages/@ember/-internals/runtime/tests/mutable-array/unshiftObjects-test.js +++ b/packages/@ember/-internals/runtime/tests/mutable-array/unshiftObjects-test.js @@ -39,6 +39,8 @@ class UnshiftObjectsTests extends AbstractTestCase { 1, 'should have notified lastObject once' ); + + obj.destroy(); } async '@test [A,B,C].unshiftObjects([X,Y]) => [X,Y,A,B,C] + notify'() { @@ -72,6 +74,8 @@ class UnshiftObjectsTests extends AbstractTestCase { false, 'should NOT have notified lastObject' ); + + obj.destroy(); } async '@test [A,B,C].unshiftObjects([A,B]) => [A,B,A,B,C] + notify'() { @@ -105,6 +109,8 @@ class UnshiftObjectsTests extends AbstractTestCase { false, 'should NOT have notified lastObject' ); + + obj.destroy(); } } diff --git a/packages/@ember/-internals/runtime/tests/system/array_proxy/length_test.js b/packages/@ember/-internals/runtime/tests/system/array_proxy/length_test.js index e7314eb0ce9..ceeab97bac3 100644 --- a/packages/@ember/-internals/runtime/tests/system/array_proxy/length_test.js +++ b/packages/@ember/-internals/runtime/tests/system/array_proxy/length_test.js @@ -202,6 +202,8 @@ moduleFor( assert.equal(cCalled, 2, 'expected observer `colors.content.length` to be called TWICE'); assert.equal(dCalled, 2, 'expected observer `colors.[]` to be called TWICE'); assert.equal(eCalled, 2, 'expected observer `colors.content.[]` to be called TWICE'); + + obj.destroy(); } } ); diff --git a/packages/@ember/-internals/runtime/tests/system/core_object_test.js b/packages/@ember/-internals/runtime/tests/system/core_object_test.js index 0f809a78234..3daa1734898 100644 --- a/packages/@ember/-internals/runtime/tests/system/core_object_test.js +++ b/packages/@ember/-internals/runtime/tests/system/core_object_test.js @@ -129,6 +129,8 @@ moduleFor( await runLoopSettled(); assert.equal(callCount, 1); + + test.destroy(); } } ); diff --git a/packages/@ember/-internals/runtime/tests/system/object/computed_test.js b/packages/@ember/-internals/runtime/tests/system/object/computed_test.js index 8469646ba92..03ed6291321 100644 --- a/packages/@ember/-internals/runtime/tests/system/object/computed_test.js +++ b/packages/@ember/-internals/runtime/tests/system/object/computed_test.js @@ -272,7 +272,7 @@ moduleFor( baz: computed(K), }); - MyClass.create(); // force apply mixins + MyClass.create().destroy(); // force apply mixins list = []; @@ -356,6 +356,9 @@ moduleFor( assert.equal(obj1.get('name'), '1'); assert.equal(obj2.get('name'), '2'); + + obj1.destroy(); + obj2.destroy(); } ['@test can declare dependent keys with .property()'](assert) { diff --git a/packages/@ember/-internals/runtime/tests/system/object/create_test.js b/packages/@ember/-internals/runtime/tests/system/object/create_test.js index 41856e37cb7..fc1b82453c6 100644 --- a/packages/@ember/-internals/runtime/tests/system/object/create_test.js +++ b/packages/@ember/-internals/runtime/tests/system/object/create_test.js @@ -44,6 +44,8 @@ moduleFor( descriptor = Object.getOwnPropertyDescriptor(o, 'bar'); assert.ok(!descriptor.set, 'Mandatory setter was not setup'); + + o.destroy(); } else { assert.expect(0); } diff --git a/packages/@ember/-internals/runtime/tests/system/object/destroy_test.js b/packages/@ember/-internals/runtime/tests/system/object/destroy_test.js index 50c3450fd38..f1270bc1961 100644 --- a/packages/@ember/-internals/runtime/tests/system/object/destroy_test.js +++ b/packages/@ember/-internals/runtime/tests/system/object/destroy_test.js @@ -136,7 +136,7 @@ moduleFor( objs.c = C.create(); - LongLivedObject.create(); + let longLived = LongLivedObject.create(); for (let obj in objs) { objs[obj].destroy(); @@ -146,6 +146,8 @@ moduleFor( assert.equal(shouldNotChange, 0, 'destroyed graph objs should not see change in willDestroy'); assert.equal(shouldChange, 1, 'long lived should see change in willDestroy'); + + longLived.destroy(); } } ); diff --git a/packages/@ember/-internals/runtime/tests/system/object/es-compatibility-test.js b/packages/@ember/-internals/runtime/tests/system/object/es-compatibility-test.js index f53318dbf91..f6cfe65fa67 100644 --- a/packages/@ember/-internals/runtime/tests/system/object/es-compatibility-test.js +++ b/packages/@ember/-internals/runtime/tests/system/object/es-compatibility-test.js @@ -363,6 +363,9 @@ moduleFor( assert.equal(someEventBase, 1); assert.equal(someEventA, 1); assert.equal(someEventB, 0); + + a.destroy(); + b.destroy(); }); } @@ -529,6 +532,8 @@ moduleFor( 'B onSomeEvent event arg', 'D onSomeEvent after super.onSomeEvent', ]); + + d.destroy(); }); } } diff --git a/packages/@ember/-internals/runtime/tests/system/object/extend_test.js b/packages/@ember/-internals/runtime/tests/system/object/extend_test.js index 5e0ccf9925d..2b7d937c26c 100644 --- a/packages/@ember/-internals/runtime/tests/system/object/extend_test.js +++ b/packages/@ember/-internals/runtime/tests/system/object/extend_test.js @@ -150,6 +150,8 @@ moduleFor( await runLoopSettled(); assert.deepEqual(seen, [1, 2]); + + child.destroy(); } } ); diff --git a/packages/@ember/-internals/runtime/tests/system/object/observer_test.js b/packages/@ember/-internals/runtime/tests/system/object/observer_test.js index 6be762ea474..4fca8038729 100644 --- a/packages/@ember/-internals/runtime/tests/system/object/observer_test.js +++ b/packages/@ember/-internals/runtime/tests/system/object/observer_test.js @@ -22,6 +22,8 @@ moduleFor( await runLoopSettled(); assert.equal(get(obj, 'count'), 1, 'should invoke observer after change'); + + obj.destroy(); } async ['@test setting `undefined` value on observed property behaves correctly'](assert) { @@ -47,6 +49,8 @@ moduleFor( await runLoopSettled(); assert.equal(get(obj, 'mood'), 'awesome'); + + obj.destroy(); } async ['@test observer on subclass'](assert) { @@ -76,6 +80,8 @@ moduleFor( await runLoopSettled(); assert.equal(get(obj, 'count'), 1, 'should invoke observer after change'); + + obj.destroy(); } async ['@test observer on instance'](assert) { @@ -93,6 +99,9 @@ moduleFor( await runLoopSettled(); assert.equal(get(obj, 'count'), 1, 'should invoke observer after change'); + + obj.destroy(); + await runLoopSettled(); } async ['@test observer on instance overriding class'](assert) { @@ -122,9 +131,11 @@ moduleFor( await runLoopSettled(); assert.equal(get(obj, 'count'), 1, 'should invoke observer after change'); + + obj.destroy(); } - ['@test observer should not fire after being destroyed'](assert) { + async ['@test observer should not fire after being destroyed'](assert) { let obj = EmberObject.extend({ count: 0, foo: observer('bar', function() { @@ -141,6 +152,8 @@ moduleFor( }, `calling set on destroyed object: ${obj}.bar = BAZ`); assert.equal(get(obj, 'count'), 0, 'should not invoke observer after change'); + + obj.destroy(); } // .......................................................... @@ -178,6 +191,9 @@ moduleFor( assert.equal(get(obj1, 'count'), 1, 'should not invoke again'); assert.equal(get(obj2, 'count'), 1, 'should invoke observer on obj2'); + + obj1.destroy(); + obj2.destroy(); } async ['@test chain observer on class'](assert) { @@ -222,6 +238,9 @@ moduleFor( assert.equal(get(obj1, 'count'), 1, 'should not invoke again'); assert.equal(get(obj2, 'count'), 1, 'should invoke observer on obj2'); + + obj1.destroy(); + obj2.destroy(); } async ['@test chain observer on class that has a reference to an uninitialized object will finish chains that reference it']( @@ -260,6 +279,9 @@ moduleFor( await runLoopSettled(); assert.equal(changed, true, 'child should have been notified of change to path'); + + parent.child.destroy(); + parent.destroy(); } async ['@test cannot re-enter observer while it is flushing'](assert) { @@ -291,6 +313,8 @@ moduleFor( obj.notifyPropertyChange('foo'); assert.equal(changed, true, 'observer fired successfully'); + + obj.destroy(); } } ); diff --git a/packages/@ember/-internals/runtime/tests/system/object_proxy_test.js b/packages/@ember/-internals/runtime/tests/system/object_proxy_test.js index c7c2d26179a..6a04222993e 100644 --- a/packages/@ember/-internals/runtime/tests/system/object_proxy_test.js +++ b/packages/@ember/-internals/runtime/tests/system/object_proxy_test.js @@ -240,6 +240,8 @@ moduleFor( assert.equal(last, 'Tomhuda Katzdale'); assert.equal(get(content2, 'firstName'), 'Tomhuda'); assert.equal(get(content2, 'lastName'), 'Katzdale'); + + proxy.destroy(); } async ['@test set and get should work with paths'](assert) { @@ -261,6 +263,8 @@ moduleFor( assert.equal(count, 1); assert.equal(proxy.get('foo.bar'), 'bye'); assert.equal(proxy.get('content.foo.bar'), 'bye'); + + proxy.destroy(); } async ['@test should transition between watched and unwatched strategies'](assert) { @@ -334,9 +338,11 @@ moduleFor( ) { assert.expect(0); - ObjectProxy.extend({ + let obj = ObjectProxy.extend({ observe: observer('foo', function() {}), }).create(); + + obj.destroy(); } async '@test custom proxies should be able to notify property changes manually'(assert) { @@ -365,6 +371,8 @@ moduleFor( assert.equal(count, 1); assert.equal(proxy.get('foo'), 456); assert.equal(proxy.get('locals.foo'), 456); + + proxy.destroy(); } } ); diff --git a/packages/@ember/object/tests/computed/dependent-key-compat-test.js b/packages/@ember/object/tests/computed/dependent-key-compat-test.js index a5ffc88f762..412a5e64f77 100644 --- a/packages/@ember/object/tests/computed/dependent-key-compat-test.js +++ b/packages/@ember/object/tests/computed/dependent-key-compat-test.js @@ -89,6 +89,8 @@ moduleFor( await runLoopSettled(); assert.equal(count, 1); + + tom.destroy(); } '@test it does not work with sync observers'(assert) { @@ -120,6 +122,8 @@ moduleFor( tom.firstName = 'Thomas'; assert.equal(count, 0); + + tom.destroy(); } } ); diff --git a/packages/@ember/object/tests/computed/reduce_computed_macros_test.js b/packages/@ember/object/tests/computed/reduce_computed_macros_test.js index 89a648ef122..e8d961a44da 100644 --- a/packages/@ember/object/tests/computed/reduce_computed_macros_test.js +++ b/packages/@ember/object/tests/computed/reduce_computed_macros_test.js @@ -2024,6 +2024,8 @@ moduleFor( ['D', 'C', 'B', 'A'], 'we now sort obj by thing' ); + + obj2.destroy(); } } ); diff --git a/packages/ember/index.js b/packages/ember/index.js index cdf07ab0fe2..3166921242e 100644 --- a/packages/ember/index.js +++ b/packages/ember/index.js @@ -5,7 +5,7 @@ import { IS_NODE, module } from 'node-module'; import * as utils from '@ember/-internals/utils'; import { Registry, Container } from '@ember/-internals/container'; import * as instrumentation from '@ember/instrumentation'; -import { deleteMeta, meta } from '@ember/-internals/meta'; +import { meta } from '@ember/-internals/meta'; import * as metal from '@ember/-internals/metal'; import { FEATURES, isEnabled, EMBER_GLIMMER_SET_COMPONENT_TEMPLATE } from '@ember/canary-features'; import * as EmberDebug from '@ember/debug'; @@ -331,7 +331,7 @@ Ember.platform = { hasPropertyAccessors: true, }; Ember.defineProperty = metal.defineProperty; -Ember.destroy = deleteMeta; +Ember.destroy = metal.destroy; Ember.libraries = metal.libraries; Ember.getProperties = metal.getProperties; Ember.setProperties = metal.setProperties; diff --git a/packages/ember/tests/reexports_test.js b/packages/ember/tests/reexports_test.js index e03126f3243..69985ea1266 100644 --- a/packages/ember/tests/reexports_test.js +++ b/packages/ember/tests/reexports_test.js @@ -179,7 +179,7 @@ let allExports = [ ['platform.defineProperty', null, { value: true }], ['platform.hasPropertyAccessors', null, { value: true }], ['defineProperty', '@ember/-internals/metal'], - ['destroy', '@ember/-internals/meta', 'deleteMeta'], + ['destroy', '@ember/-internals/metal', 'destroy'], ['libraries', '@ember/-internals/metal'], ['getProperties', '@ember/-internals/metal'], ['setProperties', '@ember/-internals/metal'], diff --git a/packages/internal-test-helpers/lib/ember-dev/observers.ts b/packages/internal-test-helpers/lib/ember-dev/observers.ts new file mode 100644 index 00000000000..c99eb73d6f8 --- /dev/null +++ b/packages/internal-test-helpers/lib/ember-dev/observers.ts @@ -0,0 +1,46 @@ +import { ASYNC_OBSERVERS, SYNC_OBSERVERS } from '@ember/-internals/metal'; +import { run } from '@ember/runloop'; + +export function setupObserversCheck(hooks: NestedHooks) { + hooks.afterEach(function() { + let { assert } = QUnit.config.current; + + if (ASYNC_OBSERVERS.size > 0) { + assert.ok(false, 'Should not have any ASYNC_OBSERVERS after tests'); + run(() => { + ASYNC_OBSERVERS.forEach((_, target) => { + ASYNC_OBSERVERS.delete(target); + if (isDestroyable(target)) { + try { + target.destroy(); + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + } + } + }); + }); + } + + if (SYNC_OBSERVERS.size > 0) { + assert.ok(false, 'Should not have any SYNC_OBSERVERS after tests'); + run(() => { + SYNC_OBSERVERS.forEach((_, target) => { + SYNC_OBSERVERS.delete(target); + if (isDestroyable(target)) { + try { + target.destroy(); + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + } + } + }); + }); + } + }); +} + +function isDestroyable(obj: object): obj is { destroy(): void } { + return 'destroy' in obj && typeof obj['destroy'] === 'function'; +} diff --git a/packages/internal-test-helpers/lib/ember-dev/setup-qunit.ts b/packages/internal-test-helpers/lib/ember-dev/setup-qunit.ts index 91e79f14c7a..576165c7b5e 100644 --- a/packages/internal-test-helpers/lib/ember-dev/setup-qunit.ts +++ b/packages/internal-test-helpers/lib/ember-dev/setup-qunit.ts @@ -5,6 +5,7 @@ import { setupAssertionHelpers } from './assertion'; import { setupContainersCheck } from './containers'; import { setupDeprecationHelpers } from './deprecation'; import { setupNamespacesCheck } from './namespaces'; +import { setupObserversCheck } from './observers'; import { setupRunLoopCheck } from './run-loop'; import { DebugEnv } from './utils'; import { setupWarningHelpers } from './warning'; @@ -36,6 +37,7 @@ export default function setupQUnit() { return originalModule(name, function(hooks) { setupContainersCheck(hooks); setupNamespacesCheck(hooks); + setupObserversCheck(hooks); setupRunLoopCheck(hooks); setupAssertionHelpers(hooks, env); setupDeprecationHelpers(hooks, env); diff --git a/tests/docs/expected.js b/tests/docs/expected.js index 3a4f37e9001..84a624de57b 100644 --- a/tests/docs/expected.js +++ b/tests/docs/expected.js @@ -49,7 +49,6 @@ module.exports = { '_queryParamsFor', '_renderMode', '_resolveCache', - '_scheduledDestroy', '_serializeQueryParam', '_serializeQueryParams', '_setRouteName', @@ -164,7 +163,6 @@ module.exports = { 'defer', 'deferReadiness', 'defineProperty', - 'deleteMeta', 'denodeify', 'dependentKeyCompat', 'deprecate',