From a5936755a4cc1ea8af7fb5daaae57e137ad66be6 Mon Sep 17 00:00:00 2001 From: Peter Wagenet Date: Tue, 7 Jul 2026 23:14:37 -0400 Subject: [PATCH] Deprecate Evented and @ember/object/events per RFC 1111 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Squashed from PR #20970 by Peter Wagenet, rebased onto current main. Co-authored-by: Peter Wagenet Finish RFC 1111 implementation: id/versions, deep imports, main compatibility - Register the deprecation as `deprecate-evented` (since 7.3.0, until 8.0.0), matching the current DEPRECATIONS id conventions - Use deep module imports per current main conventions - Drop the resurrected packages/ember/barrel.ts (deleted on main) - Drop the mixin-deprecation re-exports from the eager utils barrel Teach the side-effect probe that setDeprecation is metadata association setDeprecation(mixin, value) only writes onto its first argument, so when the mixin is file-local (as in @ember/object/evented) the association is unobservable unless the module's bindings are imported — same category as setClassicDecorator and friends. Restore Function-based signatures in internal event plumbing The Function -> ((...args: any[]) => void) narrowing in metal/meta only served the internal signatures and forced casts at every call site. Keep the this-aware overloads on the public deprecated surface (@ember/object/events, RouterService) and leave the internal plumbing typed as it was on main, so the diff only carries the deprecation work. Inline Evented methods on framework classes instead of applying the mixin CoreView, Route, and EmberRouter now define on/one/off/trigger/has as native class methods delegating to shared standalone implementations (metal/lib/evented-methods), the same shape RouterService already uses. The framework no longer applies the deprecated Evented mixin anywhere, which removes the machinery that existed only to silence internal applications: - utils/lib/mixin-deprecation.ts (setDeprecation/findDeprecation/ disableDeprecations) and the Mixin#reopen DEBUG hook are gone; the deprecation is communicated by the methods themselves. - CoreView's init-time trigger/has swap hack is unnecessary now that the overrides are ordinary class methods. - @ember/object/evented no longer has a top-level setDeprecation call, so the module is side-effect free again and the tree-shakability probe no longer needs to know about setDeprecation. Tests that expected the definition-time "Evented is deprecated" warning now create their objects without a wrapper; method-call deprecations are unchanged. Keep Evented.detect true for framework classes CoreView, Route, and EmberRouter no longer apply the Evented mixin, but their instances still provide its methods. Record Evented in each prototype's meta from a class static block so Mixin#detect keeps answering true for instances and subclasses, exactly as it did when the mixin was applied. --- package.json | 1 + .../@ember/-internals/deprecations/index.ts | 7 + .../glimmer/lib/component-managers/curly.ts | 27 +- .../glimmer/lib/component-managers/root.ts | 5 +- .../@ember/-internals/glimmer/lib/renderer.ts | 3 +- .../lib/utils/curly-component-state-bucket.ts | 5 +- .../integration/components/append-test.js | 96 ++++-- .../components/curly-components-test.js | 85 +++-- .../integration/components/life-cycle-test.js | 3 +- .../integration/event-dispatcher-test.js | 21 +- .../tests/integration/syntax/each-test.js | 8 +- .../-internals/metal/lib/evented-methods.ts | 64 ++++ .../@ember/-internals/metal/lib/events.ts | 7 + .../-internals/metal/tests/computed_test.js | 4 +- .../-internals/metal/tests/events_test.js | 99 ++++-- packages/@ember/-internals/views/index.ts | 1 + .../views/lib/views/core-view-utils.ts | 14 + .../-internals/views/lib/views/core_view.ts | 73 ++-- .../-internals/views/lib/views/states.ts | 7 +- packages/@ember/object/evented.ts | 27 +- packages/@ember/object/events.ts | 79 ++++- packages/@ember/object/mixin.ts | 2 +- .../object/tests/es-compatibility-test.js | 70 ++-- packages/@ember/object/tests/evented_test.js | 48 ++- packages/@ember/object/tests/events_test.js | 311 ++++++++++++++---- packages/@ember/routing/route.ts | 64 +++- packages/@ember/routing/router-service.ts | 115 ++++++- packages/@ember/routing/router.ts | 68 +++- .../tests/routing/decoupled_basic_test.js | 50 ++- .../@ember/routing-test/router-service.ts | 2 +- 30 files changed, 1080 insertions(+), 286 deletions(-) create mode 100644 packages/@ember/-internals/metal/lib/evented-methods.ts create mode 100644 packages/@ember/-internals/views/lib/views/core-view-utils.ts diff --git a/package.json b/package.json index dbe510a6724..52ed8c88fb0 100644 --- a/package.json +++ b/package.json @@ -222,6 +222,7 @@ "@ember/-internals/views/lib/mixins/action_support.js": "ember-source/@ember/-internals/views/lib/mixins/action_support.js", "@ember/-internals/views/lib/system/event_dispatcher.js": "ember-source/@ember/-internals/views/lib/system/event_dispatcher.js", "@ember/-internals/views/lib/system/utils.js": "ember-source/@ember/-internals/views/lib/system/utils.js", + "@ember/-internals/views/lib/views/core-view-utils.js": "ember-source/@ember/-internals/views/lib/views/core-view-utils.js", "@ember/-internals/views/lib/views/core_view.js": "ember-source/@ember/-internals/views/lib/views/core_view.js", "@ember/-internals/views/lib/views/states.js": "ember-source/@ember/-internals/views/lib/views/states.js", "@ember/application/index.js": "ember-source/@ember/application/index.js", diff --git a/packages/@ember/-internals/deprecations/index.ts b/packages/@ember/-internals/deprecations/index.ts index 9625d864bc5..0b0c42429ce 100644 --- a/packages/@ember/-internals/deprecations/index.ts +++ b/packages/@ember/-internals/deprecations/index.ts @@ -126,6 +126,13 @@ export const DEPRECATIONS = { until: '8.0.0', url: 'https://deprecations.emberjs.com/id/deprecate-target-action-support', }), + DEPRECATE_EVENTED: deprecation({ + id: 'deprecate-evented', + for: 'ember-source', + since: { available: '7.3.0' }, + until: '8.0.0', + url: 'https://deprecations.emberjs.com/id/deprecate-evented', + }), }; export function deprecateUntil(message: string, deprecation: DeprecationObject) { diff --git a/packages/@ember/-internals/glimmer/lib/component-managers/curly.ts b/packages/@ember/-internals/glimmer/lib/component-managers/curly.ts index a79ef3392af..c7e1027ca35 100644 --- a/packages/@ember/-internals/glimmer/lib/component-managers/curly.ts +++ b/packages/@ember/-internals/glimmer/lib/component-managers/curly.ts @@ -10,6 +10,7 @@ import { setElementView, setViewElement, } from '@ember/-internals/views/lib/system/utils'; +import { sendCoreViewEvent } from '@ember/-internals/views/lib/views/core-view-utils'; import type { Nullable } from '@ember/-internals/utility-types'; import { assert, debugFreeze } from '@ember/debug'; import { _instrumentStart } from '@ember/instrumentation'; @@ -318,20 +319,20 @@ export default class CurlyComponentManager addChildView(parentView, component); } - component.trigger('didReceiveAttrs'); + sendCoreViewEvent(component, 'didReceiveAttrs'); let hasWrappedElement = component.tagName !== ''; // We usually do this in the `didCreateElement`, but that hook doesn't fire for tagless components if (!hasWrappedElement) { if (isInteractive) { - component.trigger('willRender'); + sendCoreViewEvent(component, 'willRender'); } component._transitionTo('hasElement'); if (isInteractive) { - component.trigger('willInsertElement'); + sendCoreViewEvent(component, 'willInsertElement'); } } @@ -355,7 +356,7 @@ export default class CurlyComponentManager } if (isInteractive && hasWrappedElement) { - component.trigger('willRender'); + sendCoreViewEvent(component, 'willRender'); } endUntrackFrame(); @@ -420,7 +421,7 @@ export default class CurlyComponentManager if (isInteractive) { beginUntrackFrame(); - component.trigger('willInsertElement'); + sendCoreViewEvent(component, 'willInsertElement'); endUntrackFrame(); } } @@ -433,8 +434,8 @@ export default class CurlyComponentManager didCreate({ component, isInteractive }: ComponentStateBucket): void { if (isInteractive) { component._transitionTo('inDOM'); - component.trigger('didInsertElement'); - component.trigger('didRender'); + sendCoreViewEvent(component, 'didInsertElement'); + sendCoreViewEvent(component, 'didRender'); } } @@ -456,13 +457,13 @@ export default class CurlyComponentManager component.setProperties(props); component[IS_DISPATCHING_ATTRS] = false; - component.trigger('didUpdateAttrs'); - component.trigger('didReceiveAttrs'); + sendCoreViewEvent(component, 'didUpdateAttrs'); + sendCoreViewEvent(component, 'didReceiveAttrs'); } if (isInteractive) { - component.trigger('willUpdate'); - component.trigger('willRender'); + sendCoreViewEvent(component, 'willUpdate'); + sendCoreViewEvent(component, 'willRender'); } endUntrackFrame(); @@ -477,8 +478,8 @@ export default class CurlyComponentManager didUpdate({ component, isInteractive }: ComponentStateBucket): void { if (isInteractive) { - component.trigger('didUpdate'); - component.trigger('didRender'); + sendCoreViewEvent(component, 'didUpdate'); + sendCoreViewEvent(component, 'didRender'); } } diff --git a/packages/@ember/-internals/glimmer/lib/component-managers/root.ts b/packages/@ember/-internals/glimmer/lib/component-managers/root.ts index 2ca09c5e158..abcb5383a05 100644 --- a/packages/@ember/-internals/glimmer/lib/component-managers/root.ts +++ b/packages/@ember/-internals/glimmer/lib/component-managers/root.ts @@ -21,6 +21,7 @@ import CurlyComponentManager, { initialRenderInstrumentDetails, processComponentInitializationAssertions, } from './curly'; +import { sendCoreViewEvent } from '@ember/-internals/views/lib/views/core-view-utils'; class RootComponentManager extends CurlyComponentManager { component: Component; @@ -48,13 +49,13 @@ class RootComponentManager extends CurlyComponentManager { // We usually do this in the `didCreateElement`, but that hook doesn't fire for tagless components if (!hasWrappedElement) { if (isInteractive) { - component.trigger('willRender'); + sendCoreViewEvent(component, 'willRender'); } component._transitionTo('hasElement'); if (isInteractive) { - component.trigger('willInsertElement'); + sendCoreViewEvent(component, 'willInsertElement'); } } diff --git a/packages/@ember/-internals/glimmer/lib/renderer.ts b/packages/@ember/-internals/glimmer/lib/renderer.ts index e533c4042ba..c404404ecd9 100644 --- a/packages/@ember/-internals/glimmer/lib/renderer.ts +++ b/packages/@ember/-internals/glimmer/lib/renderer.ts @@ -3,6 +3,7 @@ import type { InternalOwner } from '@ember/-internals/owner'; import { getOwner } from '@ember/-internals/owner'; import { guidFor } from '@ember/-internals/utils/lib/guid'; import { getViewElement, getViewId } from '@ember/-internals/views/lib/system/utils'; +import { sendCoreViewEvent } from '@ember/-internals/views/lib/views/core-view-utils'; import { assert } from '@ember/debug'; import { associateDestroyableChild, @@ -309,7 +310,7 @@ export class Renderer extends BaseRenderer { this.cleanupRootFor(view); if (this.state.isInteractive) { - view.trigger('didDestroyElement'); + sendCoreViewEvent(view, 'didDestroyElement'); } } diff --git a/packages/@ember/-internals/glimmer/lib/utils/curly-component-state-bucket.ts b/packages/@ember/-internals/glimmer/lib/utils/curly-component-state-bucket.ts index 49907136c01..1780214877f 100644 --- a/packages/@ember/-internals/glimmer/lib/utils/curly-component-state-bucket.ts +++ b/packages/@ember/-internals/glimmer/lib/utils/curly-component-state-bucket.ts @@ -3,6 +3,7 @@ import { clearViewElement, getViewElement, } from '@ember/-internals/views/lib/system/utils'; +import { sendCoreViewEvent } from '@ember/-internals/views/lib/views/core-view-utils'; import { registerDestructor } from '@glimmer/destroyable'; import type { CapturedNamedArguments } from '@glimmer/interfaces'; import type { Reference } from '@glimmer/reference/lib/reference'; @@ -52,8 +53,8 @@ export default class ComponentStateBucket { if (isInteractive) { beginUntrackFrame(); - component.trigger('willDestroyElement'); - component.trigger('willClearRender'); + sendCoreViewEvent(component, 'willDestroyElement'); + sendCoreViewEvent(component, 'willClearRender'); endUntrackFrame(); let element = getViewElement(component); diff --git a/packages/@ember/-internals/glimmer/tests/integration/components/append-test.js b/packages/@ember/-internals/glimmer/tests/integration/components/append-test.js index 8c00b604cc9..fd5a5030aa4 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/components/append-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/components/append-test.js @@ -1,4 +1,11 @@ -import { moduleFor, RenderingTestCase, strip, runTask } from 'internal-test-helpers'; +import { + moduleFor, + RenderingTestCase, + strip, + runTask, + expectDeprecation, +} from 'internal-test-helpers'; +import { DEPRECATIONS } from '../../../../deprecations'; import { set } from '@ember/object'; import { setComponentTemplate } from '@glimmer/manager'; @@ -57,7 +64,15 @@ class AbstractAppendTest extends RenderingTestCase { } componentsByName[name] = this; pushHook('init'); - this.on('init', () => pushHook('on(init)')); + if (!DEPRECATIONS.DEPRECATE_EVENTED.isRemoved) { + expectDeprecation( + () => { + this.on('init', () => pushHook('on(init)')); + }, + /Evented#on` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); + } } didReceiveAttrs() { @@ -145,26 +160,28 @@ class AbstractAppendTest extends RenderingTestCase { { parentValue: 1, childValue: 1, foo: 'zomg', show: true } ); - assert.deepEqual( - hooks, - [ - ['x-parent', 'init'], - ['x-parent', 'on(init)'], - ['x-parent', 'didReceiveAttrs'], - ['x-parent', 'willRender'], - ['x-parent', 'willInsertElement'], - ['x-child', 'init'], - ['x-child', 'on(init)'], - ['x-child', 'didReceiveAttrs'], - ['x-child', 'willRender'], - ['x-child', 'willInsertElement'], - ['x-child', 'didInsertElement'], - ['x-child', 'didRender'], - ['x-parent', 'didInsertElement'], - ['x-parent', 'didRender'], - ], - 'creation of x-parent' - ); + let expectedCreationHooks = [ + ['x-parent', 'init'], + ['x-parent', 'on(init)'], + ['x-parent', 'didReceiveAttrs'], + ['x-parent', 'willRender'], + ['x-parent', 'willInsertElement'], + ['x-child', 'init'], + ['x-child', 'on(init)'], + ['x-child', 'didReceiveAttrs'], + ['x-child', 'willRender'], + ['x-child', 'willInsertElement'], + ['x-child', 'didInsertElement'], + ['x-child', 'didRender'], + ['x-parent', 'didInsertElement'], + ['x-parent', 'didRender'], + ]; + + if (DEPRECATIONS.DEPRECATE_EVENTED.isRemoved) { + expectedCreationHooks = expectedCreationHooks.filter(([, hook]) => hook !== 'on(init)'); + } + + assert.deepEqual(hooks, expectedCreationHooks, 'creation of x-parent'); hooks.length = 0; runTask(() => this.rerender()); @@ -298,7 +315,15 @@ class AbstractAppendTest extends RenderingTestCase { } componentsByName[name] = this; pushHook('init'); - this.on('init', () => pushHook('on(init)')); + if (!DEPRECATIONS.DEPRECATE_EVENTED.isRemoved) { + expectDeprecation( + () => { + this.on('init', () => pushHook('on(init)')); + }, + /Evented#on` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); + } } didReceiveAttrs() { @@ -381,15 +406,18 @@ class AbstractAppendTest extends RenderingTestCase { XParent = this.owner.factoryFor('component:x-parent'); this.component = XParent.create({ foo: 'zomg' }); - - assert.deepEqual( - hooks, - [ - ['x-parent', 'init'], - ['x-parent', 'on(init)'], - ], - 'creation of x-parent' - ); + if (DEPRECATIONS.DEPRECATE_EVENTED.isRemoved) { + assert.deepEqual(hooks, [['x-parent', 'init']], 'creation of x-parent'); + } else { + assert.deepEqual( + hooks, + [ + ['x-parent', 'init'], + ['x-parent', 'on(init)'], + ], + 'creation of x-parent' + ); + } hooks.length = 0; @@ -401,7 +429,7 @@ class AbstractAppendTest extends RenderingTestCase { ['x-parent', 'willInsertElement'], ['x-child', 'init'], - ['x-child', 'on(init)'], + !DEPRECATIONS.DEPRECATE_EVENTED.isRemoved && ['x-child', 'on(init)'], ['x-child', 'didReceiveAttrs'], ['x-child', 'willRender'], ['x-child', 'willInsertElement'], @@ -411,7 +439,7 @@ class AbstractAppendTest extends RenderingTestCase { ['x-parent', 'didInsertElement'], ['x-parent', 'didRender'], - ], + ].filter(Boolean), 'appending of x-parent' ); diff --git a/packages/@ember/-internals/glimmer/tests/integration/components/curly-components-test.js b/packages/@ember/-internals/glimmer/tests/integration/components/curly-components-test.js index 5a1b7771b20..41420b751bf 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/components/curly-components-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/components/curly-components-test.js @@ -7,7 +7,10 @@ import { equalsElement, runTask, runLoopSettled, + expectDeprecation, + testUnless, } from 'internal-test-helpers'; +import { DEPRECATIONS } from '../../../../deprecations'; import { action } from '@ember/object'; import { run } from '@ember/runloop'; @@ -3440,48 +3443,60 @@ moduleFor( runTask(() => set(this.context, 'foo', 5)); } - ['@test triggering an event only attempts to invoke an identically named method, if it actually is a function (GH#15228)']( + [`${testUnless(DEPRECATIONS.DEPRECATE_EVENTED.isRemoved)} @test triggering an event only attempts to invoke an identically named method, if it actually is a function (GH#15228)`]( assert ) { - assert.expect(3); + assert.expect(5); let payload = ['arbitrary', 'event', 'data']; - this.owner.register( - 'component:evented-component', - Component.extend({ - someTruthyProperty: true, - - init() { - this._super(...arguments); - this.trigger('someMethod', ...payload); - this.trigger('someTruthyProperty', ...payload); - }, + expectDeprecation( + () => { + this.owner.register( + 'component:evented-component', + Component.extend({ + someTruthyProperty: true, + + init() { + this._super(...arguments); + expectDeprecation( + () => { + this.trigger('someMethod', ...payload); + this.trigger('someTruthyProperty', ...payload); + }, + /Evented#trigger` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); + }, - someMethod(...data) { - assert.deepEqual( - data, - payload, - 'the method `someMethod` should be called, when `someMethod` is triggered' - ); - }, + someMethod(...data) { + assert.deepEqual( + data, + payload, + 'the method `someMethod` should be called, when `someMethod` is triggered' + ); + }, - listenerForSomeMethod: on('someMethod', function (...data) { - assert.deepEqual( - data, - payload, - 'the listener `listenerForSomeMethod` should be called, when `someMethod` is triggered' - ); - }), - - listenerForSomeTruthyProperty: on('someTruthyProperty', function (...data) { - assert.deepEqual( - data, - payload, - 'the listener `listenerForSomeTruthyProperty` should be called, when `someTruthyProperty` is triggered' - ); - }), - }) + listenerForSomeMethod: on('someMethod', function (...data) { + assert.deepEqual( + data, + payload, + 'the listener `listenerForSomeMethod` should be called, when `someMethod` is triggered' + ); + }), + + listenerForSomeTruthyProperty: on('someTruthyProperty', function (...data) { + assert.deepEqual( + data, + payload, + 'the listener `listenerForSomeTruthyProperty` should be called, when `someTruthyProperty` is triggered' + ); + }), + }) + ); + }, + /`on\(\)` event decorator is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled ); this.render(`{{evented-component}}`); diff --git a/packages/@ember/-internals/glimmer/tests/integration/components/life-cycle-test.js b/packages/@ember/-internals/glimmer/tests/integration/components/life-cycle-test.js index 911f26a44e4..b989a47b24a 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/components/life-cycle-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/components/life-cycle-test.js @@ -8,6 +8,7 @@ import { precompileTemplate } from '@ember/template-compilation'; import { setComponentTemplate } from '@glimmer/manager'; import { Component } from '../../utils/helpers'; +import { addListener } from '@ember/-internals/metal'; class LifeCycleHooksTest extends RenderingTestCase { constructor() { @@ -176,7 +177,7 @@ class LifeCycleHooksTest extends RenderingTestCase { assertNoElement('init', this); assertState('init', 'preRender', this); - this.on('init', () => pushHook('on(init)')); + addListener(this, 'init', () => pushHook('on(init)')); schedule('afterRender', () => { this.isInitialRender = false; diff --git a/packages/@ember/-internals/glimmer/tests/integration/event-dispatcher-test.js b/packages/@ember/-internals/glimmer/tests/integration/event-dispatcher-test.js index 9b496a0ef36..bfb6816e210 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/event-dispatcher-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/event-dispatcher-test.js @@ -1,4 +1,11 @@ -import { moduleFor, RenderingTestCase, runTask } from 'internal-test-helpers'; +import { + expectDeprecation, + moduleFor, + RenderingTestCase, + runTask, + testUnless, +} from 'internal-test-helpers'; +import { DEPRECATIONS } from '../../../deprecations'; import { Component } from '../utils/helpers'; import { _getCurrentRunLoop } from '@ember/runloop'; @@ -149,7 +156,9 @@ moduleFor( } } - ['@test event listeners are called when event is triggered'](assert) { + [`${testUnless(DEPRECATIONS.DEPRECATE_EVENTED.isRemoved)} @test event listeners are called when event is triggered`]( + assert + ) { let receivedEvent; let browserEvent; @@ -160,7 +169,13 @@ moduleFor( init() { super.init(); Object.keys(SUPPORTED_EMBER_EVENTS).forEach((browserEvent) => { - this.on(SUPPORTED_EMBER_EVENTS[browserEvent], (event) => (receivedEvent = event)); + expectDeprecation( + () => { + this.on(SUPPORTED_EMBER_EVENTS[browserEvent], (event) => (receivedEvent = event)); + }, + /Evented#on` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); }); } } diff --git a/packages/@ember/-internals/glimmer/tests/integration/syntax/each-test.js b/packages/@ember/-internals/glimmer/tests/integration/syntax/each-test.js index c1017feeaa1..ac02dd5df23 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/syntax/each-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/syntax/each-test.js @@ -1,6 +1,6 @@ import { moduleFor, RenderingTestCase, applyMixins, strip, runTask } from 'internal-test-helpers'; -import { notifyPropertyChange, on } from '@ember/-internals/metal'; +import { notifyPropertyChange } from '@ember/-internals/metal'; import { get, set, computed } from '@ember/object'; import { A as emberA } from '@ember/array'; import ArrayProxy from '@ember/array/proxy'; @@ -1115,9 +1115,11 @@ moduleFor( createList(items) { let wrapped = emberA(items); let proxy = ArrayProxy.extend({ - setup: on('init', function () { + init: function () { + this._super(...arguments); + this.set('content', emberA(wrapped)); - }), + }, }).create(); return { list: proxy, delegate: wrapped }; diff --git a/packages/@ember/-internals/metal/lib/evented-methods.ts b/packages/@ember/-internals/metal/lib/evented-methods.ts new file mode 100644 index 00000000000..59643a98a58 --- /dev/null +++ b/packages/@ember/-internals/metal/lib/evented-methods.ts @@ -0,0 +1,64 @@ +import { addListener, removeListener, hasListeners, sendEvent } from './events'; +import { DEPRECATIONS, deprecateUntil } from '@ember/-internals/deprecations'; + +/* + Standalone implementations of the deprecated `Evented` methods. They are + shared between the `Evented` mixin and the framework classes (`Component`, + `Route`, `EmberRouter`) that historically included it, so that the framework + itself no longer applies the deprecated mixin. +*/ + +export function eventedOn( + obj: object, + name: string, + target: object | Function | null, + method?: Function | PropertyKey +): void { + deprecateUntil( + '`Evented#on` is deprecated. Use native JavaScript events or a dedicated event library instead.', + DEPRECATIONS.DEPRECATE_EVENTED + ); + addListener(obj, name, target, method); +} + +export function eventedOne( + obj: object, + name: string, + target: object | Function | null, + method?: Function | PropertyKey +): void { + deprecateUntil( + '`Evented#one` is deprecated. Use native JavaScript events or a dedicated event library instead.', + DEPRECATIONS.DEPRECATE_EVENTED + ); + addListener(obj, name, target, method, true); +} + +export function eventedTrigger(obj: object, name: string, args: any[]): void { + deprecateUntil( + '`Evented#trigger` is deprecated. Use native JavaScript events or a dedicated event library instead.', + DEPRECATIONS.DEPRECATE_EVENTED + ); + sendEvent(obj, name, args); +} + +export function eventedOff( + obj: object, + name: string, + target: object | Function | null, + method?: string | Function +): void { + deprecateUntil( + '`Evented#off` is deprecated. Use native JavaScript events or a dedicated event library instead.', + DEPRECATIONS.DEPRECATE_EVENTED + ); + removeListener(obj, name, target, method); +} + +export function eventedHas(obj: object, name: string): boolean { + deprecateUntil( + '`Evented#has` is deprecated. Use native JavaScript events or a dedicated event library instead.', + DEPRECATIONS.DEPRECATE_EVENTED + ); + return hasListeners(obj, name); +} diff --git a/packages/@ember/-internals/metal/lib/events.ts b/packages/@ember/-internals/metal/lib/events.ts index 56668b9bc9b..106cfbea16f 100644 --- a/packages/@ember/-internals/metal/lib/events.ts +++ b/packages/@ember/-internals/metal/lib/events.ts @@ -6,6 +6,7 @@ import { meta as metaFor, peekMeta } from '@ember/-internals/meta/lib/meta'; import { setListeners } from '@ember/-internals/utils/lib/super'; import type { AnyFn } from '@ember/-internals/utility-types'; import { assert } from '@ember/debug'; +import { DEPRECATIONS, deprecateUntil } from '@ember/-internals/deprecations'; /* The event system uses a series of nested hashes to store listeners on an @@ -199,6 +200,7 @@ export function hasListeners(obj: object, eventName: string): boolean { @method on @static + @deprecated Use native JavaScript events or a dedicated event library instead. @for @ember/object/evented @param {String} eventNames* @param {Function} func @@ -206,6 +208,11 @@ export function hasListeners(obj: object, eventName: string): boolean { @public */ export function on(...args: [...eventNames: string[], func: T]): T { + deprecateUntil( + 'The `on()` event decorator is deprecated. Use native JavaScript events or a dedicated event library instead.', + DEPRECATIONS.DEPRECATE_EVENTED + ); + let func = args.pop(); let events = args as string[]; diff --git a/packages/@ember/-internals/metal/tests/computed_test.js b/packages/@ember/-internals/metal/tests/computed_test.js index 516da6905f6..1a20fc2166c 100644 --- a/packages/@ember/-internals/metal/tests/computed_test.js +++ b/packages/@ember/-internals/metal/tests/computed_test.js @@ -984,16 +984,16 @@ moduleFor( class LazyObject { value = 123; + /* eslint-disable no-dupe-class-members */ @computed('_value') - // eslint-disable-next-line no-dupe-class-members get value() { return get(this, '_value'); } - // eslint-disable-next-line no-dupe-class-members set value(value) { set(this, '_value', value); } + /* eslint-enable no-dupe-class-members */ static create() { obj = new LazyObject(); diff --git a/packages/@ember/-internals/metal/tests/events_test.js b/packages/@ember/-internals/metal/tests/events_test.js index 109792c39b7..c50f3e83bd3 100644 --- a/packages/@ember/-internals/metal/tests/events_test.js +++ b/packages/@ember/-internals/metal/tests/events_test.js @@ -1,6 +1,7 @@ import { on, addListener, removeListener, sendEvent, hasListeners } from '..'; import Mixin from '@ember/object/mixin'; -import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; +import { moduleFor, AbstractTestCase, expectDeprecation, testUnless } from 'internal-test-helpers'; +import { DEPRECATIONS } from '../../deprecations'; moduleFor( 'system/props/events_test', @@ -139,17 +140,26 @@ moduleFor( assert.equal(hasListeners(obj, 'event!'), true, 'has listeners'); } - ['@test a listener can be added as part of a mixin'](assert) { + [`${testUnless( + DEPRECATIONS.DEPRECATE_EVENTED.isRemoved + )} @test a listener can be added as part of a mixin`](assert) { let triggered = 0; - let MyMixin = Mixin.create({ - foo1: on('bar', function () { - triggered++; - }), - - foo2: on('bar', function () { - triggered++; - }), - }); + let MyMixin; + expectDeprecation( + () => { + MyMixin = Mixin.create({ + foo1: on('bar', function () { + triggered++; + }), + + foo2: on('bar', function () { + triggered++; + }), + }); + }, + /`on\(\)` event decorator is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); let obj = {}; MyMixin.apply(obj); @@ -158,32 +168,63 @@ moduleFor( assert.equal(triggered, 2, 'should invoke listeners'); } - [`@test 'on' asserts for invalid arguments`]() { + [`${testUnless( + DEPRECATIONS.DEPRECATE_EVENTED.isRemoved + )} @test 'on' asserts for invalid arguments`]() { expectAssertion(() => { - Mixin.create({ - foo1: on('bar'), - }); + expectDeprecation( + () => { + Mixin.create({ + foo1: on('bar'), + }); + }, + /`on\(\)` event decorator is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); }, 'on expects function as last argument'); expectAssertion(() => { - Mixin.create({ - foo1: on(function () {}), - }); + expectDeprecation( + () => { + Mixin.create({ + foo1: on(function () {}), + }); + }, + /`on\(\)` event decorator is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); }, 'on called without valid event names'); } - ['@test a listener added as part of a mixin may be overridden'](assert) { + [`${testUnless( + DEPRECATIONS.DEPRECATE_EVENTED.isRemoved + )} @test a listener added as part of a mixin may be overridden`](assert) { let triggered = 0; - let FirstMixin = Mixin.create({ - foo: on('bar', function () { - triggered++; - }), - }); - let SecondMixin = Mixin.create({ - foo: on('baz', function () { - triggered++; - }), - }); + let FirstMixin; + expectDeprecation( + () => { + FirstMixin = Mixin.create({ + foo: on('bar', function () { + triggered++; + }), + }); + }, + /`on\(\)` event decorator is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); + + let SecondMixin; + expectDeprecation( + () => { + SecondMixin = Mixin.create({ + foo: on('baz', function () { + triggered++; + }), + }); + }, + /`on\(\)` event decorator is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); let obj = {}; FirstMixin.apply(obj); diff --git a/packages/@ember/-internals/views/index.ts b/packages/@ember/-internals/views/index.ts index d68ea2baff8..a217d978b06 100644 --- a/packages/@ember/-internals/views/index.ts +++ b/packages/@ember/-internals/views/index.ts @@ -17,6 +17,7 @@ export { } from './lib/system/utils'; export { default as EventDispatcher } from './lib/system/event_dispatcher'; export { default as CoreView } from './lib/views/core_view'; +export { sendCoreViewEvent, hasCoreViewListener } from './lib/views/core-view-utils'; export { default as ActionSupport } from './lib/mixins/action_support'; export { MUTABLE_CELL } from './lib/compat/attrs'; export { default as ViewStates } from './lib/views/states'; diff --git a/packages/@ember/-internals/views/lib/views/core-view-utils.ts b/packages/@ember/-internals/views/lib/views/core-view-utils.ts new file mode 100644 index 00000000000..dfc273e84bd --- /dev/null +++ b/packages/@ember/-internals/views/lib/views/core-view-utils.ts @@ -0,0 +1,14 @@ +import { hasListeners, sendEvent } from '@ember/-internals/metal/lib/events'; +import type CoreView from './core_view'; + +export function sendCoreViewEvent(view: CoreView, name: string, args: any[] = []) { + sendEvent(view, name, args); + let method = (view as any)[name]; + if (typeof method === 'function') { + return method.apply(view, args); + } +} + +export function hasCoreViewListener(view: CoreView, name: string) { + return typeof (view as any)[name] === 'function' || hasListeners(view, name); +} diff --git a/packages/@ember/-internals/views/lib/views/core_view.ts b/packages/@ember/-internals/views/lib/views/core_view.ts index ceb6c0cfa8f..2f0e757da06 100644 --- a/packages/@ember/-internals/views/lib/views/core_view.ts +++ b/packages/@ember/-internals/views/lib/views/core_view.ts @@ -1,5 +1,13 @@ import type { Renderer, View } from '@ember/-internals/glimmer/lib/renderer'; +import { meta as metaFor } from '@ember/-internals/meta/lib/meta'; import inject from '@ember/-internals/metal/lib/injected_property'; +import { + eventedOn, + eventedOne, + eventedTrigger, + eventedOff, + eventedHas, +} from '@ember/-internals/metal/lib/evented-methods'; import ActionHandler from '@ember/-internals/runtime/lib/mixins/action_handler'; import Evented from '@ember/object/evented'; import { FrameworkObject } from '@ember/object/-internals'; @@ -23,8 +31,14 @@ import states from './states'; @private */ -interface CoreView extends Evented, ActionHandler, View {} -class CoreView extends FrameworkObject.extend(Evented, ActionHandler) { +interface CoreView extends ActionHandler, View {} +class CoreView extends FrameworkObject.extend(ActionHandler) { + static { + // The deprecated Evented mixin is no longer applied, but instances still + // provide its methods, so `Evented.detect` must keep returning true. + metaFor(this.prototype).addMixin(Evented); + } + isView = true; declare _states: typeof states; @@ -32,9 +46,6 @@ class CoreView extends FrameworkObject.extend(Evented, ActionHandler) { declare _state: keyof typeof states; declare _currentState: ViewState; - _superTrigger?: Evented['trigger']; - _superHas?: Evented['has']; - /** If the view is currently inserted into the DOM of a parent view, this property will point to the parent of the view. @@ -49,15 +60,6 @@ class CoreView extends FrameworkObject.extend(Evented, ActionHandler) { init(properties: object | undefined) { super.init(properties); - // Handle methods from Evented - // The native class inheritance will not work for mixins. To work around this, - // we copy the existing trigger and has methods provided by the mixin and swap in the - // new ones from our class. - this._superTrigger = this.trigger; - this.trigger = this._trigger; - this._superHas = this.has; - this.has = this._has; - this.parentView ??= null; this._state = 'preRender'; @@ -74,6 +76,28 @@ class CoreView extends FrameworkObject.extend(Evented, ActionHandler) { return hash; } + on( + name: string, + target: Target, + method: string | ((this: Target, ...args: any[]) => void) + ): this; + on(name: string, method: ((...args: any[]) => void) | string): this; + on(name: string, target: any, method?: any) { + eventedOn(this, name, target, method); + return this; + } + + one( + name: string, + target: Target, + method: string | ((this: Target, ...args: any[]) => void) + ): this; + one(name: string, method: string | ((...args: any[]) => void)): this; + one(name: string, target: any, method?: any) { + eventedOne(this, name, target, method); + return this; + } + /** Override the default event firing from `Evented` to also call methods with the given name. @@ -82,18 +106,27 @@ class CoreView extends FrameworkObject.extend(Evented, ActionHandler) { @param name {String} @private */ - // Changed to `trigger` on init - _trigger(name: string, ...args: any[]) { - this._superTrigger!(name, ...args); + trigger(name: string, ...args: any[]) { + eventedTrigger(this, name, args); let method = (this as any)[name]; if (typeof method === 'function') { return method.apply(this, args); } } - // Changed to `has` on init - _has(name: string) { - return typeof (this as any)[name] === 'function' || this._superHas!(name); + off( + name: string, + target: Target, + method: string | ((this: Target, ...args: any[]) => void) + ): this; + off(name: string, method: string | ((...args: any[]) => void)): this; + off(name: string, target: any, method?: any) { + eventedOff(this, name, target, method); + return this; + } + + has(name: string) { + return typeof (this as any)[name] === 'function' || eventedHas(this, name); } static isViewFactory = true; diff --git a/packages/@ember/-internals/views/lib/views/states.ts b/packages/@ember/-internals/views/lib/views/states.ts index aba69ba106b..3dead388b0f 100644 --- a/packages/@ember/-internals/views/lib/views/states.ts +++ b/packages/@ember/-internals/views/lib/views/states.ts @@ -4,6 +4,7 @@ import { assert } from '@ember/debug'; import { flaggedInstrument } from '@ember/instrumentation'; import { join } from '@ember/runloop'; import { DEBUG } from '@glimmer/env'; +import { hasCoreViewListener, sendCoreViewEvent } from './core-view-utils'; export interface ViewState { enter?(view: Component): void; @@ -45,11 +46,13 @@ const HAS_ELEMENT: Readonly = Object.freeze({ // Handle events from `Ember.EventDispatcher` handleEvent(view: Component, eventName: string, event: Event) { - if (view.has(eventName)) { + if (hasCoreViewListener(view, eventName)) { // Handler should be able to re-dispatch events, so we don't // preventDefault or stopPropagation. return flaggedInstrument(`interaction.${eventName}`, { event, view }, () => { - return join(view, view.trigger, eventName, event); + return join(() => { + return sendCoreViewEvent(view, eventName, [event]); + }); }); } else { return true; // continue event propagation diff --git a/packages/@ember/object/evented.ts b/packages/@ember/object/evented.ts index 097d274d4bf..a52280a8fd0 100644 --- a/packages/@ember/object/evented.ts +++ b/packages/@ember/object/evented.ts @@ -1,9 +1,10 @@ import { - addListener, - removeListener, - hasListeners, - sendEvent, -} from '@ember/-internals/metal/lib/events'; + eventedOn, + eventedOne, + eventedTrigger, + eventedOff, + eventedHas, +} from '@ember/-internals/metal/lib/evented-methods'; import Mixin from '@ember/object/mixin'; export { on } from '@ember/-internals/metal/lib/events'; @@ -51,6 +52,7 @@ export { on } from '@ember/-internals/metal/lib/events'; @class Evented @public + @deprecated Use native JavaScript events or a dedicated event library instead. */ interface Evented { /** @@ -68,6 +70,7 @@ interface Evented { parameter is used the callback method becomes the third argument. @method on + @deprecated Use native JavaScript events or a dedicated event library instead. @param {String} name The name of the event @param {Object} [target] The "this" binding for the callback @param {Function|String} method A function or the name of a function to be called on `target` @@ -90,6 +93,7 @@ interface Evented { becomes the third argument. @method one + @deprecated Use native JavaScript events or a dedicated event library instead. @param {String} name The name of the event @param {Object} [target] The "this" binding for the callback @param {Function|String} method A function or the name of a function to be called on `target` @@ -118,6 +122,7 @@ interface Evented { ``` @method trigger + @deprecated Use native JavaScript events or a dedicated event library instead. @param {String} name The name of the event @param {Object...} args Optional arguments to pass on @public @@ -127,6 +132,7 @@ interface Evented { Cancels subscription for given name, target, and method. @method off + @deprecated Use native JavaScript events or a dedicated event library instead. @param {String} name The name of the event @param {Object} target The target of the subscription @param {Function|String} method The function or the name of a function of the subscription @@ -143,6 +149,7 @@ interface Evented { Checks to see if object has any subscriptions for named event. @method has + @deprecated Use native JavaScript events or a dedicated event library instead. @param {String} name The name of the event @return {Boolean} does the object have a subscription for event @public @@ -151,26 +158,26 @@ interface Evented { } const Evented = Mixin.create({ on(name: string, target: object, method?: string | Function) { - addListener(this, name, target, method); + eventedOn(this, name, target, method); return this; }, one(name: string, target: object, method?: string | Function) { - addListener(this, name, target, method, true); + eventedOne(this, name, target, method); return this; }, trigger(name: string, ...args: any[]) { - sendEvent(this, name, args); + eventedTrigger(this, name, args); }, off(name: string, target: object, method?: string | Function) { - removeListener(this, name, target, method); + eventedOff(this, name, target, method); return this; }, has(name: string) { - return hasListeners(this, name); + return eventedHas(this, name); }, }); diff --git a/packages/@ember/object/events.ts b/packages/@ember/object/events.ts index 66224dac13f..9189f61882b 100644 --- a/packages/@ember/object/events.ts +++ b/packages/@ember/object/events.ts @@ -1 +1,78 @@ -export { addListener, removeListener, sendEvent } from '@ember/-internals/metal/lib/events'; +import { + addListener as originalAddListener, + removeListener as originalRemoveListener, + sendEvent as originalSendEvent, +} from '@ember/-internals/metal/lib/events'; +import { DEPRECATIONS, deprecateUntil } from '@ember/-internals/deprecations'; + +export function addListener( + obj: object, + eventName: string, + target: Target, + method: PropertyKey | ((this: Target, ...args: any[]) => void), + once?: boolean, + sync?: boolean +): void; +export function addListener( + obj: object, + eventName: string, + method: PropertyKey | ((...args: any[]) => void) +): void; +export function addListener( + obj: object, + eventName: string, + target: object | PropertyKey | ((...args: any[]) => void) | null, + method?: PropertyKey | ((...args: any[]) => void), + once?: boolean, + sync?: boolean +): void { + deprecateUntil( + 'Importing `addListener` from `@ember/object/events` is deprecated. Use native JavaScript events or a dedicated event library instead.', + DEPRECATIONS.DEPRECATE_EVENTED + ); + return originalAddListener( + obj, + eventName, + target as object | Function | null, + method, + once, + sync + ); +} + +export function removeListener( + obj: object, + eventName: string, + target: Target, + method: PropertyKey | ((this: Target, ...args: any[]) => void) +): void; +export function removeListener( + obj: object, + eventName: string, + method: PropertyKey | ((...args: any[]) => void) +): void; +export function removeListener( + obj: object, + eventName: string, + target: object | PropertyKey | ((...args: any[]) => void) | null, + method?: PropertyKey | ((...args: any[]) => void) +): void { + deprecateUntil( + 'Importing `removeListener` from `@ember/object/events` is deprecated. Use native JavaScript events or a dedicated event library instead.', + DEPRECATIONS.DEPRECATE_EVENTED + ); + return originalRemoveListener( + obj, + eventName, + target as object | Function | null, + method as string | Function + ); +} + +export const sendEvent = (...args: Parameters) => { + deprecateUntil( + 'Importing `sendEvent` from `@ember/object/events` is deprecated. Use native JavaScript events or a dedicated event library instead.', + DEPRECATIONS.DEPRECATE_EVENTED + ); + return originalSendEvent(...args); +}; diff --git a/packages/@ember/object/mixin.ts b/packages/@ember/object/mixin.ts index c76b57f7dbe..6509f6c0b6a 100644 --- a/packages/@ember/object/mixin.ts +++ b/packages/@ember/object/mixin.ts @@ -27,7 +27,7 @@ import { revalidateObservers, } from '@ember/-internals/metal/lib/observer'; import { defineDecorator, defineValue } from '@ember/-internals/metal/lib/properties'; -import { addListener, removeListener } from '@ember/object/events'; +import { addListener, removeListener } from '@ember/-internals/metal/lib/events'; const a_concat = Array.prototype.concat; const { isArray } = Array; diff --git a/packages/@ember/object/tests/es-compatibility-test.js b/packages/@ember/object/tests/es-compatibility-test.js index 33cff30a067..35372ca5f70 100644 --- a/packages/@ember/object/tests/es-compatibility-test.js +++ b/packages/@ember/object/tests/es-compatibility-test.js @@ -9,7 +9,14 @@ import { sendEvent, } from '@ember/-internals/metal'; import Mixin from '@ember/object/mixin'; -import { moduleFor, AbstractTestCase, runLoopSettled } from 'internal-test-helpers'; +import { + moduleFor, + AbstractTestCase, + runLoopSettled, + expectDeprecation, + testUnless, +} from 'internal-test-helpers'; +import { DEPRECATIONS } from '../../-internals/deprecations'; moduleFor( 'EmberObject ES Compatibility', @@ -276,37 +283,48 @@ moduleFor( SubEmberObject.metaForProperty('foo'); } - '@test observes / removeObserver on / removeListener interop'(assert) { + [`${testUnless(DEPRECATIONS.DEPRECATE_EVENTED.isRemoved)} @test observes / removeObserver on / removeListener interop`]( + assert + ) { let fooDidChangeBase = 0; let fooDidChangeA = 0; let fooDidChangeB = 0; let someEventBase = 0; let someEventA = 0; let someEventB = 0; - class A extends EmberObject.extend({ - fooDidChange: observer('foo', function () { - fooDidChangeBase++; - }), - - onSomeEvent: on('someEvent', function () { - someEventBase++; - }), - }) { - init() { - super.init(); - this.foo = 'bar'; - } - - fooDidChange() { - super.fooDidChange(); - fooDidChangeA++; - } - - onSomeEvent() { - super.onSomeEvent(); - someEventA++; - } - } + let A; + expectDeprecation( + () => { + A = class extends ( + EmberObject.extend({ + fooDidChange: observer('foo', function () { + fooDidChangeBase++; + }), + + onSomeEvent: on('someEvent', function () { + someEventBase++; + }), + }) + ) { + init() { + super.init(); + this.foo = 'bar'; + } + + fooDidChange() { + super.fooDidChange(); + fooDidChangeA++; + } + + onSomeEvent() { + super.onSomeEvent(); + someEventA++; + } + }; + }, + /`on\(\)` event decorator is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); class B extends A { fooDidChange() { diff --git a/packages/@ember/object/tests/evented_test.js b/packages/@ember/object/tests/evented_test.js index 03c65149b8a..6e2831d95da 100644 --- a/packages/@ember/object/tests/evented_test.js +++ b/packages/@ember/object/tests/evented_test.js @@ -1,12 +1,37 @@ import CoreObject from '@ember/object/core'; +import EmberObject from '@ember/object'; import EventedMixin from '@ember/object/evented'; -import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; +import Component from '@ember/component'; +import Route from '@ember/routing/route'; +import EmberRouter from '@ember/routing/router'; +import { moduleFor, AbstractTestCase, expectDeprecation, testUnless } from 'internal-test-helpers'; +import { DEPRECATIONS } from '../../-internals/deprecations'; moduleFor( 'Ember.Evented', class extends AbstractTestCase { - ['@test works properly on proxy-ish objects'](assert) { - let eventedProxyObj = class extends CoreObject.extend(EventedMixin) { + [`${testUnless( + DEPRECATIONS.DEPRECATE_EVENTED.isRemoved + )} @test Evented.detect returns true for framework classes that provide the Evented methods`]( + assert + ) { + assert.true(EventedMixin.detect(Component.prototype), 'Component'); + assert.true(EventedMixin.detect(Route.prototype), 'Route'); + assert.true(EventedMixin.detect(EmberRouter.prototype), 'EmberRouter'); + assert.true(EventedMixin.detect(class extends Route {}.prototype), 'Route subclass'); + + let route = Route.create(); + assert.true(EventedMixin.detect(route), 'Route instance'); + route.destroy(); + + assert.false(EventedMixin.detect(EmberObject.prototype), 'EmberObject'); + } + + [`${testUnless( + DEPRECATIONS.DEPRECATE_EVENTED.isRemoved + )} @test works properly on proxy-ish objects`](assert) { + let eventedProxyObj; + eventedProxyObj = class extends CoreObject.extend(EventedMixin) { unknownProperty() { return true; } @@ -14,8 +39,21 @@ moduleFor( let noop = function () {}; - eventedProxyObj.on('foo', noop); - eventedProxyObj.off('foo', noop); + expectDeprecation( + () => { + eventedProxyObj.on('foo', noop); + }, + /Evented#on` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); + + expectDeprecation( + () => { + eventedProxyObj.off('foo', noop); + }, + /Evented#off` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); assert.ok(true, 'An assertion was triggered'); } diff --git a/packages/@ember/object/tests/events_test.js b/packages/@ember/object/tests/events_test.js index fee30bd5bdb..c9c600060c6 100644 --- a/packages/@ember/object/tests/events_test.js +++ b/packages/@ember/object/tests/events_test.js @@ -1,29 +1,53 @@ import EmberObject from '@ember/object'; import Evented from '@ember/object/evented'; -import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; +import { moduleFor, AbstractTestCase, expectDeprecation, testUnless } from 'internal-test-helpers'; +import { DEPRECATIONS } from '../../-internals/deprecations'; moduleFor( 'Object events', class extends AbstractTestCase { - ['@test a listener can be added to an object'](assert) { + [`${testUnless( + DEPRECATIONS.DEPRECATE_EVENTED.isRemoved + )} @test a listener can be added to an object`](assert) { let count = 0; let F = function () { count++; }; - let obj = EmberObject.extend(Evented).create(); - - obj.on('event!', F); - obj.trigger('event!'); + let obj; + obj = EmberObject.extend(Evented).create(); + + expectDeprecation( + () => { + obj.on('event!', F); + }, + /Evented#on` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); + expectDeprecation( + () => { + obj.trigger('event!'); + }, + /Evented#trigger` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); assert.equal(count, 1, 'the event was triggered'); - obj.trigger('event!'); + expectDeprecation( + () => { + obj.trigger('event!'); + }, + /Evented#trigger` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); assert.equal(count, 2, 'the event was triggered'); } - ['@test a listener can be added and removed automatically the first time it is triggered']( + [`${testUnless( + DEPRECATIONS.DEPRECATE_EVENTED.isRemoved + )} @test a listener can be added and removed automatically the first time it is triggered`]( assert ) { let count = 0; @@ -31,77 +55,150 @@ moduleFor( count++; }; - let obj = EmberObject.extend(Evented).create(); - - obj.one('event!', F); - obj.trigger('event!'); + let obj; + obj = EmberObject.extend(Evented).create(); + + expectDeprecation( + () => { + obj.one('event!', F); + }, + /Evented#one` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); + + expectDeprecation( + () => { + obj.trigger('event!'); + }, + /Evented#trigger` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); assert.equal(count, 1, 'the event was triggered'); - obj.trigger('event!'); + expectDeprecation( + () => { + obj.trigger('event!'); + }, + /Evented#trigger` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); assert.equal(count, 1, 'the event was not triggered again'); } - ['@test triggering an event can have arguments'](assert) { + [`${testUnless( + DEPRECATIONS.DEPRECATE_EVENTED.isRemoved + )} @test triggering an event can have arguments`](assert) { let self, args; - let obj = EmberObject.extend(Evented).create(); - - obj.on('event!', function () { - args = [].slice.call(arguments); - self = this; - }); - - obj.trigger('event!', 'foo', 'bar'); + let obj; + obj = EmberObject.extend(Evented).create(); + + expectDeprecation( + () => { + obj.on('event!', function () { + args = [].slice.call(arguments); + self = this; + }); + }, + /Evented#on` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); + + expectDeprecation( + () => { + obj.trigger('event!', 'foo', 'bar'); + }, + /Evented#trigger` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); assert.deepEqual(args, ['foo', 'bar']); assert.equal(self, obj); } - ['@test a listener can be added and removed automatically and have arguments'](assert) { + [`${testUnless( + DEPRECATIONS.DEPRECATE_EVENTED.isRemoved + )} @test a listener can be added and removed automatically and have arguments`](assert) { let self, args; let count = 0; - let obj = EmberObject.extend(Evented).create(); - - obj.one('event!', function () { - args = [].slice.call(arguments); - self = this; - count++; - }); - - obj.trigger('event!', 'foo', 'bar'); + let obj; + obj = EmberObject.extend(Evented).create(); + + expectDeprecation( + () => { + obj.one('event!', function () { + args = [].slice.call(arguments); + self = this; + count++; + }); + }, + /Evented#one` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); + + expectDeprecation( + () => { + obj.trigger('event!', 'foo', 'bar'); + }, + /Evented#trigger` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); assert.deepEqual(args, ['foo', 'bar']); assert.equal(self, obj); assert.equal(count, 1, 'the event is triggered once'); - obj.trigger('event!', 'baz', 'bat'); + expectDeprecation( + () => { + obj.trigger('event!', 'baz', 'bat'); + }, + /Evented#trigger` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); assert.deepEqual(args, ['foo', 'bar']); assert.equal(count, 1, 'the event was not triggered again'); assert.equal(self, obj); } - ['@test binding an event can specify a different target'](assert) { + [`${testUnless( + DEPRECATIONS.DEPRECATE_EVENTED.isRemoved + )} @test binding an event can specify a different target`](assert) { let self, args; - let obj = EmberObject.extend(Evented).create(); + let obj; + obj = EmberObject.extend(Evented).create(); let target = {}; - obj.on('event!', target, function () { - args = [].slice.call(arguments); - self = this; - }); - - obj.trigger('event!', 'foo', 'bar'); + expectDeprecation( + () => { + obj.on('event!', target, function () { + args = [].slice.call(arguments); + self = this; + }); + }, + /Evented#on` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); + + expectDeprecation( + () => { + obj.trigger('event!', 'foo', 'bar'); + }, + /Evented#trigger` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); assert.deepEqual(args, ['foo', 'bar']); assert.equal(self, target); } - ['@test a listener registered with one can take method as string and can be added with different target']( + [`${testUnless( + DEPRECATIONS.DEPRECATE_EVENTED.isRemoved + )} @test a listener registered with one can take method as string and can be added with different target`]( assert ) { let count = 0; @@ -110,46 +207,134 @@ moduleFor( count++; }; - let obj = EmberObject.extend(Evented).create(); - - obj.one('event!', target, 'fn'); - obj.trigger('event!'); + let obj; + obj = EmberObject.extend(Evented).create(); + + expectDeprecation( + () => { + obj.one('event!', target, 'fn'); + }, + /Evented#one` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); + + expectDeprecation( + () => { + obj.trigger('event!'); + }, + /Evented#trigger` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); assert.equal(count, 1, 'the event was triggered'); - obj.trigger('event!'); + expectDeprecation( + () => { + obj.trigger('event!'); + }, + /Evented#trigger` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); assert.equal(count, 1, 'the event was not triggered again'); } - ['@test a listener registered with one can be removed with off'](assert) { - let obj = class extends EmberObject.extend(Evented) { + [`${testUnless( + DEPRECATIONS.DEPRECATE_EVENTED.isRemoved + )} @test a listener registered with one can be removed with off`](assert) { + let obj; + obj = class extends EmberObject.extend(Evented) { F() {} }.create(); let F = function () {}; - obj.one('event!', F); - obj.one('event!', obj, 'F'); - - assert.equal(obj.has('event!'), true, 'has events'); - - obj.off('event!', F); - obj.off('event!', obj, 'F'); - - assert.equal(obj.has('event!'), false, 'has no more events'); + expectDeprecation( + () => { + obj.one('event!', F); + }, + /Evented#one` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); + + expectDeprecation( + () => { + obj.one('event!', obj, 'F'); + }, + /Evented#one` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); + + let objHas; + expectDeprecation( + () => { + objHas = obj.has('event!'); + }, + /Evented#has` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); + assert.equal(objHas, true, 'has events'); + + expectDeprecation( + () => { + obj.off('event!', F); + }, + /Evented#off` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); + + expectDeprecation( + () => { + obj.off('event!', obj, 'F'); + }, + /Evented#off` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); + + expectDeprecation( + () => { + objHas = obj.has('event!'); + }, + /Evented#has` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); + + assert.equal(objHas, false, 'has no more events'); } - ['@test adding and removing listeners should be chainable'](assert) { - let obj = EmberObject.extend(Evented).create(); + [`${testUnless( + DEPRECATIONS.DEPRECATE_EVENTED.isRemoved + )} @test adding and removing listeners should be chainable`](assert) { + let obj; + obj = EmberObject.extend(Evented).create(); let F = function () {}; - let ret = obj.on('event!', F); + let ret; + + expectDeprecation( + () => { + ret = obj.on('event!', F); + }, + /Evented#on` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); assert.equal(ret, obj, '#on returns self'); - ret = obj.off('event!', F); + expectDeprecation( + () => { + ret = obj.off('event!', F); + }, + /Evented#off` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); assert.equal(ret, obj, '#off returns self'); - ret = obj.one('event!', F); + expectDeprecation( + () => { + ret = obj.one('event!', F); + }, + /Evented#one` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); assert.equal(ret, obj, '#one returns self'); } } diff --git a/packages/@ember/routing/route.ts b/packages/@ember/routing/route.ts index 4e05176c34e..dd553d3a16e 100644 --- a/packages/@ember/routing/route.ts +++ b/packages/@ember/routing/route.ts @@ -2,6 +2,14 @@ import { privatize as P } from '@ember/-internals/container/lib/registry'; import { addObserver, flushAsyncObservers } from '@ember/-internals/metal/lib/observer'; import { defineProperty } from '@ember/-internals/metal/lib/properties'; import { descriptorForProperty } from '@ember/-internals/metal/lib/decorator'; +import { sendEvent } from '@ember/-internals/metal/lib/events'; +import { + eventedOn, + eventedOne, + eventedTrigger, + eventedOff, + eventedHas, +} from '@ember/-internals/metal/lib/evented-methods'; import type Owner from '@ember/owner'; import { getOwner } from '@ember/-internals/owner'; import type { default as BucketCache } from './lib/cache'; @@ -12,6 +20,7 @@ import getProperties from '@ember/-internals/metal/lib/get_properties'; import setProperties from '@ember/-internals/metal/lib/set_properties'; import EmberObject from '@ember/object'; import Evented from '@ember/object/evented'; +import { meta as metaFor } from '@ember/-internals/meta/lib/meta'; import { A as emberA } from '@ember/array'; import ActionHandler from '@ember/-internals/runtime/lib/mixins/action_handler'; import typeOf from '@ember/utils/lib/type-of'; @@ -82,7 +91,7 @@ const RENDER_STATE = Symbol('render-state'); @since 1.0.0 @public */ -interface Route extends IRoute, ActionHandler, Evented { +interface Route extends IRoute, ActionHandler { /** The `willTransition` action is fired at the beginning of any attempted transition with a `Transition` object as the sole @@ -256,7 +265,13 @@ interface Route extends IRoute, ActionHandler, Evented { error?(error: Error, transition: Transition): boolean | void; } -class Route extends EmberObject.extend(ActionHandler, Evented) implements IRoute { +class Route extends EmberObject.extend(ActionHandler) implements IRoute { + static { + // The deprecated Evented mixin is no longer applied, but instances still + // provide its methods, so `Evented.detect` must keep returning true. + metaFor(this.prototype).addMixin(Evented); + } + static isRouteFactory = true; // These properties will end up appearing in the public interface because we @@ -773,10 +788,51 @@ class Route extends EmberObject.extend(ActionHandler, Evented) */ exit(transition?: Transition) { this.deactivate(transition); - this.trigger('deactivate', transition); + sendEvent(this, 'deactivate', [transition]); this.teardownViews(); } + on( + name: string, + target: Target, + method: string | ((this: Target, ...args: any[]) => void) + ): this; + on(name: string, method: ((...args: any[]) => void) | string): this; + on(name: string, target: any, method?: any) { + eventedOn(this, name, target, method); + return this; + } + + one( + name: string, + target: Target, + method: string | ((this: Target, ...args: any[]) => void) + ): this; + one(name: string, method: string | ((...args: any[]) => void)): this; + one(name: string, target: any, method?: any) { + eventedOne(this, name, target, method); + return this; + } + + trigger(name: string, ...args: any[]): void { + eventedTrigger(this, name, args); + } + + off( + name: string, + target: Target, + method: string | ((this: Target, ...args: any[]) => void) + ): this; + off(name: string, method: string | ((...args: any[]) => void)): this; + off(name: string, target: any, method?: any) { + eventedOff(this, name, target, method); + return this; + } + + has(name: string): boolean { + return eventedHas(this, name); + } + /** @private @@ -799,7 +855,7 @@ class Route extends EmberObject.extend(ActionHandler, Evented) enter(transition: Transition) { this[RENDER_STATE] = undefined; this.activate(transition); - this.trigger('activate', transition); + sendEvent(this, 'activate', [transition]); } /** diff --git a/packages/@ember/routing/router-service.ts b/packages/@ember/routing/router-service.ts index d95bbc2ea4c..81645762229 100644 --- a/packages/@ember/routing/router-service.ts +++ b/packages/@ember/routing/router-service.ts @@ -2,7 +2,6 @@ * @module @ember/routing/router-service */ import { getOwner } from '@ember/-internals/owner'; -import Evented from '@ember/object/evented'; import { assert } from '@ember/debug'; import { readOnly } from '@ember/object/computed'; import Service from '@ember/service'; @@ -14,6 +13,12 @@ import EmberRouter from '@ember/routing/router'; import type { RouteInfo, RouteInfoWithAttributes } from './lib/route-info'; import type { RouteArgs, RouteOptions } from './lib/utils'; import { extractRouteArgs, resemblesURL, shallowEqual } from './lib/utils'; +import { + addListener, + hasListeners, + removeListener, + sendEvent, +} from '@ember/-internals/metal/lib/events'; export const ROUTER = Symbol('ROUTER'); @@ -25,6 +30,8 @@ function cleanURL(url: string, rootURL: string) { return url.substring(rootURL.length); } +type EventName = 'routeWillChange' | 'routeDidChange'; + /** The Router service is the public API that provides access to the router. @@ -56,14 +63,108 @@ function cleanURL(url: string, rootURL: string) { @extends Service @class RouterService */ -interface RouterService extends Evented { +class RouterService extends Service { + [ROUTER]?: EmberRouter; + + /** + Subscribes to a named event with given function. + + @method on + @param {String} name The name of the event + @param {Object} [target] The "this" binding for the callback + @param {Function|String} method A function or the name of a function to be called on `target` + @return this + */ + on(name: 'routeWillChange' | 'routeDidChange', callback: (transition: Transition) => void): this; + on( + name: EventName, + target: Target, + method: string | ((this: Target, ...args: any[]) => void) + ): this; + on(name: EventName, method: ((...args: any[]) => void) | string): this; on( - eventName: 'routeWillChange' | 'routeDidChange', - callback: (transition: Transition) => void + name: EventName, + target: object | ((...args: any[]) => void) | string, + method?: string | ((...args: any[]) => void) + ) { + // SAFETY: The types are not actually correct, but it's not worth the effort to fix them, since we'll be deprecating this API soon. + addListener(this, name, target as object | Function, method as any); + return this; + } + + /** + Subscribes a function to a named event and then cancels the subscription + after the first time the event is triggered. + + @method one + @param {String} name The name of the event + @param {Object} [target] The "this" binding for the callback + @param {Function|String} method A function or the name of a function to be called on `target` + @return this + */ + one( + name: string, + target: Target, + method: string | ((this: Target, ...args: any[]) => void) ): this; -} -class RouterService extends Service.extend(Evented) { - [ROUTER]?: EmberRouter; + one(name: string, method: string | ((...args: any[]) => void)): this; + one( + name: string, + target: object | string | ((...args: any[]) => void), + method?: string | Function + ) { + // SAFETY: The types are not actually correct, but it's not worth the effort to fix them, since we'll be deprecating this API soon. + addListener(this, name, target as object | Function, method as any, true); + return this; + } + + /** + Triggers a named event for the object. + + @method trigger + @param {String} name The name of the event + @param {Object...} args Optional arguments to pass on + @return {boolean} true if listeners were notified, false otherwise + */ + trigger(name: string, ...args: any[]): boolean { + return sendEvent(this, name, args); + } + + /** + Cancels subscription for given name, target, and method. + + @method off + @param {String} name The name of the event + @param {Object} target The target of the subscription + @param {Function|String} method The function or the name of a function of the subscription + @return this + */ + off( + name: string, + target: Target, + method: string | ((this: Target, ...args: any[]) => void) + ): this; + off(name: string, method: string | ((...args: any[]) => void)): this; + off( + name: string, + target: object | string | ((...args: any[]) => void), + method?: string | Function + ) { + // SAFETY: The types are not actually correct, but it's not worth the effort to fix them, since we'll be deprecating this API soon. + removeListener(this, name, target as any, method as any); + return this; + } + + /** + Checks to see if object has any subscriptions for named event. + + @method has + @param {String} name The name of the event + @return {Boolean} does the object have a subscription for event + */ + has(name: string) { + return hasListeners(this, name); + } get _router(): EmberRouter { let router = this[ROUTER]; diff --git a/packages/@ember/routing/router.ts b/packages/@ember/routing/router.ts index 7f6d760dd9d..a057ae2e5ea 100644 --- a/packages/@ember/routing/router.ts +++ b/packages/@ember/routing/router.ts @@ -27,9 +27,9 @@ import type { } from '@ember/routing/location'; import type RouterService from '@ember/routing/router-service'; import EmberObject from '@ember/object'; +import Evented from '@ember/object/evented'; import { A as emberA } from '@ember/array'; import typeOf from '@ember/utils/lib/type-of'; -import Evented from '@ember/object/evented'; import { assert, info } from '@ember/debug'; import { cancel, once, run, scheduleOnce } from '@ember/runloop'; import { DEBUG } from '@glimmer/env'; @@ -57,6 +57,15 @@ import type { QueryParams } from 'route-recognizer'; import type { AnyFn, MethodNamesOf, OmitFirst } from '@ember/-internals/utility-types'; import type { Template } from '@glimmer/interfaces'; import type ApplicationInstance from '@ember/application/instance'; +import { sendEvent } from '@ember/-internals/metal/lib/events'; +import { meta as metaFor } from '@ember/-internals/meta/lib/meta'; +import { + eventedOn, + eventedOne, + eventedTrigger, + eventedOff, + eventedHas, +} from '@ember/-internals/metal/lib/evented-methods'; /** @module @ember/routing/router @@ -142,7 +151,13 @@ const { slice } = Array.prototype; @uses Evented @public */ -class EmberRouter extends EmberObject.extend(Evented) implements Evented { +class EmberRouter extends EmberObject { + static { + // The deprecated Evented mixin is no longer applied, but instances still + // provide its methods, so `Evented.detect` must keep returning true. + metaFor(this.prototype).addMixin(Evented); + } + /** Represents the URL of the root of the application, often '/'. This prefix is assumed on all routes defined on this router. @@ -202,11 +217,46 @@ class EmberRouter extends EmberObject.extend(Evented) implements Evented { private namespace: any; // Begin Evented - declare on: (name: string, method: ((...args: any[]) => void) | string) => this; - declare one: (name: string, method: string | ((...args: any[]) => void)) => this; - declare trigger: (name: string, ...args: any[]) => unknown; - declare off: (name: string, method: string | ((...args: any[]) => void)) => this; - declare has: (name: string) => boolean; + on( + name: string, + target: Target, + method: string | ((this: Target, ...args: any[]) => void) + ): this; + on(name: string, method: ((...args: any[]) => void) | string): this; + on(name: string, target: any, method?: any) { + eventedOn(this, name, target, method); + return this; + } + + one( + name: string, + target: Target, + method: string | ((this: Target, ...args: any[]) => void) + ): this; + one(name: string, method: string | ((...args: any[]) => void)): this; + one(name: string, target: any, method?: any) { + eventedOne(this, name, target, method); + return this; + } + + trigger(name: string, ...args: any[]): void { + eventedTrigger(this, name, args); + } + + off( + name: string, + target: Target, + method: string | ((this: Target, ...args: any[]) => void) + ): this; + off(name: string, method: string | ((...args: any[]) => void)): this; + off(name: string, target: any, method?: any) { + eventedOff(this, name, target, method); + return this; + } + + has(name: string): boolean { + return eventedHas(this, name); + } // End Evented // Set with reopenClass @@ -422,7 +472,7 @@ class EmberRouter extends EmberObject.extend(Evented) implements Evented { } routeWillChange(transition: Transition) { - router.trigger('routeWillChange', transition); + sendEvent(router, 'routeWillChange', [transition]); if (DEBUG) { freezeRouteInfo(transition); @@ -440,7 +490,7 @@ class EmberRouter extends EmberObject.extend(Evented) implements Evented { routeDidChange(transition: Transition) { router.set('currentRoute', transition.to); once(() => { - router.trigger('routeDidChange', transition); + sendEvent(router, 'routeDidChange', [transition]); if (DEBUG) { freezeRouteInfo(transition); diff --git a/packages/ember/tests/routing/decoupled_basic_test.js b/packages/ember/tests/routing/decoupled_basic_test.js index 0b81cb807ed..4b53915cb78 100644 --- a/packages/ember/tests/routing/decoupled_basic_test.js +++ b/packages/ember/tests/routing/decoupled_basic_test.js @@ -13,7 +13,9 @@ import { ModuleBasedTestResolver, runDestroy, runTask, + expectDeprecation, } from 'internal-test-helpers'; +import { DEPRECATIONS } from '@ember/-internals/deprecations'; import { run } from '@ember/runloop'; import { addObserver } from '@ember/-internals/metal'; import { service } from '@ember/service'; @@ -888,8 +890,12 @@ moduleFor( }); } - ['@test `activate` event fires on the route'](assert) { - assert.expect(4); + [`@test \`activate\` event fires on the route`](assert) { + if (DEPRECATIONS.DEPRECATE_EVENTED.isRemoved) { + assert.expect(2); + } else { + assert.expect(5); + } let eventFired = 0; @@ -903,10 +909,18 @@ moduleFor( init() { super.init(...arguments); - this.on('activate', function (transition) { - assert.equal(++eventFired, 1, 'activate event is fired once'); - assert.ok(transition, 'transition is passed to activate event'); - }); + if (!DEPRECATIONS.DEPRECATE_EVENTED.isRemoved) { + expectDeprecation( + () => { + this.on('activate', function (transition) { + assert.equal(++eventFired, 1, 'activate event is fired once'); + assert.ok(transition, 'transition is passed to activate event'); + }); + }, + /Evented#on` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); + } } activate(transition) { @@ -919,8 +933,12 @@ moduleFor( return this.visit('/nork'); } - ['@test `deactivate` event fires on the route'](assert) { - assert.expect(4); + [`@test \`deactivate\` event fires on the route`](assert) { + if (DEPRECATIONS.DEPRECATE_EVENTED.isRemoved) { + assert.expect(2); + } else { + assert.expect(5); + } let eventFired = 0; @@ -935,10 +953,18 @@ moduleFor( init() { super.init(...arguments); - this.on('deactivate', function (transition) { - assert.equal(++eventFired, 1, 'deactivate event is fired once'); - assert.ok(transition, 'transition is passed'); - }); + if (!DEPRECATIONS.DEPRECATE_EVENTED.isRemoved) { + expectDeprecation( + () => { + this.on('deactivate', function (transition) { + assert.equal(++eventFired, 1, 'deactivate event is fired once'); + assert.ok(transition, 'transition is passed'); + }); + }, + /Evented#on` is deprecated/, + DEPRECATIONS.DEPRECATE_EVENTED.isEnabled + ); + } } deactivate(transition) { diff --git a/type-tests/@ember/routing-test/router-service.ts b/type-tests/@ember/routing-test/router-service.ts index 2b6083b8a34..0944a281c4e 100644 --- a/type-tests/@ember/routing-test/router-service.ts +++ b/type-tests/@ember/routing-test/router-service.ts @@ -22,7 +22,7 @@ expectTypeOf(router.on('routeWillChange', routeWillChangeHandler)).toEqualTypeOf expectTypeOf(router.has('routeWillChange')).toEqualTypeOf(); expectTypeOf(router.off('routeWillChange', routeWillChangeHandler)).toEqualTypeOf(); expectTypeOf(router.one('routeWillChange', routeWillChangeHandler)).toEqualTypeOf(); -expectTypeOf(router.trigger('routeWillChange', 'boo')).toBeAny(); +expectTypeOf(router.trigger('routeWillChange', 'boo')).toEqualTypeOf(); const transition = router.transitionTo('someRoute');