diff --git a/broccoli/amd-compat-entrypoints/ember.debug.js b/broccoli/amd-compat-entrypoints/ember.debug.js index 8493dde592d..198b2300139 100644 --- a/broccoli/amd-compat-entrypoints/ember.debug.js +++ b/broccoli/amd-compat-entrypoints/ember.debug.js @@ -56,18 +56,6 @@ d('@ember/-internals/runtime/index', emberinternalsRuntimeIndex); import * as emberinternalsRuntimeLibExtRsvp from '@ember/-internals/runtime/lib/ext/rsvp'; d('@ember/-internals/runtime/lib/ext/rsvp', emberinternalsRuntimeLibExtRsvp); -import * as emberinternalsRuntimeLibMixinsContainerProxy from '@ember/-internals/runtime/lib/mixins/container_proxy'; -d( - '@ember/-internals/runtime/lib/mixins/container_proxy', - emberinternalsRuntimeLibMixinsContainerProxy -); - -import * as emberinternalsRuntimeLibMixinsRegistryProxy from '@ember/-internals/runtime/lib/mixins/registry_proxy'; -d( - '@ember/-internals/runtime/lib/mixins/registry_proxy', - emberinternalsRuntimeLibMixinsRegistryProxy -); - import * as emberinternalsStringIndex from '@ember/-internals/string/index'; d('@ember/-internals/string/index', emberinternalsStringIndex); @@ -185,12 +173,6 @@ d('@ember/engine/instance', emberEngineInstance); import * as emberEngineLibEngineParent from '@ember/engine/lib/engine-parent'; d('@ember/engine/lib/engine-parent', emberEngineLibEngineParent); -import * as emberEnumerableIndex from '@ember/enumerable/index'; -d('@ember/enumerable/index', emberEnumerableIndex); - -import * as emberEnumerableMutable from '@ember/enumerable/mutable'; -d('@ember/enumerable/mutable', emberEnumerableMutable); - import * as emberHelperIndex from '@ember/helper/index'; d('@ember/helper/index', emberHelperIndex); @@ -227,9 +209,6 @@ d('@ember/object/lib/computed/computed_macros', emberObjectLibComputedComputedMa import * as emberObjectMixin from '@ember/object/mixin'; d('@ember/object/mixin', emberObjectMixin); -import * as emberObjectObservable from '@ember/object/observable'; -d('@ember/object/observable', emberObjectObservable); - import * as emberObjectObservers from '@ember/object/observers'; d('@ember/object/observers', emberObjectObservers); diff --git a/package.json b/package.json index c0489cc1a03..7d6a433d95f 100644 --- a/package.json +++ b/package.json @@ -205,8 +205,6 @@ "@ember/-internals/routing/index.js": "ember-source/@ember/-internals/routing/index.js", "@ember/-internals/runtime/index.js": "ember-source/@ember/-internals/runtime/index.js", "@ember/-internals/runtime/lib/ext/rsvp.js": "ember-source/@ember/-internals/runtime/lib/ext/rsvp.js", - "@ember/-internals/runtime/lib/mixins/container_proxy.js": "ember-source/@ember/-internals/runtime/lib/mixins/container_proxy.js", - "@ember/-internals/runtime/lib/mixins/registry_proxy.js": "ember-source/@ember/-internals/runtime/lib/mixins/registry_proxy.js", "@ember/-internals/string/index.js": "ember-source/@ember/-internals/string/index.js", "@ember/-internals/utility-types/index.js": "ember-source/@ember/-internals/utility-types/index.js", "@ember/-internals/utils/index.js": "ember-source/@ember/-internals/utils/index.js", @@ -247,8 +245,6 @@ "@ember/engine/instance.js": "ember-source/@ember/engine/instance.js", "@ember/engine/lib/engine-parent.js": "ember-source/@ember/engine/lib/engine-parent.js", "@ember/engine/parent.js": "ember-source/@ember/engine/parent.js", - "@ember/enumerable/index.js": "ember-source/@ember/enumerable/index.js", - "@ember/enumerable/mutable.js": "ember-source/@ember/enumerable/mutable.js", "@ember/helper/index.js": "ember-source/@ember/helper/index.js", "@ember/instrumentation/index.js": "ember-source/@ember/instrumentation/index.js", "@ember/modifier/index.js": "ember-source/@ember/modifier/index.js", @@ -263,7 +259,6 @@ "@ember/object/lib/computed/computed_macros.js": "ember-source/@ember/object/lib/computed/computed_macros.js", "@ember/object/lib/computed/reduce_computed_macros.js": "ember-source/@ember/object/lib/computed/reduce_computed_macros.js", "@ember/object/mixin.js": "ember-source/@ember/object/mixin.js", - "@ember/object/observable.js": "ember-source/@ember/object/observable.js", "@ember/object/observers.js": "ember-source/@ember/object/observers.js", "@ember/owner/index.js": "ember-source/@ember/owner/index.js", "@ember/renderer/index.js": "ember-source/@ember/renderer/index.js", diff --git a/packages/@ember/-internals/glimmer/lib/component.ts b/packages/@ember/-internals/glimmer/lib/component.ts index 86b1e748071..facb381a739 100644 --- a/packages/@ember/-internals/glimmer/lib/component.ts +++ b/packages/@ember/-internals/glimmer/lib/component.ts @@ -59,150 +59,6 @@ function matches(el: Element, selector: string): boolean { @module @ember/component */ -interface ComponentMethods { - // Overrideable methods are defined here since you can't `declare` a method in a class - - /** - Called when the attributes passed into the component have been updated. - Called both during the initial render of a container and during a rerender. - Can be used in place of an observer; code placed here will be executed - every time any attribute updates. - @method didReceiveAttrs - @public - @since 1.13.0 - */ - didReceiveAttrs(): void; - - /** - Called when the attributes passed into the component have been updated. - Called both during the initial render of a container and during a rerender. - Can be used in place of an observer; code placed here will be executed - every time any attribute updates. - @event didReceiveAttrs - @public - @since 1.13.0 - */ - - /** - Called after a component has been rendered, both on initial render and - in subsequent rerenders. - @method didRender - @public - @since 1.13.0 - */ - didRender(): void; - - /** - Called after a component has been rendered, both on initial render and - in subsequent rerenders. - @event didRender - @public - @since 1.13.0 - */ - - /** - Called before a component has been rendered, both on initial render and - in subsequent rerenders. - @method willRender - @public - @since 1.13.0 - */ - willRender(): void; - - /** - Called before a component has been rendered, both on initial render and - in subsequent rerenders. - @event willRender - @public - @since 1.13.0 - */ - - /** - Called when the attributes passed into the component have been changed. - Called only during a rerender, not during an initial render. - @method didUpdateAttrs - @public - @since 1.13.0 - */ - didUpdateAttrs(): void; - - /** - Called when the attributes passed into the component have been changed. - Called only during a rerender, not during an initial render. - @event didUpdateAttrs - @public - @since 1.13.0 - */ - - /** - Called when the component is about to update and rerender itself. - Called only during a rerender, not during an initial render. - @method willUpdate - @public - @since 1.13.0 - */ - willUpdate(): void; - - /** - Called when the component is about to update and rerender itself. - Called only during a rerender, not during an initial render. - @event willUpdate - @public - @since 1.13.0 - */ - - /** - Called when the component has updated and rerendered itself. - Called only during a rerender, not during an initial render. - @method didUpdate - @public - @since 1.13.0 - */ - didUpdate(): void; - - /** - Called when the component has updated and rerendered itself. - Called only during a rerender, not during an initial render. - @event didUpdate - @public - @since 1.13.0 - */ - - /** - The HTML `id` of the component's element in the DOM. You can provide this - value yourself but it must be unique (just as in HTML): - - ```handlebars - {{my-component elementId="a-really-cool-id"}} - ``` - - ```handlebars - - ``` - If not manually set a default value will be provided by the framework. - Once rendered an element's `elementId` is considered immutable and you - should never change it. If you need to compute a dynamic value for the - `elementId`, you should do this when the component or element is being - instantiated: - - ```javascript - export default class extends Component { - init() { - super.init(...arguments); - - var index = this.get('index'); - this.set('elementId', `component-id${index}`); - } - } - ``` - - @property elementId - @type String - @public - */ - layoutName?: string; -} - // A zero-runtime-overhead private symbol to use in branding the component to // preserve its type parameter. declare const SIGNATURE: unique symbol; @@ -789,28 +645,12 @@ declare const SIGNATURE: unique symbol; @extends Ember.CoreView @public */ -// This type param is used in the class, so must appear here. -// eslint-disable-next-line @typescript-eslint/no-unused-vars -interface Component extends CoreView, ComponentMethods {} - class Component - extends CoreView.extend( - { - // These need to be overridable via extend/create but should still - // have a default. Defining them here is the best way to achieve that. - didReceiveAttrs() {}, - didRender() {}, - didUpdate() {}, - didUpdateAttrs() {}, - willRender() {}, - willUpdate() {}, - } as ComponentMethods, - { - concatenatedProperties: ['attributeBindings', 'classNames', 'classNameBindings'], - classNames: EMPTY_ARRAY, - classNameBindings: EMPTY_ARRAY, - } - ) + extends CoreView.extend({ + concatenatedProperties: ['attributeBindings', 'classNames', 'classNameBindings'], + classNames: EMPTY_ARRAY, + classNameBindings: EMPTY_ARRAY, + }) implements PropertyDidChange { isComponent = true; @@ -1657,6 +1497,98 @@ class Component // End ViewMixin + // Begin lifecycle hooks + + /** + Called when the attributes passed into the component have been updated. + Called both during the initial render of a container and during a rerender. + Can be used in place of an observer; code placed here will be executed + every time any attribute updates. + @method didReceiveAttrs + @public + @since 1.13.0 + */ + didReceiveAttrs(): void {} + + /** + Called after a component has been rendered, both on initial render and + in subsequent rerenders. + @method didRender + @public + @since 1.13.0 + */ + didRender(): void {} + + /** + Called after a component has been rendered, both on initial render and + in subsequent rerenders. + @event didRender + @public + @since 1.13.0 + */ + + /** + Called before a component has been rendered, both on initial render and + in subsequent rerenders. + @method willRender + @public + @since 1.13.0 + */ + willRender(): void {} + + /** + Called before a component has been rendered, both on initial render and + in subsequent rerenders. + @event willRender + @public + @since 1.13.0 + */ + + /** + Called when the attributes passed into the component have been changed. + Called only during a rerender, not during an initial render. + @method didUpdateAttrs + @public + @since 1.13.0 + */ + didUpdateAttrs(): void {} + + /** + Called when the attributes passed into the component have been changed. + Called only during a rerender, not during an initial render. + @event didUpdateAttrs + @public + @since 1.13.0 + */ + + /** + Called when the component is about to update and rerender itself. + Called only during a rerender, not during an initial render. + @method willUpdate + @public + @since 1.13.0 + */ + willUpdate(): void {} + + /** + Called when the component is about to update and rerender itself. + Called only during a rerender, not during an initial render. + @event willUpdate + @public + @since 1.13.0 + */ + + /** + Called when the component has updated and rerendered itself. + Called only during a rerender, not during an initial render. + @method didUpdate + @public + @since 1.13.0 + */ + didUpdate(): void {} + + // End lifecycle hooks + static isComponentFactory = true; static toString() { diff --git a/packages/@ember/-internals/package.json b/packages/@ember/-internals/package.json index 58d115f325f..9a9ef015325 100644 --- a/packages/@ember/-internals/package.json +++ b/packages/@ember/-internals/package.json @@ -29,7 +29,6 @@ "@ember/debug": "workspace:*", "@ember/destroyable": "workspace:*", "@ember/engine": "workspace:*", - "@ember/enumerable": "workspace:*", "@ember/helper": "workspace:*", "@ember/instrumentation": "workspace:*", "@ember/modifier": "workspace:*", diff --git a/packages/@ember/-internals/runtime/index.ts b/packages/@ember/-internals/runtime/index.ts index c60cf8c9ccc..fb977fe1c7a 100644 --- a/packages/@ember/-internals/runtime/index.ts +++ b/packages/@ember/-internals/runtime/index.ts @@ -1,5 +1 @@ -export { default as RegistryProxyMixin } from './lib/mixins/registry_proxy'; -export { default as ContainerProxyMixin } from './lib/mixins/container_proxy'; -export { default as MutableEnumerable } from '@ember/enumerable/mutable'; - export { default as RSVP, onerrorDefault } from './lib/ext/rsvp'; // just for side effect of extending Ember.RSVP diff --git a/packages/@ember/-internals/runtime/lib/mixins/container_proxy.ts b/packages/@ember/-internals/runtime/lib/mixins/container_proxy.ts deleted file mode 100644 index 9b9a870a7c9..00000000000 --- a/packages/@ember/-internals/runtime/lib/mixins/container_proxy.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { schedule, join } from '@ember/runloop'; -/** -@module ember -*/ -import type Container from '@ember/-internals/container/lib/container'; -import Mixin from '@ember/object/mixin'; -import type { ContainerProxy } from '@ember/-internals/owner'; - -// This is defined as a separate interface so that it can be used in the definition of -// `Owner` without also including the `__container__` property. - -/** - ContainerProxyMixin is used to provide public access to specific - container functionality. - - @class ContainerProxyMixin - @extends ContainerProxy - @private -*/ -interface ContainerProxyMixin extends ContainerProxy { - /** @internal */ - __container__: Container; -} -const ContainerProxyMixin = Mixin.create({ - /** - The container stores state. - - @private - @property {Ember.Container} __container__ - */ - __container__: null, - - ownerInjection() { - return this.__container__.ownerInjection(); - }, - - lookup(fullName: string, options: object) { - return this.__container__.lookup(fullName, options); - }, - - destroy() { - let container = this.__container__; - - if (container) { - join(() => { - container.destroy(); - schedule('destroy', container, 'finalizeDestroy'); - }); - } - - this._super(); - }, - - factoryFor(fullName: string) { - return this.__container__.factoryFor(fullName); - }, -}); - -export default ContainerProxyMixin; diff --git a/packages/@ember/-internals/runtime/lib/mixins/registry_proxy.ts b/packages/@ember/-internals/runtime/lib/mixins/registry_proxy.ts deleted file mode 100644 index 1399a185d49..00000000000 --- a/packages/@ember/-internals/runtime/lib/mixins/registry_proxy.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** -@module ember -*/ - -import type { Registry } from '@ember/-internals/container'; -import type { RegistryProxy } from '@ember/-internals/owner'; -import type { AnyFn } from '@ember/-internals/utility-types'; - -import { assert } from '@ember/debug'; -import Mixin from '@ember/object/mixin'; - -/** - RegistryProxyMixin is used to provide public access to specific - registry functionality. - - @class RegistryProxyMixin - @extends RegistryProxy - @private -*/ -interface RegistryProxyMixin extends RegistryProxy { - /** @internal */ - __registry__: Registry; -} -const RegistryProxyMixin = Mixin.create({ - __registry__: null, - - resolveRegistration(fullName: string) { - assert('fullName must be a proper full name', this.__registry__.isValidFullName(fullName)); - return this.__registry__.resolve(fullName); - }, - - register: registryAlias('register'), - unregister: registryAlias('unregister'), - hasRegistration: registryAlias('has'), - registeredOption: registryAlias('getOption'), - registerOptions: registryAlias('options'), - registeredOptions: registryAlias('getOptions'), - registerOptionsForType: registryAlias('optionsForType'), - registeredOptionsForType: registryAlias('getOptionsForType'), -}); - -type AliasMethods = - | 'register' - | 'unregister' - | 'has' - | 'getOption' - | 'options' - | 'getOptions' - | 'optionsForType' - | 'getOptionsForType'; - -function registryAlias(name: N) { - return function (this: RegistryProxyMixin, ...args: Parameters) { - // We need this cast because `Parameters` is deferred so that it is not - // possible for TS to see it will always produce the right type. However, - // since `AnyFn` has a rest type, it is allowed. See discussion on [this - // issue](https://github.com/microsoft/TypeScript/issues/47615). - return (this.__registry__[name] as AnyFn)(...args); - }; -} - -export default RegistryProxyMixin; diff --git a/packages/@ember/-internals/runtime/tests/mixins/container_proxy_test.js b/packages/@ember/-internals/runtime/tests/mixins/container_proxy_test.js deleted file mode 100644 index 1f9cbcfa175..00000000000 --- a/packages/@ember/-internals/runtime/tests/mixins/container_proxy_test.js +++ /dev/null @@ -1,70 +0,0 @@ -import { getOwner } from '@ember/-internals/owner'; -import { Container, Registry } from '@ember/-internals/container'; -import ContainerProxy from '../../lib/mixins/container_proxy'; -import EmberObject from '@ember/object'; -import { run, schedule } from '@ember/runloop'; -import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; -import { destroy } from '@glimmer/destroyable'; - -moduleFor( - '@ember/-internals/runtime/mixins/container_proxy', - class extends AbstractTestCase { - beforeEach() { - this.Owner = EmberObject.extend(ContainerProxy); - this.instance = this.Owner.create(); - - this.registry = new Registry(); - - this.instance.__container__ = new Container(this.registry, { - owner: this.instance, - }); - } - - ['@test provides ownerInjection helper method'](assert) { - let result = this.instance.ownerInjection(); - - assert.equal(getOwner(result), this.instance, 'returns an object with an associated owner'); - } - - ['@test actions queue completes before destruction'](assert) { - assert.expect(1); - - this.registry.register( - 'service:auth', - class extends EmberObject { - willDestroy() { - assert.ok(getOwner(this).lookup('service:auth'), 'can still lookup'); - } - } - ); - - let service = this.instance.lookup('service:auth'); - - run(() => { - schedule('actions', service, 'destroy'); - this.instance.destroy(); - }); - } - - '@test being destroyed by @ember/destroyable properly destroys the container and created instances'( - assert - ) { - assert.expect(1); - - this.registry.register( - 'service:foo', - class FooService extends EmberObject { - willDestroy() { - assert.ok(true, 'is properly destroyed'); - } - } - ); - - this.instance.lookup('service:foo'); - - run(() => { - destroy(this.instance); - }); - } - } -); diff --git a/packages/@ember/-internals/views/lib/system/event_dispatcher.ts b/packages/@ember/-internals/views/lib/system/event_dispatcher.ts index db3512393fc..f2f1c19b4f9 100644 --- a/packages/@ember/-internals/views/lib/system/event_dispatcher.ts +++ b/packages/@ember/-internals/views/lib/system/event_dispatcher.ts @@ -350,13 +350,13 @@ export default class EventDispatcher extends EmberObject { destroy() { if (this._didSetup === false) { - return; + return this; } let rootElement = this._sanitizedRootElement; if (!rootElement) { - return; + return this; } for (let event in this._eventHandlers) { @@ -365,7 +365,7 @@ export default class EventDispatcher extends EmberObject { rootElement.classList.remove(ROOT_ELEMENT_CLASS); - return this._super(...arguments); + return super.destroy(); } toString() { diff --git a/packages/@ember/array/package.json b/packages/@ember/array/package.json index 40ae1570bf6..de9b0400387 100644 --- a/packages/@ember/array/package.json +++ b/packages/@ember/array/package.json @@ -12,7 +12,6 @@ "@ember/-internals": "workspace:*", "@ember/application": "workspace:*", "@ember/debug": "workspace:*", - "@ember/enumerable": "workspace:*", "@ember/object": "workspace:*", "@ember/runloop": "workspace:*", "@ember/utils": "workspace:*", diff --git a/packages/@ember/controller/index.ts b/packages/@ember/controller/index.ts index 38575db7073..f6bd7021d54 100644 --- a/packages/@ember/controller/index.ts +++ b/packages/@ember/controller/index.ts @@ -3,9 +3,7 @@ import { computed, get } from '@ember/object'; import { FrameworkObject } from '@ember/object/-internals'; import { inject as metalInject } from '@ember/-internals/metal'; import type { DecoratorPropertyDescriptor, ElementDescriptor } from '@ember/-internals/metal'; -import Mixin from '@ember/object/mixin'; import type { RouteArgs } from '@ember/routing/-internals'; -import { symbol } from '@ember/-internals/utils'; import type { Transition } from 'router_js'; export type ControllerQueryParamType = 'boolean' | 'number' | 'array' | 'string'; @@ -14,274 +12,113 @@ export type ControllerQueryParam = | Record | Record; -const MODEL = symbol('MODEL'); +const MODEL = Symbol('MODEL'); /** @module @ember/controller */ +// NOTE: This doesn't actually extend EmberObject. /** - @class ControllerMixin - @namespace Ember - @private + @class Controller + @extends EmberObject + @public */ -interface ControllerMixin { - /** @internal */ - _qpDelegate: unknown | null; - - isController: true; - +class Controller extends FrameworkObject.extend({ + concatenatedProperties: ['queryParams'], +}) { /** - The object to which actions from the view should be sent. + This property is updated to various different callback functions depending on + the current "state" of the backing route. It is used by + `Controller.prototype._qpChanged`. - For example, when a Handlebars template uses the `{{action}}` helper, - it will attempt to send the action to the view's controller's `target`. + The methods backing each state can be found in the `Route.prototype._qp` computed + property return value (the `.states` property). The current values are listed here for + the sanity of future travelers: - By default, the value of the target property is set to the router, and - is injected when a controller is instantiated. This injection is applied - as part of the application's initialization process. In most cases the - `target` property will automatically be set to the logical consumer of - actions for the controller. + * `inactive` - This state is used when this controller instance is not part of the active + route hierarchy. Set in `Route.prototype._reset` (a `router.js` microlib hook) and + `Route.prototype.actions.finalizeQueryParamChange`. + * `active` - This state is used when this controller instance is part of the active + route hierarchy. Set in `Route.prototype.actions.finalizeQueryParamChange`. + * `allowOverrides` - This state is used in `Route.prototype.setup` (`route.js` microlib hook). - @property target - @default null - @public + @method _qpDelegate + @private */ - target: unknown | null; + /** @internal */ + declare _qpDelegate: unknown; // Set by route - /** - The controller's current model. When retrieving or modifying a controller's - model, this property should be used instead of the `content` property. + /* ducktype as a controller */ + isController = true; - @property model - @public - */ - model: T; + declare namespace: unknown; /** - Defines which query parameters the controller accepts. - If you give the names `['category','page']` it will bind - the values of these query parameters to the variables - `this.category` and `this.page`. - - By default, query parameters are parsed as strings. This - may cause unexpected behavior if a query parameter is used with `toggleProperty`, - because the initial value set for `param=false` will be the string `"false"`, which is truthy. - - To avoid this, you may specify that the query parameter should be parsed as a boolean - by using the following verbose form with a `type` property: - ```javascript - queryParams: [{ - category: { - type: 'boolean' - } - }] - ``` - Available values for the `type` parameter are `'boolean'`, `'number'`, `'array'`, and `'string'`. - If query param type is not specified, it will default to `'string'`. - - @for Ember.ControllerMixin - @property queryParams - @public - */ - queryParams: Readonly>; + The object to which actions from the view should be sent. - /** - Transition the application into another route. The route may - be either a single route or route path: - - ```javascript - aController.transitionToRoute('blogPosts'); - aController.transitionToRoute('blogPosts.recentEntries'); - ``` - - Optionally supply a model for the route in question. The model - will be serialized into the URL using the `serialize` hook of - the route: - - ```javascript - aController.transitionToRoute('blogPost', aPost); - ``` - - If a literal is passed (such as a number or a string), it will - be treated as an identifier instead. In this case, the `model` - hook of the route will be triggered: - - ```javascript - aController.transitionToRoute('blogPost', 1); - ``` - - Multiple models will be applied last to first recursively up the - route tree. - - ```app/router.js - Router.map(function() { - this.route('blogPost', { path: ':blogPostId' }, function() { - this.route('blogComment', { path: ':blogCommentId', resetNamespace: true }); - }); - }); - ``` - - ```javascript - aController.transitionToRoute('blogComment', aPost, aComment); - aController.transitionToRoute('blogComment', 1, 13); - ``` - - It is also possible to pass a URL (a string that starts with a - `/`). - - ```javascript - aController.transitionToRoute('/'); - aController.transitionToRoute('/blog/post/1/comment/13'); - aController.transitionToRoute('/blog/posts?sort=title'); - ``` - - An options hash with a `queryParams` property may be provided as - the final argument to add query parameters to the destination URL. - - ```javascript - aController.transitionToRoute('blogPost', 1, { - queryParams: { showComments: 'true' } - }); - - // if you just want to transition the query parameters without changing the route - aController.transitionToRoute({ queryParams: { sort: 'date' } }); - ``` - - See also [replaceRoute](/ember/release/classes/Ember.ControllerMixin/methods/replaceRoute?anchor=replaceRoute). - - @for Ember.ControllerMixin - @method transitionToRoute - @deprecated Use transitionTo from the Router service instead. - @param {String} [name] the name of the route or a URL - @param {...Object} models the model(s) or identifier(s) to be used - while transitioning to the route. - @param {Object} [options] optional hash with a queryParams property - containing a mapping of query parameters - @return {Transition} the transition object associated with this - attempted transition - @public - */ - transitionToRoute(...args: RouteArgs): Transition; - - /** - Transition into another route while replacing the current URL, if possible. - This will replace the current history entry instead of adding a new one. - Beside that, it is identical to `transitionToRoute` in all other respects. - - ```javascript - aController.replaceRoute('blogPosts'); - aController.replaceRoute('blogPosts.recentEntries'); - ``` - - Optionally supply a model for the route in question. The model - will be serialized into the URL using the `serialize` hook of - the route: - - ```javascript - aController.replaceRoute('blogPost', aPost); - ``` - - If a literal is passed (such as a number or a string), it will - be treated as an identifier instead. In this case, the `model` - hook of the route will be triggered: - - ```javascript - aController.replaceRoute('blogPost', 1); - ``` - - Multiple models will be applied last to first recursively up the - route tree. - - ```app/router.js - Router.map(function() { - this.route('blogPost', { path: ':blogPostId' }, function() { - this.route('blogComment', { path: ':blogCommentId', resetNamespace: true }); - }); - }); - ``` - - ``` - aController.replaceRoute('blogComment', aPost, aComment); - aController.replaceRoute('blogComment', 1, 13); - ``` - - It is also possible to pass a URL (a string that starts with a - `/`). - - ```javascript - aController.replaceRoute('/'); - aController.replaceRoute('/blog/post/1/comment/13'); - ``` - - @for Ember.ControllerMixin - @method replaceRoute - @deprecated Use replaceWith from the Router service instead. - @param {String} [name] the name of the route or a URL - @param {...Object} models the model(s) or identifier(s) to be used - while transitioning to the route. - @param {Object} [options] optional hash with a queryParams property - containing a mapping of query parameters - @return {Transition} the transition object associated with this - attempted transition - @public - */ - replaceRoute(...args: RouteArgs): Transition; -} -const ControllerMixin = Mixin.create({ - // Support the action hash which is still used internally - mergedProperties: ['actions'], + For example, when a Handlebars template uses the `{{action}}` helper, + it will attempt to send the action to the view's controller's `target`. - /* ducktype as a controller */ - isController: true, + By default, the value of the target property is set to the router, and + is injected when a controller is instantiated. This injection is applied + as part of the application's initialization process. In most cases the + `target` property will automatically be set to the logical consumer of + actions for the controller. - concatenatedProperties: ['queryParams'], + @property target + @default null + @public + */ + declare target: unknown; - target: null, - - store: null, - - init() { - this._super(...arguments); + init(properties: object | undefined) { + super.init(properties); let owner = getOwner(this); if (owner) { this.namespace = owner.lookup('application:main'); this.target = owner.lookup('router:main'); } - }, + } - model: computed({ - get() { - return this[MODEL]; - }, + declare [MODEL]: T; - set(_key, value) { - return (this[MODEL] = value); - }, - }), + @computed + get model() { + return this[MODEL]!; + } - queryParams: null, + set model(value: T) { + this[MODEL] = value; + } /** - This property is updated to various different callback functions depending on - the current "state" of the backing route. It is used by - `Controller.prototype._qpChanged`. - - The methods backing each state can be found in the `Route.prototype._qp` computed - property return value (the `.states` property). The current values are listed here for - the sanity of future travelers: - - * `inactive` - This state is used when this controller instance is not part of the active - route hierarchy. Set in `Route.prototype._reset` (a `router.js` microlib hook) and - `Route.prototype.actions.finalizeQueryParamChange`. - * `active` - This state is used when this controller instance is part of the active - route hierarchy. Set in `Route.prototype.actions.finalizeQueryParamChange`. - * `allowOverrides` - This state is used in `Route.prototype.setup` (`route.js` microlib hook). - - @method _qpDelegate - @private - */ - _qpDelegate: null, // set by route + Defines which query parameters the controller accepts. + If you give the names `['category','page']` it will bind + the values of these query parameters to the variables + `this.category` and `this.page`. + + By default, query parameters are parsed as strings. This + may cause unexpected behavior if a query parameter is used with `toggleProperty`, + because the initial value set for `param=false` will be the string `"false"`, which is truthy. + + To avoid this, you may specify that the query parameter should be parsed as a boolean + by using the following verbose form with a `type` property: + ```javascript + queryParams: [{ + category: { + type: 'boolean' + } + }] + ``` + Available values for the `type` parameter are `'boolean'`, `'number'`, `'array'`, and `'string'`. + If query param type is not specified, it will default to `'string'`. + + @for Ember.Controller + @property queryParams + @public + */ + declare queryParams: Readonly>; /** During `Route#setup` observers are created to invoke this method @@ -302,18 +139,150 @@ const ControllerMixin = Mixin.create({ let delegate = controller._qpDelegate; let value = get(controller, prop); delegate(prop, value); - }, -}); + } -// NOTE: This doesn't actually extend EmberObject. -/** - @class Controller - @extends EmberObject - @uses Ember.ControllerMixin - @public -*/ -interface Controller<_T = unknown> extends FrameworkObject, ControllerMixin<_T> {} -class Controller<_T = unknown> extends FrameworkObject.extend(ControllerMixin) {} + /** + Transition the application into another route. The route may + be either a single route or route path: + + ```javascript + aController.transitionToRoute('blogPosts'); + aController.transitionToRoute('blogPosts.recentEntries'); + ``` + + Optionally supply a model for the route in question. The model + will be serialized into the URL using the `serialize` hook of + the route: + + ```javascript + aController.transitionToRoute('blogPost', aPost); + ``` + + If a literal is passed (such as a number or a string), it will + be treated as an identifier instead. In this case, the `model` + hook of the route will be triggered: + + ```javascript + aController.transitionToRoute('blogPost', 1); + ``` + + Multiple models will be applied last to first recursively up the + route tree. + + ```app/router.js + Router.map(function() { + this.route('blogPost', { path: ':blogPostId' }, function() { + this.route('blogComment', { path: ':blogCommentId', resetNamespace: true }); + }); + }); + ``` + + ```javascript + aController.transitionToRoute('blogComment', aPost, aComment); + aController.transitionToRoute('blogComment', 1, 13); + ``` + + It is also possible to pass a URL (a string that starts with a + `/`). + + ```javascript + aController.transitionToRoute('/'); + aController.transitionToRoute('/blog/post/1/comment/13'); + aController.transitionToRoute('/blog/posts?sort=title'); + ``` + + An options hash with a `queryParams` property may be provided as + the final argument to add query parameters to the destination URL. + + ```javascript + aController.transitionToRoute('blogPost', 1, { + queryParams: { showComments: 'true' } + }); + + // if you just want to transition the query parameters without changing the route + aController.transitionToRoute({ queryParams: { sort: 'date' } }); + ``` + + See also [replaceRoute](/ember/release/classes/Ember.Controller/methods/replaceRoute?anchor=replaceRoute). + + @for Ember.Controller + @method transitionToRoute + @deprecated Use transitionTo from the Router service instead. + @param {String} [name] the name of the route or a URL + @param {...Object} models the model(s) or identifier(s) to be used + while transitioning to the route. + @param {Object} [options] optional hash with a queryParams property + containing a mapping of query parameters + @return {Transition} the transition object associated with this + attempted transition + @public + */ + declare transitionToRoute: (...args: RouteArgs) => Transition; + + /** + Transition into another route while replacing the current URL, if possible. + This will replace the current history entry instead of adding a new one. + Beside that, it is identical to `transitionToRoute` in all other respects. + + ```javascript + aController.replaceRoute('blogPosts'); + aController.replaceRoute('blogPosts.recentEntries'); + ``` + + Optionally supply a model for the route in question. The model + will be serialized into the URL using the `serialize` hook of + the route: + + ```javascript + aController.replaceRoute('blogPost', aPost); + ``` + + If a literal is passed (such as a number or a string), it will + be treated as an identifier instead. In this case, the `model` + hook of the route will be triggered: + + ```javascript + aController.replaceRoute('blogPost', 1); + ``` + + Multiple models will be applied last to first recursively up the + route tree. + + ```app/router.js + Router.map(function() { + this.route('blogPost', { path: ':blogPostId' }, function() { + this.route('blogComment', { path: ':blogCommentId', resetNamespace: true }); + }); + }); + ``` + + ``` + aController.replaceRoute('blogComment', aPost, aComment); + aController.replaceRoute('blogComment', 1, 13); + ``` + + It is also possible to pass a URL (a string that starts with a + `/`). + + ```javascript + aController.replaceRoute('/'); + aController.replaceRoute('/blog/post/1/comment/13'); + ``` + + @for Ember.Controller + @method replaceRoute + @deprecated Use replaceWith from the Router service instead. + @param {String} [name] the name of the route or a URL + @param {...Object} models the model(s) or identifier(s) to be used + while transitioning to the route. + @param {Object} [options] optional hash with a queryParams property + containing a mapping of query parameters + @return {Transition} the transition object associated with this + attempted transition + @public + */ + declare replaceRoute: (...args: RouteArgs) => Transition; +} /** Creates a property that lazily looks up another controller in the container. @@ -366,7 +335,7 @@ export function inject( return metalInject('controller', ...args); } -export { Controller as default, ControllerMixin }; +export default Controller; /** A type registry for Ember `Controller`s. Meant to be declaration-merged so string diff --git a/packages/@ember/debug/package.json b/packages/@ember/debug/package.json index a4bef6ab2e3..2772bd52d89 100644 --- a/packages/@ember/debug/package.json +++ b/packages/@ember/debug/package.json @@ -12,7 +12,6 @@ "@ember/application": "workspace:*", "@ember/array": "workspace:*", "@ember/engine": "workspace:*", - "@ember/enumerable": "workspace:*", "@ember/object": "workspace:*", "@ember/owner": "workspace:*", "@ember/routing": "workspace:*", diff --git a/packages/@ember/engine/index.ts b/packages/@ember/engine/index.ts index e16240fa87d..b0d8f420193 100644 --- a/packages/@ember/engine/index.ts +++ b/packages/@ember/engine/index.ts @@ -14,7 +14,8 @@ import EngineInstance from '@ember/engine/instance'; import { RoutingService } from '@ember/routing/-internals'; import { ComponentLookup } from '@ember/-internals/views'; import { setupEngineRegistry } from '@ember/-internals/glimmer'; -import { RegistryProxyMixin } from '@ember/-internals/runtime'; +import type { FullName, RegisterOptions } from '@ember/owner'; +import type { FactoryClass, InternalFactory } from '@ember/-internals/owner'; function props(obj: object) { let properties = []; @@ -50,12 +51,9 @@ export interface Initializer { @class Engine @extends Ember.Namespace - @uses RegistryProxyMixin @public */ -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -interface Engine extends RegistryProxyMixin {} -class Engine extends Namespace.extend(RegistryProxyMixin) { +class Engine extends Namespace { static initializers: Record> = Object.create(null); static instanceInitializers: Record> = Object.create(null); @@ -443,6 +441,79 @@ class Engine extends Namespace.extend(RegistryProxyMixin) { graph.topsort(cb); } + + // Registry Proxy + // Duplicated with EngineInstance + + declare __registry__: Registry; + + resolveRegistration(fullName: string) { + assert('fullName must be a proper full name', this.__registry__.isValidFullName(fullName)); + return this.__registry__.resolve(fullName); + } + + register( + fullName: FullName, + factory: InternalFactory, + options: RegisterOptions & { instantiate: true } + ): void; + register(fullName: FullName, factory: object, options?: RegisterOptions): void; + register( + fullName: FullName, + factory: InternalFactory, + options?: RegisterOptions + ): void; + register(...args: Parameters) { + return this.__registry__.register(...args); + } + + /** + Unregister a fullName + */ + unregister(fullName: FullName) { + this.__registry__.unregister(fullName); + } + + /** + Given a fullName check if the registry is aware of its factory + or singleton instance. + + @private + @method hasRegistration + @param {String} fullName + @param {Object} [options] + @param {String} [options.source] the fullname of the request source (used for local lookups) + @return {Boolean} + */ + hasRegistration(fullName: FullName): boolean { + return this.__registry__.has(fullName); + } + + registeredOption( + fullName: FullName, + optionName: K + ): RegisterOptions[K] | undefined { + return this.__registry__.getOption(fullName, optionName); + } + + registerOptions(fullName: FullName, options: RegisterOptions) { + return this.__registry__.options(fullName, options); + } + + registeredOptions(fullName: FullName): RegisterOptions | undefined { + return this.__registry__.getOptions(fullName); + } + + /** + Allow registering options for all factories of a type. + */ + registerOptionsForType(type: string, options: RegisterOptions) { + return this.__registry__.optionsForType(type, options); + } + + registeredOptionsForType(type: string): RegisterOptions | undefined { + return this.__registry__.getOptionsForType(type); + } } /** diff --git a/packages/@ember/engine/instance.ts b/packages/@ember/engine/instance.ts index 097b5c6ca6e..d760e62f791 100644 --- a/packages/@ember/engine/instance.ts +++ b/packages/@ember/engine/instance.ts @@ -3,13 +3,20 @@ */ import EmberObject from '@ember/object'; +import { schedule, join } from '@ember/runloop'; import { RSVP } from '@ember/-internals/runtime'; import { assert } from '@ember/debug'; +import type { Container } from '@ember/-internals/container'; import { Registry, privatize as P } from '@ember/-internals/container'; import { guidFor } from '@ember/-internals/utils'; import { ENGINE_PARENT, getEngineParent, setEngineParent } from './parent'; -import { ContainerProxyMixin, RegistryProxyMixin } from '@ember/-internals/runtime'; -import type { InternalOwner } from '@ember/-internals/owner'; +import type { + ContainerProxy, + FactoryClass, + InternalFactory, + InternalOwner, + RegisterOptions, +} from '@ember/-internals/owner'; import type Owner from '@ember/-internals/owner'; import { type FullName, isFactory } from '@ember/-internals/owner'; import type Engine from '@ember/engine'; @@ -40,10 +47,9 @@ export interface EngineInstanceOptions { @public @class EngineInstance @extends EmberObject - @uses RegistryProxyMixin - @uses ContainerProxyMixin */ +// TODO: Update this comment // Note on types: since `EngineInstance` uses `RegistryProxyMixin` and // `ContainerProxyMixin`, which respectively implement the same `RegistryMixin` // and `ContainerMixin` types used to define `InternalOwner`, this is the same @@ -51,8 +57,9 @@ export interface EngineInstanceOptions { // clauses for `InternalOwner` and `Owner` is to keep us honest: if this stops // type checking, we have broken part of our public API contract. Medium-term, // the goal here is to `EngineInstance` simple be `Owner`. -interface EngineInstance extends RegistryProxyMixin, ContainerProxyMixin, InternalOwner, Owner {} -class EngineInstance extends EmberObject.extend(RegistryProxyMixin, ContainerProxyMixin) { +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +interface EngineInstance extends Owner {} +class EngineInstance extends EmberObject implements ContainerProxy, InternalOwner, Owner { /** @private @method setupRegistry @@ -169,23 +176,6 @@ class EngineInstance extends EmberObject.extend(RegistryProxyMixin, ContainerPro (this.constructor as typeof EngineInstance).setupRegistry(this.__registry__, options); } - /** - Unregister a factory. - - Overrides `RegistryProxy#unregister` in order to clear any cached instances - of the unregistered factory. - - @public - @method unregister - @param {String} fullName - */ - unregister(fullName: FullName) { - this.__container__.reset(fullName); - - // We overwrote this method from RegistryProxyMixin. - this.__registry__.unregister(fullName); - } - /** Build a new `EngineInstance` that's a child of this instance. @@ -256,6 +246,133 @@ class EngineInstance extends EmberObject.extend(RegistryProxyMixin, ContainerPro this.register(key, singleton, { instantiate: false }); }); } + + // Container Proxy + + /** + The container stores state. + + @private + @property {Ember.Container} __container__ + */ + declare __container__: Container; + + ownerInjection() { + return this.__container__.ownerInjection(); + } + + destroy() { + let container = this.__container__; + + if (container) { + join(() => { + container.destroy(); + schedule('destroy', container, 'finalizeDestroy'); + }); + } + + return super.destroy(); + } + + // Registry Proxy + // Duplicated with Engine + + declare __registry__: Registry; + + resolveRegistration(fullName: string) { + assert('fullName must be a proper full name', this.__registry__.isValidFullName(fullName)); + return this.__registry__.resolve(fullName); + } + + /** + Registers a factory for later injection. + + @private + @method register + @param {String} fullName + @param {Function} factory + @param {Object} options + */ + register( + fullName: FullName, + factory: InternalFactory, + options: RegisterOptions & { instantiate: true } + ): void; + register(fullName: FullName, factory: object, options?: RegisterOptions): void; + register(...args: Parameters) { + return this.__registry__.register(...args); + } + + /** + Unregister a factory. + + Also clears any cached instances of the unregistered factory. + + @public + @method unregister + @param {String} fullName + */ + unregister(fullName: FullName) { + this.__container__.reset(fullName); + + // We overwrote this method from RegistryProxyMixin. + this.__registry__.unregister(fullName); + } + + /** + Given a fullName check if the registry is aware of its factory + or singleton instance. + + @private + @method hasRegistration + @param {String} fullName + @param {Object} [options] + @param {String} [options.source] the fullname of the request source (used for local lookups) + @return {Boolean} + */ + hasRegistration(fullName: FullName): boolean { + return this.__registry__.has(fullName); + } + + registeredOption( + fullName: FullName, + optionName: K + ): RegisterOptions[K] | undefined { + return this.__registry__.getOption(fullName, optionName); + } + + registerOptions(fullName: FullName, options: RegisterOptions) { + return this.__registry__.options(fullName, options); + } + + registeredOptions(fullName: FullName): RegisterOptions | undefined { + return this.__registry__.getOptions(fullName); + } + + /** + Allow registering options for all factories of a type. + */ + registerOptionsForType(type: string, options: RegisterOptions) { + return this.__registry__.optionsForType(type, options); + } + + registeredOptionsForType(type: string): RegisterOptions | undefined { + return this.__registry__.getOptionsForType(type); + } } +// MEGAHAX: This is really nasty, but if we don't define the functions this way, we need to provide types. +// If we provide types, for reasons I don't understand, they somehow break the interface. +// Adding the methods this way allows us to keep the types defined by the interface. + +// @ts-expect-error This is a huge hack to avoid type issues. +EngineInstance.prototype.lookup = function lookup(fullName: FullName, options?: RegisterOptions) { + return this.__container__.lookup(fullName, options); +}; + +// @ts-expect-error This is a huge hack to avoid type issues +EngineInstance.prototype.factoryFor = function factoryFor(fullName: FullName) { + return this.__container__.factoryFor(fullName); +}; + export default EngineInstance; diff --git a/packages/@ember/engine/type-tests/index.test.ts b/packages/@ember/engine/type-tests/index.test.ts index 4e63038279a..62cf5f68e44 100644 --- a/packages/@ember/engine/type-tests/index.test.ts +++ b/packages/@ember/engine/type-tests/index.test.ts @@ -1,11 +1,12 @@ import type { ResolverClass } from '@ember/-internals/container/lib/registry'; -import type { default as Owner, RegisterOptions, Factory } from '@ember/owner'; +import type { default as Owner, RegisterOptions } from '@ember/owner'; import type Namespace from '@ember/application/namespace'; import type { Initializer } from '@ember/engine'; import Engine from '@ember/engine'; import type EngineInstance from '@ember/engine/instance'; import EmberObject from '@ember/object'; import { expectTypeOf } from 'expect-type'; +import { InternalFactory } from '@ember/-internals/owner'; declare let owner: Owner; @@ -37,7 +38,8 @@ expectTypeOf(engine.Resolver).toEqualTypeOf(); // RegistryProxy expectTypeOf(engine.resolveRegistration('foo:bar')).toEqualTypeOf< - Factory | object | undefined + // NOTE: The original tests had Factory here. It seems like Factory should match InternalFactory... + InternalFactory | object | undefined >(); // @ts-expect-error Requires name engine.resolveRegistration(); diff --git a/packages/@ember/enumerable/index.ts b/packages/@ember/enumerable/index.ts deleted file mode 100644 index 2929953363b..00000000000 --- a/packages/@ember/enumerable/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -import Mixin from '@ember/object/mixin'; - -/** -@module @ember/enumerable -@private -*/ - -/** - The methods in this mixin have been moved to [MutableArray](/ember/release/classes/MutableArray). This mixin has - been intentionally preserved to avoid breaking Enumerable.detect checks - until the community migrates away from them. - - @class Enumerable - @private -*/ -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -interface Enumerable {} -const Enumerable = Mixin.create(); - -export default Enumerable; diff --git a/packages/@ember/enumerable/mutable.ts b/packages/@ember/enumerable/mutable.ts deleted file mode 100644 index 5f1b01182b9..00000000000 --- a/packages/@ember/enumerable/mutable.ts +++ /dev/null @@ -1,22 +0,0 @@ -import Enumerable from '@ember/enumerable'; -import Mixin from '@ember/object/mixin'; - -/** -@module ember -*/ - -/** - The methods in this mixin have been moved to MutableArray. This mixin has - been intentionally preserved to avoid breaking MutableEnumerable.detect - checks until the community migrates away from them. - - @class MutableEnumerable - @namespace Ember - @uses Enumerable - @private -*/ -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -interface MutableEnumerable extends Enumerable {} -const MutableEnumerable = Mixin.create(Enumerable); - -export default MutableEnumerable; diff --git a/packages/@ember/enumerable/package.json b/packages/@ember/enumerable/package.json deleted file mode 100644 index f05957cccbe..00000000000 --- a/packages/@ember/enumerable/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "@ember/enumerable", - "private": true, - "type": "module", - "exports": { - ".": "./index.ts", - "./mutable": "./mutable.ts" - }, - "dependencies": { - "@ember/-internals": "workspace:*", - "@ember/array": "workspace:*", - "@ember/debug": "workspace:*", - "@ember/object": "workspace:*", - "@glimmer/destroyable": "0.94.8", - "@glimmer/env": "^0.1.7", - "@glimmer/owner": "0.93.4", - "@glimmer/util": "0.94.8", - "@glimmer/validator": "0.94.8", - "internal-test-helpers": "workspace:*" - } -} diff --git a/packages/@ember/object/index.ts b/packages/@ember/object/index.ts index 58325bcf278..f46fb565fe1 100644 --- a/packages/@ember/object/index.ts +++ b/packages/@ember/object/index.ts @@ -5,12 +5,22 @@ import { isElementDescriptor, expandProperties, setClassicDecorator, + hasListeners, + beginPropertyChanges, + notifyPropertyChange, + endPropertyChanges, + addObserver, + removeObserver, + get, + set, + getProperties, + setProperties, } from '@ember/-internals/metal'; import { getFactoryFor } from '@ember/-internals/container'; import { setObservers } from '@ember/-internals/utils'; import type { AnyFn } from '@ember/-internals/utility-types'; import CoreObject from '@ember/object/core'; -import Observable from '@ember/object/observable'; +import { peekMeta } from '@ember/-internals/meta'; export { notifyPropertyChange, @@ -23,23 +33,446 @@ export { trySet, } from '@ember/-internals/metal'; +type ObserverMethod = + | (keyof Target & string) + | ((this: Target, sender: Sender, key: string, value: any, rev: number) => void); + /** @module @ember/object */ /** - `EmberObject` is the main base class for all Ember objects. It is a subclass - of `CoreObject` with the `Observable` mixin applied. For details, - see the documentation for each of these. + `EmberObject` is the main base class for all Ember objects. @class EmberObject @extends CoreObject - @uses Observable @public */ -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -interface EmberObject extends Observable {} -class EmberObject extends CoreObject.extend(Observable) { +class EmberObject extends CoreObject { + /** + Retrieves the value of a property from the object. + + This method is usually similar to using `object[keyName]` or `object.keyName`, + however it supports both computed properties and the unknownProperty + handler. + + Because `get` unifies the syntax for accessing all these kinds + of properties, it can make many refactorings easier, such as replacing a + simple property with a computed property, or vice versa. + + ### Computed Properties + + Computed properties are methods defined with the `property` modifier + declared at the end, such as: + + ```javascript + import { computed } from '@ember/object'; + + fullName: computed('firstName', 'lastName', function() { + return this.get('firstName') + ' ' + this.get('lastName'); + }) + ``` + + When you call `get` on a computed property, the function will be + called and the return value will be returned instead of the function + itself. + + ### Unknown Properties + + Likewise, if you try to call `get` on a property whose value is + `undefined`, the `unknownProperty()` method will be called on the object. + If this method returns any value other than `undefined`, it will be returned + instead. This allows you to implement "virtual" properties that are + not defined upfront. + + @method get + @param {String} keyName The property to retrieve + @return {Object} The property value or undefined. + @public + */ + get(key: K): this[K]; + get(key: string): unknown; + get(keyName: string) { + return get(this, keyName); + } + /** + To get the values of multiple properties at once, call `getProperties` + with a list of strings or an array: + + ```javascript + record.getProperties('firstName', 'lastName', 'zipCode'); + // { firstName: 'John', lastName: 'Doe', zipCode: '10011' } + ``` + + is equivalent to: + + ```javascript + record.getProperties(['firstName', 'lastName', 'zipCode']); + // { firstName: 'John', lastName: 'Doe', zipCode: '10011' } + ``` + + @method getProperties + @param {String...|Array} list of keys to get + @return {Object} + @public + */ + getProperties>(list: L): { [Key in L[number]]: this[Key] }; + getProperties>(...list: L): { [Key in L[number]]: this[Key] }; + getProperties(list: L): { [Key in L[number]]: unknown }; + getProperties(...list: L): { [Key in L[number]]: unknown }; + getProperties(...args: string[]) { + return getProperties(this, ...args); + } + // NOT TYPE SAFE! + /** + Sets the provided key or path to the value. + + ```javascript + record.set("key", value); + ``` + + This method is generally very similar to calling `object["key"] = value` or + `object.key = value`, except that it provides support for computed + properties, the `setUnknownProperty()` method and property observers. + + ### Computed Properties + + If you try to set a value on a key that has a computed property handler + defined (see the `get()` method for an example), then `set()` will call + that method, passing both the value and key instead of simply changing + the value itself. This is useful for those times when you need to + implement a property that is composed of one or more member + properties. + + ### Unknown Properties + + If you try to set a value on a key that is undefined in the target + object, then the `setUnknownProperty()` handler will be called instead. This + gives you an opportunity to implement complex "virtual" properties that + are not predefined on the object. If `setUnknownProperty()` returns + undefined, then `set()` will simply set the value on the object. + + ### Property Observers + + In addition to changing the property, `set()` will also register a property + change with the object. Unless you have placed this call inside of a + `beginPropertyChanges()` and `endPropertyChanges(),` any "local" observers + (i.e. observer methods declared on the same object), will be called + immediately. Any "remote" observers (i.e. observer methods declared on + another object) will be placed in a queue and called at a later time in a + coalesced manner. + + @method set + @param {String} keyName The property to set + @param {Object} value The value to set or `null`. + @return {Object} The passed value + @public + */ + set(key: K, value: T): T; + set(key: string, value: T): T; + set(keyName: string, value: unknown) { + return set(this, keyName, value); + } + // NOT TYPE SAFE! + /** + Sets a list of properties at once. These properties are set inside + a single `beginPropertyChanges` and `endPropertyChanges` batch, so + observers will be buffered. + + ```javascript + record.setProperties({ firstName: 'Charles', lastName: 'Jolley' }); + ``` + + @method setProperties + @param {Object} hash the hash of keys and values to set + @return {Object} The passed in hash + @public + */ + setProperties(hash: P): P; + setProperties>(hash: T): T; + setProperties(hash: object) { + return setProperties(this, hash); + } + + /** + Begins a grouping of property changes. + + You can use this method to group property changes so that notifications + will not be sent until the changes are finished. If you plan to make a + large number of changes to an object at one time, you should call this + method at the beginning of the changes to begin deferring change + notifications. When you are done making changes, call + `endPropertyChanges()` to deliver the deferred change notifications and end + deferring. + + @method beginPropertyChanges + @return {Observable} + @private + */ + beginPropertyChanges() { + beginPropertyChanges(); + return this; + } + + /** + Ends a grouping of property changes. + + You can use this method to group property changes so that notifications + will not be sent until the changes are finished. If you plan to make a + large number of changes to an object at one time, you should call + `beginPropertyChanges()` at the beginning of the changes to defer change + notifications. When you are done making changes, call this method to + deliver the deferred change notifications and end deferring. + + @method endPropertyChanges + @return {Observable} + @private + */ + endPropertyChanges() { + endPropertyChanges(); + return this; + } + /** + Convenience method to call `propertyWillChange` and `propertyDidChange` in + succession. + + Notify the observer system that a property has just changed. + + Sometimes you need to change a value directly or indirectly without + actually calling `get()` or `set()` on it. In this case, you can use this + method instead. Calling this method will notify all observers that the + property has potentially changed value. + + @method notifyPropertyChange + @param {String} keyName The property key to be notified about. + @return {Observable} + @public + */ + notifyPropertyChange(keyName: string) { + notifyPropertyChange(this, keyName); + return this; + } + + /** + Adds an observer on a property. + + This is the core method used to register an observer for a property. + + Once you call this method, any time the key's value is set, your observer + will be notified. Note that the observers are triggered any time the + value is set, regardless of whether it has actually changed. Your + observer should be prepared to handle that. + + There are two common invocation patterns for `.addObserver()`: + + - Passing two arguments: + - the name of the property to observe (as a string) + - the function to invoke (an actual function) + - Passing three arguments: + - the name of the property to observe (as a string) + - the target object (will be used to look up and invoke a + function on) + - the name of the function to invoke on the target object + (as a string). + + ```app/components/my-component.js + import Component from '@ember/component'; + + export default Component.extend({ + init() { + this._super(...arguments); + + // the following are equivalent: + + // using three arguments + this.addObserver('foo', this, 'fooDidChange'); + + // using two arguments + this.addObserver('foo', (...args) => { + this.fooDidChange(...args); + }); + }, + + fooDidChange() { + // your custom logic code + } + }); + ``` + + ### Observer Methods + + Observer methods have the following signature: + + ```app/components/my-component.js + import Component from '@ember/component'; + + export default Component.extend({ + init() { + this._super(...arguments); + this.addObserver('foo', this, 'fooDidChange'); + }, + + fooDidChange(sender, key, value, rev) { + // your code + } + }); + ``` + + The `sender` is the object that changed. The `key` is the property that + changes. The `value` property is currently reserved and unused. The `rev` + is the last property revision of the object when it changed, which you can + use to detect if the key value has really changed or not. + + Usually you will not need the value or revision parameters at + the end. In this case, it is common to write observer methods that take + only a sender and key value as parameters or, if you aren't interested in + any of these values, to write an observer that has no parameters at all. + + @method addObserver + @param {String} key The key to observe + @param {Object} target The target object to invoke + @param {String|Function} method The method to invoke + @param {Boolean} sync Whether the observer is sync or not + @return {Observable} + @public + */ + addObserver( + key: keyof this & string, + target: Target, + method: ObserverMethod + ): this; + addObserver(key: keyof this & string, method: ObserverMethod): this; + addObserver( + key: string, + target: Target, + method?: ObserverMethod, + sync?: boolean + ) { + addObserver(this, key, target, method, sync); + return this; + } + + /** + Remove an observer you have previously registered on this object. Pass + the same key, target, and method you passed to `addObserver()` and your + target will no longer receive notifications. + + @method removeObserver + @param {String} key The key to observe + @param {Object} target The target object to invoke + @param {String|Function} method The method to invoke + @param {Boolean} sync Whether the observer is async or not + @return {Observable} + @public + */ + removeObserver( + key: keyof this & string, + target: Target, + method: ObserverMethod + ): this; + removeObserver(key: keyof this & string, method: ObserverMethod): this; + removeObserver( + key: string, + target: Target, + method?: string | Function, + sync?: boolean + ) { + removeObserver(this, key, target, method, sync); + return this; + } + + /** + Returns `true` if the object currently has observers registered for a + particular key. You can use this method to potentially defer performing + an expensive action until someone begins observing a particular property + on the object. + + @method hasObserverFor + @param {String} key Key to check + @return {Boolean} + @private + */ + hasObserverFor(key: string) { + return hasListeners(this, `${key}:change`); + } + + // NOT TYPE SAFE! + /** + Set the value of a property to the current value plus some amount. + + ```javascript + person.incrementProperty('age'); + team.incrementProperty('score', 2); + ``` + + @method incrementProperty + @param {String} keyName The name of the property to increment + @param {Number} increment The amount to increment by. Defaults to 1 + @return {Number} The new property value + @public + */ + incrementProperty(keyName: keyof this & string, increment = 1): number { + assert( + 'Must pass a numeric value to incrementProperty', + !isNaN(parseFloat(String(increment))) && isFinite(increment) + ); + return set(this, keyName, (parseFloat(get(this, keyName) as string) || 0) + increment); + } + // NOT TYPE SAFE! + /** + Set the value of a property to the current value minus some amount. + + ```javascript + player.decrementProperty('lives'); + orc.decrementProperty('health', 5); + ``` + + @method decrementProperty + @param {String} keyName The name of the property to decrement + @param {Number} decrement The amount to decrement by. Defaults to 1 + @return {Number} The new property value + @public + */ + decrementProperty(keyName: keyof this & string, decrement = 1): number { + assert( + 'Must pass a numeric value to decrementProperty', + (typeof decrement === 'number' || !isNaN(parseFloat(decrement))) && isFinite(decrement) + ); + return set(this, keyName, ((get(this, keyName) as number) || 0) - decrement); + } + // NOT TYPE SAFE! + /** + Set the value of a boolean property to the opposite of its + current value. + + ```javascript + starship.toggleProperty('warpDriveEngaged'); + ``` + + @method toggleProperty + @param {String} keyName The name of the property to toggle + @return {Boolean} The new property value + @public + */ + toggleProperty(keyName: keyof this & string): boolean { + return set(this, keyName, !get(this, keyName)); + } + /** + Returns the cached value of a computed property, if it exists. + This allows you to inspect the value of a computed property + without accidentally invoking it if it is intended to be + generated lazily. + + @method cacheFor + @param {String} keyName + @return {Object} The cached value of the computed property, if any + @public + */ + cacheFor(keyName: keyof this & string): unknown { + let meta = peekMeta(this); + return meta !== null ? meta.valueFor(keyName) : undefined; + } + get _debugContainerKey() { let factory = getFactoryFor(this); return factory !== undefined && factory.fullName; diff --git a/packages/@ember/object/observable.ts b/packages/@ember/object/observable.ts deleted file mode 100644 index 05ba879bac8..00000000000 --- a/packages/@ember/object/observable.ts +++ /dev/null @@ -1,542 +0,0 @@ -/** -@module @ember/object/observable -*/ - -import { peekMeta } from '@ember/-internals/meta'; -import { - hasListeners, - beginPropertyChanges, - notifyPropertyChange, - endPropertyChanges, - addObserver, - removeObserver, - get, - set, - getProperties, - setProperties, -} from '@ember/-internals/metal'; - -import Mixin from '@ember/object/mixin'; -import { assert } from '@ember/debug'; - -export type ObserverMethod = - | keyof Target - | ((this: Target, sender: Sender, key: string, value: any, rev: number) => void); - -/** - ## Overview - - This mixin provides properties and property observing functionality, core - features of the Ember object model. - - Properties and observers allow one object to observe changes to a - property on another object. This is one of the fundamental ways that - models, controllers and views communicate with each other in an Ember - application. - - Any object that has this mixin applied can be used in observer - operations. That includes `EmberObject` and most objects you will - interact with as you write your Ember application. - - Note that you will not generally apply this mixin to classes yourself, - but you will use the features provided by this module frequently, so it - is important to understand how to use it. - - ## Using `get()` and `set()` - - Because of Ember's support for bindings and observers, you will always - access properties using the get method, and set properties using the - set method. This allows the observing objects to be notified and - computed properties to be handled properly. - - More documentation about `get` and `set` are below. - - ## Observing Property Changes - - You typically observe property changes simply by using the `observer` - function in classes that you write. - - For example: - - ```javascript - import { observer } from '@ember/object'; - import EmberObject from '@ember/object'; - - EmberObject.extend({ - valueObserver: observer('value', function(sender, key, value, rev) { - // Executes whenever the "value" property changes - // See the addObserver method for more information about the callback arguments - }) - }); - ``` - - Although this is the most common way to add an observer, this capability - is actually built into the `EmberObject` class on top of two methods - defined in this mixin: `addObserver` and `removeObserver`. You can use - these two methods to add and remove observers yourself if you need to - do so at runtime. - - To add an observer for a property, call: - - ```javascript - object.addObserver('propertyKey', targetObject, targetAction) - ``` - - This will call the `targetAction` method on the `targetObject` whenever - the value of the `propertyKey` changes. - - Note that if `propertyKey` is a computed property, the observer will be - called when any of the property dependencies are changed, even if the - resulting value of the computed property is unchanged. This is necessary - because computed properties are not computed until `get` is called. - - @class Observable - @public -*/ -interface Observable { - /** - Retrieves the value of a property from the object. - - This method is usually similar to using `object[keyName]` or `object.keyName`, - however it supports both computed properties and the unknownProperty - handler. - - Because `get` unifies the syntax for accessing all these kinds - of properties, it can make many refactorings easier, such as replacing a - simple property with a computed property, or vice versa. - - ### Computed Properties - - Computed properties are methods defined with the `property` modifier - declared at the end, such as: - - ```javascript - import { computed } from '@ember/object'; - - fullName: computed('firstName', 'lastName', function() { - return this.get('firstName') + ' ' + this.get('lastName'); - }) - ``` - - When you call `get` on a computed property, the function will be - called and the return value will be returned instead of the function - itself. - - ### Unknown Properties - - Likewise, if you try to call `get` on a property whose value is - `undefined`, the `unknownProperty()` method will be called on the object. - If this method returns any value other than `undefined`, it will be returned - instead. This allows you to implement "virtual" properties that are - not defined upfront. - - @method get - @param {String} keyName The property to retrieve - @return {Object} The property value or undefined. - @public - */ - get(key: K): this[K]; - get(key: string): unknown; - - /** - To get the values of multiple properties at once, call `getProperties` - with a list of strings or an array: - - ```javascript - record.getProperties('firstName', 'lastName', 'zipCode'); - // { firstName: 'John', lastName: 'Doe', zipCode: '10011' } - ``` - - is equivalent to: - - ```javascript - record.getProperties(['firstName', 'lastName', 'zipCode']); - // { firstName: 'John', lastName: 'Doe', zipCode: '10011' } - ``` - - @method getProperties - @param {String...|Array} list of keys to get - @return {Object} - @public - */ - getProperties>(list: L): { [Key in L[number]]: this[Key] }; - getProperties>(...list: L): { [Key in L[number]]: this[Key] }; - getProperties(list: L): { [Key in L[number]]: unknown }; - getProperties(...list: L): { [Key in L[number]]: unknown }; - - // NOT TYPE SAFE! - /** - Sets the provided key or path to the value. - - ```javascript - record.set("key", value); - ``` - - This method is generally very similar to calling `object["key"] = value` or - `object.key = value`, except that it provides support for computed - properties, the `setUnknownProperty()` method and property observers. - - ### Computed Properties - - If you try to set a value on a key that has a computed property handler - defined (see the `get()` method for an example), then `set()` will call - that method, passing both the value and key instead of simply changing - the value itself. This is useful for those times when you need to - implement a property that is composed of one or more member - properties. - - ### Unknown Properties - - If you try to set a value on a key that is undefined in the target - object, then the `setUnknownProperty()` handler will be called instead. This - gives you an opportunity to implement complex "virtual" properties that - are not predefined on the object. If `setUnknownProperty()` returns - undefined, then `set()` will simply set the value on the object. - - ### Property Observers - - In addition to changing the property, `set()` will also register a property - change with the object. Unless you have placed this call inside of a - `beginPropertyChanges()` and `endPropertyChanges(),` any "local" observers - (i.e. observer methods declared on the same object), will be called - immediately. Any "remote" observers (i.e. observer methods declared on - another object) will be placed in a queue and called at a later time in a - coalesced manner. - - @method set - @param {String} keyName The property to set - @param {Object} value The value to set or `null`. - @return {Object} The passed value - @public - */ - set(key: K, value: T): T; - set(key: string, value: T): T; - - // NOT TYPE SAFE! - /** - Sets a list of properties at once. These properties are set inside - a single `beginPropertyChanges` and `endPropertyChanges` batch, so - observers will be buffered. - - ```javascript - record.setProperties({ firstName: 'Charles', lastName: 'Jolley' }); - ``` - - @method setProperties - @param {Object} hash the hash of keys and values to set - @return {Object} The passed in hash - @public - */ - setProperties(hash: P): P; - setProperties>(hash: T): T; - - /** - Convenience method to call `propertyWillChange` and `propertyDidChange` in - succession. - - Notify the observer system that a property has just changed. - - Sometimes you need to change a value directly or indirectly without - actually calling `get()` or `set()` on it. In this case, you can use this - method instead. Calling this method will notify all observers that the - property has potentially changed value. - - @method notifyPropertyChange - @param {String} keyName The property key to be notified about. - @return {Observable} - @public - */ - notifyPropertyChange(keyName: string): this; - - /** - Adds an observer on a property. - - This is the core method used to register an observer for a property. - - Once you call this method, any time the key's value is set, your observer - will be notified. Note that the observers are triggered any time the - value is set, regardless of whether it has actually changed. Your - observer should be prepared to handle that. - - There are two common invocation patterns for `.addObserver()`: - - - Passing two arguments: - - the name of the property to observe (as a string) - - the function to invoke (an actual function) - - Passing three arguments: - - the name of the property to observe (as a string) - - the target object (will be used to look up and invoke a - function on) - - the name of the function to invoke on the target object - (as a string). - - ```app/components/my-component.js - import Component from '@ember/component'; - - export default Component.extend({ - init() { - this._super(...arguments); - - // the following are equivalent: - - // using three arguments - this.addObserver('foo', this, 'fooDidChange'); - - // using two arguments - this.addObserver('foo', (...args) => { - this.fooDidChange(...args); - }); - }, - - fooDidChange() { - // your custom logic code - } - }); - ``` - - ### Observer Methods - - Observer methods have the following signature: - - ```app/components/my-component.js - import Component from '@ember/component'; - - export default Component.extend({ - init() { - this._super(...arguments); - this.addObserver('foo', this, 'fooDidChange'); - }, - - fooDidChange(sender, key, value, rev) { - // your code - } - }); - ``` - - The `sender` is the object that changed. The `key` is the property that - changes. The `value` property is currently reserved and unused. The `rev` - is the last property revision of the object when it changed, which you can - use to detect if the key value has really changed or not. - - Usually you will not need the value or revision parameters at - the end. In this case, it is common to write observer methods that take - only a sender and key value as parameters or, if you aren't interested in - any of these values, to write an observer that has no parameters at all. - - @method addObserver - @param {String} key The key to observe - @param {Object} target The target object to invoke - @param {String|Function} method The method to invoke - @param {Boolean} sync Whether the observer is sync or not - @return {Observable} - @public - */ - addObserver(key: keyof this, target: Target, method: ObserverMethod): this; - addObserver(key: keyof this, method: ObserverMethod): this; - - /** - Remove an observer you have previously registered on this object. Pass - the same key, target, and method you passed to `addObserver()` and your - target will no longer receive notifications. - - @method removeObserver - @param {String} key The key to observe - @param {Object} target The target object to invoke - @param {String|Function} method The method to invoke - @param {Boolean} sync Whether the observer is async or not - @return {Observable} - @public - */ - removeObserver( - key: keyof this, - target: Target, - method: ObserverMethod - ): this; - removeObserver(key: keyof this, method: ObserverMethod): this; - - // NOT TYPE SAFE! - /** - Set the value of a property to the current value plus some amount. - - ```javascript - person.incrementProperty('age'); - team.incrementProperty('score', 2); - ``` - - @method incrementProperty - @param {String} keyName The name of the property to increment - @param {Number} increment The amount to increment by. Defaults to 1 - @return {Number} The new property value - @public - */ - incrementProperty(keyName: keyof this, increment?: number): number; - - // NOT TYPE SAFE! - /** - Set the value of a property to the current value minus some amount. - - ```javascript - player.decrementProperty('lives'); - orc.decrementProperty('health', 5); - ``` - - @method decrementProperty - @param {String} keyName The name of the property to decrement - @param {Number} decrement The amount to decrement by. Defaults to 1 - @return {Number} The new property value - @public - */ - decrementProperty(keyName: keyof this, decrement?: number): number; - - // NOT TYPE SAFE! - /** - Set the value of a boolean property to the opposite of its - current value. - - ```javascript - starship.toggleProperty('warpDriveEngaged'); - ``` - - @method toggleProperty - @param {String} keyName The name of the property to toggle - @return {Boolean} The new property value - @public - */ - toggleProperty(keyName: keyof this): boolean; - - /** - Returns the cached value of a computed property, if it exists. - This allows you to inspect the value of a computed property - without accidentally invoking it if it is intended to be - generated lazily. - - @method cacheFor - @param {String} keyName - @return {Object} The cached value of the computed property, if any - @public - */ - cacheFor(key: K): unknown; -} -const Observable = Mixin.create({ - get(keyName: string) { - return get(this, keyName); - }, - - getProperties(...args: string[]) { - return getProperties(this, ...args); - }, - - set(keyName: string, value: unknown) { - return set(this, keyName, value); - }, - - setProperties(hash: object) { - return setProperties(this, hash); - }, - - /** - Begins a grouping of property changes. - - You can use this method to group property changes so that notifications - will not be sent until the changes are finished. If you plan to make a - large number of changes to an object at one time, you should call this - method at the beginning of the changes to begin deferring change - notifications. When you are done making changes, call - `endPropertyChanges()` to deliver the deferred change notifications and end - deferring. - - @method beginPropertyChanges - @return {Observable} - @private - */ - beginPropertyChanges() { - beginPropertyChanges(); - return this; - }, - - /** - Ends a grouping of property changes. - - You can use this method to group property changes so that notifications - will not be sent until the changes are finished. If you plan to make a - large number of changes to an object at one time, you should call - `beginPropertyChanges()` at the beginning of the changes to defer change - notifications. When you are done making changes, call this method to - deliver the deferred change notifications and end deferring. - - @method endPropertyChanges - @return {Observable} - @private - */ - endPropertyChanges() { - endPropertyChanges(); - return this; - }, - - notifyPropertyChange(keyName: string) { - notifyPropertyChange(this, keyName); - return this; - }, - - addObserver( - key: string, - target: object | Function | null, - method?: string | Function, - sync?: boolean - ) { - addObserver(this, key, target, method, sync); - return this; - }, - - removeObserver( - key: string, - target: object | Function | null, - method?: string | Function, - sync?: boolean - ) { - removeObserver(this, key, target, method, sync); - return this; - }, - - /** - Returns `true` if the object currently has observers registered for a - particular key. You can use this method to potentially defer performing - an expensive action until someone begins observing a particular property - on the object. - - @method hasObserverFor - @param {String} key Key to check - @return {Boolean} - @private - */ - hasObserverFor(key: string) { - return hasListeners(this, `${key}:change`); - }, - - incrementProperty(keyName: string, increment = 1) { - assert( - 'Must pass a numeric value to incrementProperty', - !isNaN(parseFloat(String(increment))) && isFinite(increment) - ); - return set(this, keyName, (parseFloat(get(this, keyName)) || 0) + increment); - }, - - decrementProperty(keyName: string, decrement = 1) { - assert( - 'Must pass a numeric value to decrementProperty', - (typeof decrement === 'number' || !isNaN(parseFloat(decrement))) && isFinite(decrement) - ); - return set(this, keyName, (get(this, keyName) || 0) - decrement); - }, - - toggleProperty(keyName: string) { - return set(this, keyName, !get(this, keyName)); - }, - - cacheFor(keyName: string) { - let meta = peekMeta(this); - return meta !== null ? meta.valueFor(keyName) : undefined; - }, -}); - -export default Observable; diff --git a/packages/@ember/object/package.json b/packages/@ember/object/package.json index 6941bc3b4aa..b0d4e464b94 100644 --- a/packages/@ember/object/package.json +++ b/packages/@ember/object/package.json @@ -9,7 +9,6 @@ "./internals": "./internals.ts", "./evented": "./evented.ts", "./core": "./core.ts", - "./observable": "./observable.ts", "./events": "./events.ts", "./computed": "./computed.ts", "./compat": "./compat.ts", @@ -20,7 +19,6 @@ "@ember/application": "workspace:*", "@ember/array": "workspace:*", "@ember/debug": "workspace:*", - "@ember/enumerable": "workspace:*", "@ember/runloop": "workspace:*", "@ember/service": "workspace:*", "@ember/utils": "workspace:*", diff --git a/packages/@ember/object/tests/observable_test.js b/packages/@ember/object/tests/observable_test.js deleted file mode 100644 index 1d58fe4eec4..00000000000 --- a/packages/@ember/object/tests/observable_test.js +++ /dev/null @@ -1,869 +0,0 @@ -import { context } from '@ember/-internals/environment'; -import { run } from '@ember/runloop'; -import { get, computed } from '@ember/object'; -import EmberObject, { observer } from '@ember/object'; -import Observable from '@ember/object/observable'; -import { moduleFor, AbstractTestCase, runLoopSettled } from 'internal-test-helpers'; - -/* - NOTE: This test is adapted from the 1.x series of unit tests. The tests - are the same except for places where we intend to break the API we instead - validate that we warn the developer appropriately. - - CHANGES FROM 1.6: - - * Added ObservableObject which applies the Ember.Observable mixin. - * Changed reference to Ember.T_FUNCTION to 'function' - * Changed all references to sc_super to this._super(...arguments) - * Changed Ember.objectForPropertyPath() to Ember.getPath() - * Removed allPropertiesDidChange test - no longer supported - * Changed test that uses 'ObjectE' as path to 'objectE' to reflect new - rule on using capital letters for property paths. - * Removed test passing context to addObserver. context param is no longer - supported. - * removed test in observer around line 862 that expected key/value to be - the last item in the chained path. Should be root and chained path - -*/ - -// ======================================================================== -// Ember.Observable Tests -// ======================================================================== - -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 ObservableTestCase { - beforeEach() { - object = ObservableObject.extend(Observable, { - computed: computed(function () { - return 'value'; - }), - method() { - return 'value'; - }, - unknownProperty(key) { - this.lastUnknownProperty = key; - return 'unknown'; - }, - }).create({ - normal: 'value', - numberVal: 24, - toggleVal: true, - nullProperty: null, - }); - } - - ['@test should get normal properties'](assert) { - assert.equal(object.get('normal'), 'value'); - } - - ['@test should call computed properties and return their result'](assert) { - assert.equal(object.get('computed'), 'value'); - } - - ['@test should return the function for a non-computed property'](assert) { - let value = object.get('method'); - assert.equal(typeof value, 'function'); - } - - ['@test should return null when property value is null'](assert) { - assert.equal(object.get('nullProperty'), null); - } - - ['@test should call unknownProperty when value is undefined'](assert) { - assert.equal(object.get('unknown'), 'unknown'); - assert.equal(object.lastUnknownProperty, 'unknown'); - } - } -); -// .......................................................... -// Ember.GET() -// -moduleFor( - 'Ember.get()', - class extends ObservableTestCase { - beforeEach() { - objectA = ObservableObject.extend({ - computed: computed(function () { - return 'value'; - }), - method() { - return 'value'; - }, - unknownProperty(key) { - this.lastUnknownProperty = key; - return 'unknown'; - }, - }).create({ - normal: 'value', - numberVal: 24, - toggleVal: true, - nullProperty: null, - }); - - objectB = { - normal: 'value', - nullProperty: null, - }; - } - - ['@test should get normal properties on Ember.Observable'](assert) { - assert.equal(get(objectA, 'normal'), 'value'); - } - - ['@test should call computed properties on Ember.Observable and return their result'](assert) { - assert.equal(get(objectA, 'computed'), 'value'); - } - - ['@test should return the function for a non-computed property on Ember.Observable'](assert) { - let value = get(objectA, 'method'); - assert.equal(typeof value, 'function'); - } - - ['@test should return null when property value is null on Ember.Observable'](assert) { - assert.equal(get(objectA, 'nullProperty'), null); - } - - ['@test should call unknownProperty when value is undefined on Ember.Observable'](assert) { - assert.equal(get(objectA, 'unknown'), 'unknown'); - assert.equal(objectA.lastUnknownProperty, 'unknown'); - } - - ['@test should get normal properties on standard objects'](assert) { - assert.equal(get(objectB, 'normal'), 'value'); - } - - ['@test should return null when property is null on standard objects'](assert) { - assert.equal(get(objectB, 'nullProperty'), null); - } - - ['@test raise if the provided object is undefined']() { - expectAssertion(function () { - get(undefined, 'key'); - }, /Cannot call get with 'key' on an undefined object/i); - } - } -); - -moduleFor( - 'Ember.get() with paths', - 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({ - baz: computed(function () { - return 'blargh'; - }), - }).create(), - }); - - assert.equal(get(foo, 'bar.baz'), 'blargh'); - } - - ['@test should return a property at a given path relative to the passed object - JavaScript hash']( - assert - ) { - let foo = { - bar: { - baz: 'blargh', - }, - }; - - assert.equal(get(foo, 'bar.baz'), 'blargh'); - } - } -); - -// .......................................................... -// SET() -// - -moduleFor( - 'object.set()', - class extends ObservableTestCase { - beforeEach() { - object = ObservableObject.extend({ - computed: computed({ - get() { - return this._computed; - }, - set(key, value) { - this._computed = value; - return this._computed; - }, - }), - - method(key, value) { - if (value !== undefined) { - this._method = value; - } - return this._method; - }, - - unknownProperty() { - return this._unknown; - }, - - setUnknownProperty(key, value) { - this._unknown = value; - return this._unknown; - }, - - // normal property - normal: 'value', - - // computed property - _computed: 'computed', - // method, but not a property - _method: 'method', - // null property - nullProperty: null, - - // unknown property - _unknown: 'unknown', - }).create(); - } - - ['@test should change normal properties and return the value'](assert) { - let ret = object.set('normal', 'changed'); - assert.equal(object.get('normal'), 'changed'); - assert.equal(ret, 'changed'); - } - - ['@test should call computed properties passing value and return the value'](assert) { - let ret = object.set('computed', 'changed'); - assert.equal(object.get('_computed'), 'changed'); - assert.equal(ret, 'changed'); - } - - ['@test should change normal properties when passing undefined'](assert) { - let ret = object.set('normal', undefined); - assert.equal(object.get('normal'), undefined); - assert.equal(ret, undefined); - } - - ['@test should replace the function for a non-computed property and return the value'](assert) { - let ret = object.set('method', 'changed'); - assert.equal(object.get('_method'), 'method'); // make sure this was NOT run - assert.ok(typeof object.get('method') !== 'function'); - assert.equal(ret, 'changed'); - } - - ['@test should replace prover when property value is null'](assert) { - let ret = object.set('nullProperty', 'changed'); - assert.equal(object.get('nullProperty'), 'changed'); - assert.equal(ret, 'changed'); - } - - ['@test should call unknownProperty with value when property is undefined'](assert) { - let ret = object.set('unknown', 'changed'); - assert.equal(object.get('_unknown'), 'changed'); - assert.equal(ret, 'changed'); - } - } -); - -// .......................................................... -// COMPUTED PROPERTIES -// - -moduleFor( - 'Computed properties', - class extends ObservableTestCase { - beforeEach() { - lookup = context.lookup = {}; - - object = ObservableObject.extend({ - computed: computed({ - get() { - this.computedCalls.push('getter-called'); - return 'computed'; - }, - set(key, value) { - this.computedCalls.push(value); - }, - }), - - dependent: computed('changer', { - get() { - this.dependentCalls.push('getter-called'); - return 'dependent'; - }, - set(key, value) { - this.dependentCalls.push(value); - }, - }), - - inc: computed('changer', function () { - return this.incCallCount++; - }), - - nestedInc: computed('inc', function () { - get(this, 'inc'); - return this.nestedIncCallCount++; - }), - - isOn: computed('state', { - get() { - return this.get('state') === 'on'; - }, - set() { - this.set('state', 'on'); - return this.get('state') === 'on'; - }, - }), - - isOff: computed('state', { - get() { - return this.get('state') === 'off'; - }, - set() { - this.set('state', 'off'); - return this.get('state') === 'off'; - }, - }), - }).create({ - computedCalls: [], - changer: 'foo', - dependentCalls: [], - incCallCount: 0, - nestedIncCallCount: 0, - state: 'on', - }); - } - - ['@test getting values should call function return value'](assert) { - // get each property twice. Verify return. - let keys = ['computed', 'dependent']; - - keys.forEach(function (key) { - assert.equal(object.get(key), key, `Try #1: object.get(${key}) should run function`); - assert.equal(object.get(key), key, `Try #2: object.get(${key}) should run function`); - }); - - // verify each call count. cached should only be called once - ['computedCalls', 'dependentCalls'].forEach((key) => { - assert.equal(object[key].length, 1, `non-cached property ${key} should be called 1x`); - }); - } - - ['@test setting values should call function return value'](assert) { - // get each property twice. Verify return. - let keys = ['computed', 'dependent']; - let values = ['value1', 'value2']; - - keys.forEach((key) => { - assert.equal( - object.set(key, values[0]), - values[0], - `Try #1: object.set(${key}, ${values[0]}) should run function` - ); - - assert.equal( - object.set(key, values[1]), - values[1], - `Try #2: object.set(${key}, ${values[1]}) should run function` - ); - - assert.equal( - object.set(key, values[1]), - values[1], - `Try #3: object.set(${key}, ${values[1]}) should not run function since it is setting same value as before` - ); - }); - - // verify each call count. cached should only be called once - keys.forEach((key) => { - let calls = object[key + 'Calls']; - let idx, expectedLength; - - // Cached properties first check their cached value before setting the - // property. Other properties blindly call set. - expectedLength = 3; - assert.equal( - calls.length, - expectedLength, - `set(${key}) should be called the right amount of times` - ); - for (idx = 0; idx < 2; idx++) { - assert.equal( - calls[idx], - values[idx], - `call #${idx + 1} to set(${key}) should have passed value ${values[idx]}` - ); - } - }); - } - - ['@test notify change should clear cache'](assert) { - // call get several times to collect call count - object.get('computed'); // should run func - object.get('computed'); // should not run func - - object.notifyPropertyChange('computed'); - - object.get('computed'); // should run again - assert.equal(object.computedCalls.length, 2, 'should have invoked method 2x'); - } - - ['@test change dependent should clear cache'](assert) { - // call get several times to collect call count - let ret1 = object.get('inc'); // should run func - assert.equal(object.get('inc'), ret1, 'multiple calls should not run cached prop'); - - object.set('changer', 'bar'); - - assert.equal(object.get('inc'), ret1 + 1, 'should increment after dependent key changes'); // should run again - } - - ['@test just notifying change of dependent should clear cache'](assert) { - // call get several times to collect call count - let ret1 = object.get('inc'); // should run func - assert.equal(object.get('inc'), ret1, 'multiple calls should not run cached prop'); - - object.notifyPropertyChange('changer'); - - assert.equal(object.get('inc'), ret1 + 1, 'should increment after dependent key changes'); // should run again - } - - ['@test changing dependent should clear nested cache'](assert) { - // call get several times to collect call count - let ret1 = object.get('nestedInc'); // should run func - assert.equal(object.get('nestedInc'), ret1, 'multiple calls should not run cached prop'); - - object.set('changer', 'bar'); - - assert.equal( - object.get('nestedInc'), - ret1 + 1, - 'should increment after dependent key changes' - ); // should run again - } - - ['@test just notifying change of dependent should clear nested cache'](assert) { - // call get several times to collect call count - let ret1 = object.get('nestedInc'); // should run func - assert.equal(object.get('nestedInc'), ret1, 'multiple calls should not run cached prop'); - - object.notifyPropertyChange('changer'); - - assert.equal( - object.get('nestedInc'), - ret1 + 1, - 'should increment after dependent key changes' - ); // should run again - } - - // This verifies a specific bug encountered where observers for computed - // properties would fire before their prop caches were cleared. - ['@test change dependent should clear cache when observers of dependent are called'](assert) { - // call get several times to collect call count - let ret1 = object.get('inc'); // should run func - assert.equal(object.get('inc'), ret1, 'multiple calls should not run cached prop'); - - // add observer to verify change... - object.addObserver('inc', this, function () { - assert.equal(object.get('inc'), ret1 + 1, 'should increment after dependent key changes'); // should run again - }); - - // now run - object.set('changer', 'bar'); - } - - ['@test setting one of two computed properties that depend on a third property should clear the kvo cache']( - assert - ) { - // we have to call set twice to fill up the cache - object.set('isOff', true); - object.set('isOn', true); - - // setting isOff to true should clear the kvo cache - object.set('isOff', true); - assert.equal(object.get('isOff'), true, 'object.isOff should be true'); - assert.equal(object.get('isOn'), false, 'object.isOn should be false'); - } - - ['@test dependent keys should be able to be specified as property paths'](assert) { - let depObj = ObservableObject.extend({ - menuPrice: computed('menu.price', function () { - return this.get('menu.price'); - }), - }).create({ - menu: ObservableObject.create({ - price: 5, - }), - }); - - assert.equal(depObj.get('menuPrice'), 5, 'precond - initial value returns 5'); - - depObj.set('menu.price', 6); - - assert.equal( - depObj.get('menuPrice'), - 6, - 'cache is properly invalidated after nested property changes' - ); - } - - ['@test cacheable nested dependent keys should clear after their dependencies update'](assert) { - assert.ok(true); - - let DepObj; - - run(function () { - lookup.DepObj = DepObj = ObservableObject.extend({ - price: computed('restaurant.menu.price', function () { - return this.get('restaurant.menu.price'); - }), - }).create({ - restaurant: ObservableObject.create({ - menu: ObservableObject.create({ - price: 5, - }), - }), - }); - }); - - assert.equal(DepObj.get('price'), 5, 'precond - computed property is correct'); - - run(function () { - DepObj.set('restaurant.menu.price', 10); - }); - assert.equal( - DepObj.get('price'), - 10, - 'cacheable computed properties are invalidated even if no run loop occurred' - ); - - run(function () { - DepObj.set('restaurant.menu.price', 20); - }); - assert.equal( - DepObj.get('price'), - 20, - 'cacheable computed properties are invalidated after a second get before a run loop' - ); - assert.equal( - DepObj.get('price'), - 20, - 'precond - computed properties remain correct after a run loop' - ); - - run(function () { - DepObj.set( - 'restaurant.menu', - ObservableObject.create({ - price: 15, - }) - ); - }); - - assert.equal( - DepObj.get('price'), - 15, - 'cacheable computed properties are invalidated after a middle property changes' - ); - - run(function () { - DepObj.set( - 'restaurant.menu', - ObservableObject.create({ - price: 25, - }) - ); - }); - - assert.equal( - DepObj.get('price'), - 25, - 'cacheable computed properties are invalidated after a middle property changes again, before a run loop' - ); - } - } -); - -// .......................................................... -// OBSERVABLE OBJECTS -// - -moduleFor( - 'Observable objects & object properties ', - class extends ObservableTestCase { - beforeEach() { - object = ObservableObject.extend({ - getEach() { - let keys = ['normal', 'abnormal']; - let ret = []; - for (let idx = 0; idx < keys.length; idx++) { - ret[ret.length] = this.get(keys[idx]); - } - return ret; - }, - - newObserver() { - this.abnormal = 'changedValueObserved'; - }, - - testObserver: observer('normal', function () { - this.abnormal = 'removedObserver'; - }), - }).create({ - normal: 'value', - abnormal: 'zeroValue', - numberVal: 24, - toggleVal: true, - observedProperty: 'beingWatched', - testRemove: 'observerToBeRemoved', - }); - } - - ['@test incrementProperty and decrementProperty'](assert) { - let newValue = object.incrementProperty('numberVal'); - - assert.equal(25, newValue, 'numerical value incremented'); - object.numberVal = 24; - newValue = object.decrementProperty('numberVal'); - assert.equal(23, newValue, 'numerical value decremented'); - object.numberVal = 25; - newValue = object.incrementProperty('numberVal', 5); - assert.equal(30, newValue, 'numerical value incremented by specified increment'); - object.numberVal = 25; - newValue = object.incrementProperty('numberVal', -5); - assert.equal(20, newValue, 'minus numerical value incremented by specified increment'); - object.numberVal = 25; - newValue = object.incrementProperty('numberVal', 0); - assert.equal(25, newValue, 'zero numerical value incremented by specified increment'); - - expectAssertion(function () { - newValue = object.incrementProperty('numberVal', 0 - void 0); // Increment by NaN - }, /Must pass a numeric value to incrementProperty/i); - - expectAssertion(function () { - newValue = object.incrementProperty('numberVal', 'Ember'); // Increment by non-numeric String - }, /Must pass a numeric value to incrementProperty/i); - - expectAssertion(function () { - newValue = object.incrementProperty('numberVal', 1 / 0); // Increment by Infinity - }, /Must pass a numeric value to incrementProperty/i); - - assert.equal( - 25, - newValue, - 'Attempting to increment by non-numeric values should not increment value' - ); - - object.numberVal = 25; - newValue = object.decrementProperty('numberVal', 5); - assert.equal(20, newValue, 'numerical value decremented by specified increment'); - object.numberVal = 25; - newValue = object.decrementProperty('numberVal', -5); - assert.equal(30, newValue, 'minus numerical value decremented by specified increment'); - object.numberVal = 25; - newValue = object.decrementProperty('numberVal', 0); - assert.equal(25, newValue, 'zero numerical value decremented by specified increment'); - - expectAssertion(function () { - newValue = object.decrementProperty('numberVal', 0 - void 0); // Decrement by NaN - }, /Must pass a numeric value to decrementProperty/i); - - expectAssertion(function () { - newValue = object.decrementProperty('numberVal', 'Ember'); // Decrement by non-numeric String - }, /Must pass a numeric value to decrementProperty/i); - - expectAssertion(function () { - newValue = object.decrementProperty('numberVal', 1 / 0); // Decrement by Infinity - }, /Must pass a numeric value to decrementProperty/i); - - assert.equal( - 25, - newValue, - 'Attempting to decrement by non-numeric values should not decrement value' - ); - } - - ['@test toggle function, should be boolean'](assert) { - assert.equal(object.toggleProperty('toggleVal', true, false), object.get('toggleVal')); - assert.equal(object.toggleProperty('toggleVal', true, false), object.get('toggleVal')); - assert.equal( - object.toggleProperty('toggleVal', undefined, undefined), - object.get('toggleVal') - ); - } - } -); - -moduleFor( - 'object.addObserver()', - class extends ObservableTestCase { - beforeEach() { - objectE = ObservableObject.create({ - propertyVal: 'chainedProperty', - }); - objectC = ObservableObject.create({ - objectE, - normal: 'value', - normal1: 'zeroValue', - normal2: 'dependentValue', - incrementor: 10, - - action() { - this.normal1 = 'newZeroValue'; - }, - - observeOnceAction() { - this.incrementor = this.incrementor + 1; - }, - - chainedObserver() { - this.normal2 = 'chainedPropertyObserved'; - }, - }); - } - - async ['@test should register an observer for a property'](assert) { - objectC.addObserver('normal', objectC, 'action'); - objectC.set('normal', 'newValue'); - - await runLoopSettled(); - 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'); - await runLoopSettled(); - - assert.equal('chainedPropertyObserved', objectC.normal2); - objectC.normal2 = 'dependentValue'; - objectC.set('objectE', ''); - await runLoopSettled(); - - assert.equal('chainedPropertyObserved', objectC.normal2); - } - } -); - -moduleFor( - 'object.removeObserver()', - class extends ObservableTestCase { - beforeEach() { - objectF = ObservableObject.create({ - propertyVal: 'chainedProperty', - }); - objectD = ObservableObject.create({ - objectF, - - normal: 'value', - normal1: 'zeroValue', - normal2: 'dependentValue', - ArrayKeys: ['normal', 'normal1'], - - addAction() { - this.normal1 = 'newZeroValue'; - }, - removeAction() { - this.normal2 = 'newDependentValue'; - }, - removeChainedObserver() { - this.normal2 = 'chainedPropertyObserved'; - }, - - observableValue: 'hello world', - - observer1() { - // Just an observer - }, - observer2() { - this.removeObserver('observableValue', null, 'observer1'); - this.removeObserver('observableValue', null, 'observer2'); - this.hasObserverFor('observableValue'); // Tickle 'getMembers()' - this.removeObserver('observableValue', null, 'observer3'); - }, - observer3() { - // Just an observer - }, - }); - } - - async ['@test should unregister an observer for a property'](assert) { - objectD.addObserver('normal', objectD, 'addAction'); - objectD.set('normal', 'newValue'); - await runLoopSettled(); - - assert.equal(objectD.normal1, 'newZeroValue'); - - objectD.set('normal1', 'zeroValue'); - await runLoopSettled(); - - 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'); - await runLoopSettled(); - - objectD.removeObserver('objectF.propertyVal', objectD, 'removeChainedObserver'); - objectD.normal2 = 'dependentValue'; - - objectD.objectF.set('propertyVal', 'removedPropertyValue'); - await runLoopSettled(); - - assert.equal('dependentValue', objectD.normal2); - - objectD.set('objectF', ''); - await runLoopSettled(); - - assert.equal('dependentValue', objectD.normal2); - } - - async ['@test removing an observer inside of an observer shouldn’t cause any problems']( - assert - ) { - // The observable system should be protected against clients removing - // 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.set('observableValue', 'hi world'); - - await runLoopSettled(); - } catch { - encounteredError = true; - } - assert.equal(encounteredError, false); - } - } -); diff --git a/packages/@ember/object/type-tests/observable/index.test.ts b/packages/@ember/object/type-tests/observable/index.test.ts deleted file mode 100644 index 77e3e602b59..00000000000 --- a/packages/@ember/object/type-tests/observable/index.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { expectTypeOf } from 'expect-type'; - -import type Mixin from '@ember/object/mixin'; -import type Observable from '@ember/object/observable'; - -// A very naive test that at least makes sure we can import this -expectTypeOf().toMatchTypeOf(); diff --git a/packages/@ember/routing/history-location.ts b/packages/@ember/routing/history-location.ts index 709c344a03f..b8c9184baac 100644 --- a/packages/@ember/routing/history-location.ts +++ b/packages/@ember/routing/history-location.ts @@ -88,8 +88,8 @@ export default class HistoryLocation extends EmberObject implements EmberLocatio return getHash(this.location); } - init(): void { - this._super(...arguments); + init(properties: object | undefined): void { + super.init(properties); let base = document.querySelector('base'); let baseURL = ''; diff --git a/packages/@ember/routing/package.json b/packages/@ember/routing/package.json index 73ee06d6056..6dbfd89f05d 100644 --- a/packages/@ember/routing/package.json +++ b/packages/@ember/routing/package.json @@ -19,7 +19,6 @@ "@ember/controller": "workspace:*", "@ember/debug": "workspace:*", "@ember/engine": "workspace:*", - "@ember/enumerable": "workspace:*", "@ember/object": "workspace:*", "@ember/owner": "workspace:*", "@ember/runloop": "workspace:*", diff --git a/packages/@ember/utils/package.json b/packages/@ember/utils/package.json index 9d3ef031484..9e96d19cfef 100644 --- a/packages/@ember/utils/package.json +++ b/packages/@ember/utils/package.json @@ -10,7 +10,6 @@ "@ember/application": "workspace:*", "@ember/array": "workspace:*", "@ember/debug": "workspace:*", - "@ember/enumerable": "workspace:*", "@ember/object": "workspace:*", "@ember/runloop": "workspace:*", "@glimmer/destroyable": "0.94.8", diff --git a/packages/@ember/version/package.json b/packages/@ember/version/package.json index 7133e966480..9ea7613ee94 100644 --- a/packages/@ember/version/package.json +++ b/packages/@ember/version/package.json @@ -15,7 +15,6 @@ "@ember/debug": "workspace:*", "@ember/destroyable": "workspace:*", "@ember/engine": "workspace:*", - "@ember/enumerable": "workspace:*", "@ember/instrumentation": "workspace:*", "@ember/object": "workspace:*", "@ember/routing": "workspace:*", diff --git a/packages/ember-template-compiler/package.json b/packages/ember-template-compiler/package.json index 163fba58782..c67f7c096f2 100644 --- a/packages/ember-template-compiler/package.json +++ b/packages/ember-template-compiler/package.json @@ -17,7 +17,6 @@ "@ember/debug": "workspace:*", "@ember/destroyable": "workspace:*", "@ember/engine": "workspace:*", - "@ember/enumerable": "workspace:*", "@ember/instrumentation": "workspace:*", "@ember/object": "workspace:*", "@ember/routing": "workspace:*", diff --git a/packages/ember/barrel.ts b/packages/ember/barrel.ts index 3fe97be7fe0..e4ee87a0676 100644 --- a/packages/ember/barrel.ts +++ b/packages/ember/barrel.ts @@ -15,10 +15,7 @@ import { FEATURES as EmberFEATURES, isEnabled } from '@ember/canary-features'; import * as EmberDebug from '@ember/debug'; import { assert as emberAssert, captureRenderTree } from '@ember/debug'; import Backburner from 'backburner.js'; -import EmberController, { - inject as injectController, - ControllerMixin as EmberControllerMixin, -} from '@ember/controller'; +import EmberController, { inject as injectController } from '@ember/controller'; import EmberService, { service } from '@ember/service'; import EmberObject, { @@ -44,7 +41,7 @@ import { sendEvent as emberSendEvent, } from '@ember/object/events'; -import { RegistryProxyMixin, ContainerProxyMixin, RSVP as _RSVP } from '@ember/-internals/runtime'; +import { RSVP as _RSVP } from '@ember/-internals/runtime'; import { componentCapabilities, modifierCapabilities, @@ -74,11 +71,8 @@ import EmberComponent, { Input as EmberInput } from '@ember/component'; import EmberHelper from '@ember/component/helper'; import EmberEngine from '@ember/engine'; import EmberEngineInstance from '@ember/engine/instance'; -import EmberEnumerable from '@ember/enumerable'; -import EmberMutableEnumerable from '@ember/enumerable/mutable'; import EmberCoreObject from '@ember/object/core'; import EmberMixin, { mixin as emberMixin } from '@ember/object/mixin'; -import EmberObservable from '@ember/object/observable'; import { addObserver as emberAddObserver, removeObserver as emberRemoveObserver, @@ -166,10 +160,6 @@ namespace Ember { export const hasListeners = metal.hasListeners; export const libraries = metal.libraries; - // ****@ember/-internals/runtime**** - export const _ContainerProxyMixin = ContainerProxyMixin; - export const _RegistryProxyMixin = RegistryProxyMixin; - // ****@ember/-internals/view**** export const ComponentLookup = views.ComponentLookup; export const EventDispatcher = views.EventDispatcher; @@ -218,8 +208,6 @@ namespace Ember { // ****@ember/controller**** export const Controller = EmberController; export type Controller = EmberController; - export const ControllerMixin = EmberControllerMixin; - export type ControllerMixin = EmberControllerMixin; // ****@ember/debug**** export const _captureRenderTree = captureRenderTree; @@ -264,14 +252,6 @@ namespace Ember { export const EngineInstance = EmberEngineInstance; export type EngineInstance = EmberEngineInstance; - // ****@ember/enumerable**** - export const Enumerable = EmberEnumerable; - export type Enumerable = EmberEnumerable; - - // ****@ember/enumerable/mutable**** - export const MutableEnumerable = EmberMutableEnumerable; - export type MutableEnumerable = EmberMutableEnumerable; - // ****@ember/instrumentation**** /** @private */ export const instrument = instrumentation.instrument; @@ -325,10 +305,6 @@ namespace Ember { export type Mixin = EmberMixin; export const mixin = emberMixin; - // ****@ember/object/observable**** - export const Observable = EmberObservable; - export type Observable = EmberObservable; - // ****@ember/object/observers**** export const addObserver = emberAddObserver; export const removeObserver = emberRemoveObserver; diff --git a/packages/ember/package.json b/packages/ember/package.json index b9feaeb5170..1d4d86c4914 100644 --- a/packages/ember/package.json +++ b/packages/ember/package.json @@ -16,7 +16,6 @@ "@ember/debug": "workspace:*", "@ember/destroyable": "workspace:*", "@ember/engine": "workspace:*", - "@ember/enumerable": "workspace:*", "@ember/helper": "workspace:*", "@ember/instrumentation": "workspace:*", "@ember/modifier": "workspace:*", diff --git a/packages/ember/tests/reexports_test.js b/packages/ember/tests/reexports_test.js index 52b3337d027..81f013259fa 100644 --- a/packages/ember/tests/reexports_test.js +++ b/packages/ember/tests/reexports_test.js @@ -69,7 +69,6 @@ import * as test13 from '@ember/debug/data-adapter'; import * as test14 from '@ember/destroyable'; import * as test15 from '@ember/engine'; import * as test16 from '@ember/engine/instance'; -import * as test17 from '@ember/enumerable'; import * as test18 from '@ember/instrumentation'; import * as test19 from '@ember/modifier'; import * as test20 from '@ember/helper'; @@ -80,7 +79,6 @@ import * as test24 from '@ember/object/core'; import * as test26 from '@ember/object/events'; import * as test27 from '@ember/object/internals'; import * as test28 from '@ember/object/mixin'; -import * as test29 from '@ember/object/observable'; import * as test30 from '@ember/object/observers'; import * as test33 from '@ember/routing/hash-location'; import * as test34 from '@ember/routing/history-location'; @@ -106,7 +104,6 @@ import * as test53 from '@ember/-internals/error-handling'; import * as test54 from '@ember/-internals/meta'; import * as test55 from '@ember/-internals/views'; import * as test56 from '@ember/-internals/glimmer'; -import * as test57 from '@ember/-internals/runtime'; import * as test58 from '@ember/-internals/routing'; import * as test59 from 'backburner.js'; import * as test60 from 'rsvp'; @@ -130,7 +127,6 @@ let allExports = [ ['Helper.helper', '@ember/component/helper', 'helper', test8], ['_templateOnlyComponent', '@ember/component/template-only', 'default', test9], ['Controller', '@ember/controller', 'default', test10], - ['ControllerMixin', '@ember/controller', 'ControllerMixin', test10], ['inject.controller', '@ember/controller', 'inject', test10], ['deprecateFunc', '@ember/debug', 'deprecateFunc', test11], ['deprecate', '@ember/debug', 'deprecate', test11], @@ -167,7 +163,6 @@ let allExports = [ ['_unregisterDestructor', '@ember/destroyable', 'unregisterDestructor', test14], ['Engine', '@ember/engine', 'default', test15], ['EngineInstance', '@ember/engine/instance', 'default', test16], - ['Enumerable', '@ember/enumerable', 'default', test17], ['instrument', '@ember/instrumentation', 'instrument', test18], ['subscribe', '@ember/instrumentation', 'subscribe', test18], ['Instrumentation.instrument', '@ember/instrumentation', 'instrument', test18], @@ -206,7 +201,6 @@ let allExports = [ ['cacheFor', '@ember/object/internals', 'cacheFor', test27], ['guidFor', '@ember/object/internals', 'guidFor', test27], ['Mixin', '@ember/object/mixin', 'default', test28], - ['Observable', '@ember/object/observable', 'default', test29], ['addObserver', '@ember/object/observers', 'addObserver', test30], ['removeObserver', '@ember/object/observers', 'removeObserver', test30], ['HashLocation', '@ember/routing/hash-location', 'default', test33], @@ -323,9 +317,6 @@ let allExports = [ test56, ], ['_Input', '@ember/-internals/glimmer', 'Input', test56], - ['_RegistryProxyMixin', '@ember/-internals/runtime', 'RegistryProxyMixin', test57], - ['_ContainerProxyMixin', '@ember/-internals/runtime', 'ContainerProxyMixin', test57], - ['MutableEnumerable', '@ember/-internals/runtime', null, test57], ['controllerFor', '@ember/-internals/routing', null, test58], ['generateControllerFactory', '@ember/-internals/routing', null, test58], ['generateController', '@ember/-internals/routing', null, test58], diff --git a/packages/internal-test-helpers/package.json b/packages/internal-test-helpers/package.json index 077a0929102..ec6812f033f 100644 --- a/packages/internal-test-helpers/package.json +++ b/packages/internal-test-helpers/package.json @@ -16,7 +16,6 @@ "@ember/debug": "workspace:*", "@ember/destroyable": "workspace:*", "@ember/engine": "workspace:*", - "@ember/enumerable": "workspace:*", "@ember/instrumentation": "workspace:*", "@ember/object": "workspace:*", "@ember/owner": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6b4cc0ec317..df9b4330393 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -337,9 +337,6 @@ importers: '@ember/engine': specifier: workspace:* version: link:../engine - '@ember/enumerable': - specifier: workspace:* - version: link:../enumerable '@ember/helper': specifier: workspace:* version: link:../helper @@ -539,9 +536,6 @@ importers: '@ember/debug': specifier: workspace:* version: link:../debug - '@ember/enumerable': - specifier: workspace:* - version: link:../enumerable '@ember/object': specifier: workspace:* version: link:../object @@ -653,9 +647,6 @@ importers: '@ember/engine': specifier: workspace:* version: link:../engine - '@ember/enumerable': - specifier: workspace:* - version: link:../enumerable '@ember/object': specifier: workspace:* version: link:../object @@ -773,39 +764,6 @@ importers: specifier: ^8.0.5 version: 8.0.6(route-recognizer@0.3.4)(rsvp@4.8.5) - packages/@ember/enumerable: - dependencies: - '@ember/-internals': - specifier: workspace:* - version: link:../-internals - '@ember/array': - specifier: workspace:* - version: link:../array - '@ember/debug': - specifier: workspace:* - version: link:../debug - '@ember/object': - specifier: workspace:* - version: link:../object - '@glimmer/destroyable': - specifier: 0.94.8 - version: 0.94.8 - '@glimmer/env': - specifier: ^0.1.7 - version: 0.1.7 - '@glimmer/owner': - specifier: 0.93.4 - version: 0.93.4 - '@glimmer/util': - specifier: 0.94.8 - version: 0.94.8 - '@glimmer/validator': - specifier: 0.94.8 - version: 0.94.8 - internal-test-helpers: - specifier: workspace:* - version: link:../../internal-test-helpers - packages/@ember/helper: dependencies: '@ember/-internals': @@ -883,9 +841,6 @@ importers: '@ember/debug': specifier: workspace:* version: link:../debug - '@ember/enumerable': - specifier: workspace:* - version: link:../enumerable '@ember/runloop': specifier: workspace:* version: link:../runloop @@ -983,9 +938,6 @@ importers: '@ember/engine': specifier: workspace:* version: link:../engine - '@ember/enumerable': - specifier: workspace:* - version: link:../enumerable '@ember/object': specifier: workspace:* version: link:../object @@ -1199,9 +1151,6 @@ importers: '@ember/debug': specifier: workspace:* version: link:../debug - '@ember/enumerable': - specifier: workspace:* - version: link:../enumerable '@ember/object': specifier: workspace:* version: link:../object @@ -1262,9 +1211,6 @@ importers: '@ember/engine': specifier: workspace:* version: link:../engine - '@ember/enumerable': - specifier: workspace:* - version: link:../enumerable '@ember/instrumentation': specifier: workspace:* version: link:../instrumentation @@ -1349,9 +1295,6 @@ importers: '@ember/engine': specifier: workspace:* version: link:../@ember/engine - '@ember/enumerable': - specifier: workspace:* - version: link:../@ember/enumerable '@ember/helper': specifier: workspace:* version: link:../@ember/helper @@ -1475,9 +1418,6 @@ importers: '@ember/engine': specifier: workspace:* version: link:../@ember/engine - '@ember/enumerable': - specifier: workspace:* - version: link:../@ember/enumerable '@ember/instrumentation': specifier: workspace:* version: link:../@ember/instrumentation @@ -1628,9 +1568,6 @@ importers: '@ember/engine': specifier: workspace:* version: link:../@ember/engine - '@ember/enumerable': - specifier: workspace:* - version: link:../@ember/enumerable '@ember/instrumentation': specifier: workspace:* version: link:../@ember/instrumentation diff --git a/tests/docs/expected.js b/tests/docs/expected.js index a18d52a79ab..ec7a58e074e 100644 --- a/tests/docs/expected.js +++ b/tests/docs/expected.js @@ -495,6 +495,7 @@ module.exports = { 'ApplicationInstance', 'ApplicationInstance.BootOptions', 'Component', + 'Controller', 'ComputedProperty', 'ContainerDebugAdapter', 'CoreObject', @@ -522,7 +523,6 @@ module.exports = { 'Mixin', 'Namespace', 'NoneLocation', - 'Observable', 'Owner', 'Promise', 'RegisterOptions', @@ -555,7 +555,6 @@ module.exports = { '@ember/object', '@ember/object/core', '@ember/object/mixin', - '@ember/object/observable', '@ember/owner', '@ember/renderer', '@ember/routing', diff --git a/tests/node/helpers/build-owner.js b/tests/node/helpers/build-owner.js index 672a399838e..f2a2323d941 100644 --- a/tests/node/helpers/build-owner.js +++ b/tests/node/helpers/build-owner.js @@ -1,5 +1,18 @@ module.exports = function buildOwner(Ember, resolver) { - let Owner = Ember.Object.extend(Ember._RegistryProxyMixin, Ember._ContainerProxyMixin); + // NOTE: This doesn't actually implement all Owner methods, just enough for tests to pass + let Owner = class extends Ember.Object { + register(...args) { + return this.__registry__.register(...args); + } + + lookup(fullName, options) { + return this.__container__.lookup(fullName, options); + } + + factoryFor(fullName) { + return this.__container__.factoryFor(fullName); + } + }; let namespace = Ember.Object.create({ Resolver: { diff --git a/type-tests/@ember/application-test/application-instance.ts b/type-tests/@ember/application-test/application-instance.ts index 07bbf776dee..b69b73a1af0 100644 --- a/type-tests/@ember/application-test/application-instance.ts +++ b/type-tests/@ember/application-test/application-instance.ts @@ -24,8 +24,8 @@ appInstance.register('templates:foo/bar', hbs`

Hello World

`, { singleton: true, instantiate: true, }); +// @ts-expect-error appInstance.register('templates:foo/bar', hbs`

Hello World

`, { - // @ts-expect-error singleton: 'true', instantiate: true, }); diff --git a/type-tests/ember/ember-module-tests.ts b/type-tests/ember/ember-module-tests.ts index 940bf30c622..219032d8a4f 100644 --- a/type-tests/ember/ember-module-tests.ts +++ b/type-tests/ember/ember-module-tests.ts @@ -193,8 +193,6 @@ const myNs = Ember.Namespace.extend({}); expectTypeOf(new Ember.NoneLocation()).toEqualTypeOf(); // Ember.Object new Ember.Object(); -// Ember.Observable -Ember.Object.extend(Ember.Observable, {}); // Ember.Route new Ember.Route(owner); // Ember.Router diff --git a/type-tests/ember/inject.ts b/type-tests/ember/inject.ts index 42df94317e7..4f7fdc9ae95 100755 --- a/type-tests/ember/inject.ts +++ b/type-tests/ember/inject.ts @@ -6,6 +6,7 @@ class AuthService extends Ember.Service { } class ApplicationController extends Ember.Controller { + // @ts-expect-error TODO: Should this actually work? model = {}; declare string: string; transitionToLogin() {} diff --git a/type-tests/ember/techniques/properties-from-this.ts b/type-tests/ember/techniques/properties-from-this.ts index 1c3b78a9f83..f5d4323d289 100644 --- a/type-tests/ember/techniques/properties-from-this.ts +++ b/type-tests/ember/techniques/properties-from-this.ts @@ -1,7 +1,6 @@ /** * These tests validate that the method of pulling property types off of this - * continues to work. We use this technique in the critical Observable interface - * that serves to implement a lot of Ember.CoreObject's functionality + * continues to work. */ import { expectTypeOf } from 'expect-type';