From f65171da2b8f4378bc1ede130cfab117918a3442 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Wed, 29 Apr 2026 22:10:07 -0400 Subject: [PATCH] Drop isInteractive and hasDOM; remove FastBoot codepaths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since we always have a DOM and modifiers always run, the entire isInteractive/hasDOM split is dead weight. Tear it out. Glimmer-vm: - @glimmer/runtime/lib/environment.ts: drop the isInteractive field on EnvironmentImpl and EnvironmentDelegate. scheduleInstallModifier and scheduleUpdateModifier always schedule. - @glimmer/runtime/lib/compiled/opcodes/dom.ts: drop the early-returns in VM_MODIFIER_OP and VM_DYNAMIC_MODIFIER_OP. Modifiers always run. - @glimmer/interfaces: drop isInteractive from the Environment interface. Ember component layer: - curly + root component managers: lifecycle hooks (willRender, willInsertElement, didInsertElement, willUpdate, didUpdate, didRender, willDestroyElement, willClearRender) always fire. - ComponentStateBucket no longer carries isInteractive. - BootEnvironment loses both fields. Renderer: - BaseRenderer / Renderer / renderComponent no longer take isInteractive or hasDOM env config. - Renderer.getElement always returns the element (no more "not allowed in non-interactive environments" throw). - Renderer.remove always triggers didDestroyElement. - Drop the _isInteractive getter and the debug.isInteractive field. - EmberEnvironmentDelegate has no constructor args. Application/engine boot: - BootOptions drops isInteractive and isBrowser. - _BootOptions no longer threads isInteractive through isBrowser / shouldRender / option override; just _renderMode, location, shouldRender, document, rootElement. - ApplicationInstance always calls setupEventDispatcher. - EngineInstance's singleton list always includes event_dispatcher:main. - EventDispatcher.setup drops the "should never be setup in fastboot" assertion. - @ember/-internals/views/lib/system/event_dispatcher.ts no longer imports from @ember/-internals/glimmer. Component: - The DEBUG tagless event-handler check no longer needs renderer._isInteractive. - _dispatcher always looks up event_dispatcher:main. - appendTo always uses document.querySelector for string selectors; the "no DOM" branch is gone. matches() drops its fastboot guard. @ember/-internals/browser-environment package: deleted entirely. - isChrome/isFirefox were the only remaining consumers (in @ember/debug); inlined there with `typeof window !== 'undefined'` guards. - self/location/history/window references were the only hasDOM users. - Removed from @ember/-internals package exports, root package.json embedded-package map, types/stable manifest, and lib/index.js implicit-modules list. - Test files that imported `window`/`isChrome`/`isFirefox` from browser-environment now use globals directly. lazily initializes its INPUT_ELEMENT probe (instead of at module -load time) so node-side build tooling that imports the package without a DOM in scope still loads. The DOM is required at render time, which is the contract. Tests: - Drop the non-interactive variants of the lifecycle, custom-modifier, and on-modifier integration tests. Drop the lifecycle test's isInteractive/nonInteractive split — assertHooks always uses the interactive expectations. - Drop the visit_test that checked event_dispatcher:main wasn't created for isInteractive: false. - OutletView render-queue test now appends to a real DOM element rather than a CSS-selector string. - type-tests for BootOptions no longer mention isBrowser. Smoke tests: - Delete app-boot, component-rendering, fastboot-sandbox, and visit tests in node-template, plus their setup-app/setup-component/ build-owner/assert-html-matches helpers. They were FastBoot-only — every one of them used SimpleDOM as a stand-in for document. tests/docs/expected.js: drop the isBrowser, isInteractive, and matches entries (the public surfaces that have been removed). Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/index.js | 1 - package.json | 1 - .../-internals/browser-environment/index.ts | 12 - .../browser-environment/lib/has-dom.ts | 19 - .../glimmer/lib/component-managers/curly.ts | 53 +-- .../glimmer/lib/component-managers/root.ts | 15 +- .../-internals/glimmer/lib/component.ts | 86 ++--- .../glimmer/lib/components/input.ts | 51 ++- .../-internals/glimmer/lib/environment.ts | 6 - .../@ember/-internals/glimmer/lib/renderer.ts | 79 +--- .../lib/utils/curly-component-state-bucket.ts | 23 +- .../-internals/glimmer/lib/views/outlet.ts | 10 +- .../integration/components/life-cycle-test.js | 186 +++------ .../components/render-component-test.ts | 2 +- .../custom-modifier-manager-test.js | 63 ---- .../tests/integration/modifiers/on-test.js | 65 +--- .../glimmer/tests/unit/outlet-test.js | 2 +- packages/@ember/-internals/package.json | 1 - .../runtime/tests/core/is_array_test.js | 1 - .../utils/tests/checkHasSuper_test.js | 5 +- .../views/lib/system/event_dispatcher.ts | 15 - packages/@ember/application/index.ts | 4 +- packages/@ember/application/instance.ts | 105 +----- .../@ember/application/tests/visit_test.js | 53 --- .../application/type-tests/index.test.ts | 5 - .../application/type-tests/instance.test.ts | 5 - packages/@ember/debug/index.ts | 6 +- packages/@ember/engine/instance.ts | 8 +- .../@ember/engine/type-tests/instance.test.ts | 5 - packages/@ember/utils/tests/type_of_test.js | 1 - .../integration-tests/lib/base-env.ts | 2 - .../integration-tests/test/env-test.ts | 2 - .../interfaces/lib/runtime/environment.d.ts | 1 - .../runtime/lib/compiled/opcodes/dom.ts | 7 - packages/@glimmer/runtime/lib/environment.ts | 18 +- .../lib/test-cases/rendering.ts | 5 +- .../node-template/tests/node/app-boot-test.js | 138 ------- .../tests/node/component-rendering-test.js | 45 --- .../tests/node/fastboot-sandbox-test.js | 81 ---- .../tests/node/helpers/assert-html-matches.js | 26 -- .../tests/node/helpers/build-owner.js | 35 -- .../tests/node/helpers/setup-app.js | 207 ---------- .../tests/node/helpers/setup-component.js | 113 ------ .../node-template/tests/node/visit-test.js | 357 ------------------ tests/docs/expected.js | 3 - 45 files changed, 161 insertions(+), 1767 deletions(-) delete mode 100644 packages/@ember/-internals/browser-environment/index.ts delete mode 100644 packages/@ember/-internals/browser-environment/lib/has-dom.ts delete mode 100644 smoke-tests/node-template/tests/node/app-boot-test.js delete mode 100644 smoke-tests/node-template/tests/node/component-rendering-test.js delete mode 100644 smoke-tests/node-template/tests/node/fastboot-sandbox-test.js delete mode 100644 smoke-tests/node-template/tests/node/helpers/assert-html-matches.js delete mode 100644 smoke-tests/node-template/tests/node/helpers/build-owner.js delete mode 100644 smoke-tests/node-template/tests/node/helpers/setup-app.js delete mode 100644 smoke-tests/node-template/tests/node/helpers/setup-component.js delete mode 100644 smoke-tests/node-template/tests/node/visit-test.js diff --git a/lib/index.js b/lib/index.js index 36c496c739d..8008d99ff5b 100644 --- a/lib/index.js +++ b/lib/index.js @@ -27,7 +27,6 @@ const shim = addonV1Shim(path.join(__dirname, '..'), { return { ...meta, 'implicit-modules': [ - './dist/dev/packages/@ember/-internals/browser-environment/index.js', './dist/dev/packages/@ember/-internals/container/index.js', './dist/dev/packages/@ember/-internals/deprecations/index.js', './dist/dev/packages/@ember/-internals/environment/index.js', diff --git a/package.json b/package.json index 008724da9c5..dd4b0b61152 100644 --- a/package.json +++ b/package.json @@ -171,7 +171,6 @@ "type": "addon", "version": 2, "renamed-modules": { - "@ember/-internals/browser-environment/index.js": "ember-source/@ember/-internals/browser-environment/index.js", "@ember/-internals/container/index.js": "ember-source/@ember/-internals/container/index.js", "@ember/-internals/deprecations/index.js": "ember-source/@ember/-internals/deprecations/index.js", "@ember/-internals/environment/index.js": "ember-source/@ember/-internals/environment/index.js", diff --git a/packages/@ember/-internals/browser-environment/index.ts b/packages/@ember/-internals/browser-environment/index.ts deleted file mode 100644 index 0b8c320bd3d..00000000000 --- a/packages/@ember/-internals/browser-environment/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -import hasDom from './lib/has-dom'; - -declare const chrome: unknown; -declare const opera: unknown; - -export { default as hasDOM } from './lib/has-dom'; -export const window = hasDom ? self : null; -export const location = hasDom ? self.location : null; -export const history = hasDom ? self.history : null; -export const userAgent = hasDom ? self.navigator.userAgent : 'Lynx (textmode)'; -export const isChrome = hasDom ? typeof chrome === 'object' && !(typeof opera === 'object') : false; -export const isFirefox = hasDom ? /Firefox|FxiOS/.test(userAgent) : false; diff --git a/packages/@ember/-internals/browser-environment/lib/has-dom.ts b/packages/@ember/-internals/browser-environment/lib/has-dom.ts deleted file mode 100644 index 0360db0bd8e..00000000000 --- a/packages/@ember/-internals/browser-environment/lib/has-dom.ts +++ /dev/null @@ -1,19 +0,0 @@ -// check if window exists and actually is the global -export default typeof self === 'object' && - self !== null && - self.Object === Object && - typeof Window !== 'undefined' && - self.constructor === Window && - typeof document === 'object' && - document !== null && - self.document === document && - typeof location === 'object' && - location !== null && - self.location === location && - typeof history === 'object' && - history !== null && - self.history === history && - typeof navigator === 'object' && - navigator !== null && - self.navigator === navigator && - typeof navigator.userAgent === 'string'; diff --git a/packages/@ember/-internals/glimmer/lib/component-managers/curly.ts b/packages/@ember/-internals/glimmer/lib/component-managers/curly.ts index 44b4cdc5c61..1829fc23168 100644 --- a/packages/@ember/-internals/glimmer/lib/component-managers/curly.ts +++ b/packages/@ember/-internals/glimmer/lib/component-managers/curly.ts @@ -251,7 +251,7 @@ export default class CurlyComponentManager owner: Owner, ComponentClass: ComponentFactory, args: VMArguments, - { isInteractive }: Environment, + _env: Environment, dynamicScope: DynamicScope, callerSelfRef: Reference ): ComponentStateBucket { @@ -313,15 +313,9 @@ export default class CurlyComponentManager // We usually do this in the `didCreateElement`, but that hook doesn't fire for tagless components if (!hasWrappedElement) { - if (isInteractive) { - component.trigger('willRender'); - } - + component.trigger('willRender'); component._transitionTo('hasElement'); - - if (isInteractive) { - component.trigger('willInsertElement'); - } + component.trigger('willInsertElement'); } // Track additional lifecycle metadata about this component in a state bucket. @@ -331,8 +325,7 @@ export default class CurlyComponentManager capturedArgs, argsTag, finalizer, - hasWrappedElement, - isInteractive + hasWrappedElement ); if (args.named.has('class')) { @@ -343,7 +336,7 @@ export default class CurlyComponentManager processComponentInitializationAssertions(component, props); } - if (isInteractive && hasWrappedElement) { + if (hasWrappedElement) { component.trigger('willRender'); } @@ -367,7 +360,7 @@ export default class CurlyComponentManager } didCreateElement( - { component, classRef, isInteractive, rootRef }: ComponentStateBucket, + { component, classRef, rootRef }: ComponentStateBucket, element: Element, operations: ElementOperations ): void { @@ -407,11 +400,9 @@ export default class CurlyComponentManager component._transitionTo('hasElement'); - if (isInteractive) { - beginUntrackFrame(); - component.trigger('willInsertElement'); - endUntrackFrame(); - } + beginUntrackFrame(); + component.trigger('willInsertElement'); + endUntrackFrame(); } didRenderLayout(bucket: ComponentStateBucket, bounds: Bounds): void { @@ -419,16 +410,14 @@ export default class CurlyComponentManager bucket.finalize(); } - didCreate({ component, isInteractive }: ComponentStateBucket): void { - if (isInteractive) { - component._transitionTo('inDOM'); - component.trigger('didInsertElement'); - component.trigger('didRender'); - } + didCreate({ component }: ComponentStateBucket): void { + component._transitionTo('inDOM'); + component.trigger('didInsertElement'); + component.trigger('didRender'); } update(bucket: ComponentStateBucket): void { - let { component, args, argsTag, argsRevision, isInteractive } = bucket; + let { component, args, argsTag, argsRevision } = bucket; bucket.finalizer = _instrumentStart('render.component', rerenderInstrumentDetails, component); @@ -449,10 +438,8 @@ export default class CurlyComponentManager component.trigger('didReceiveAttrs'); } - if (isInteractive) { - component.trigger('willUpdate'); - component.trigger('willRender'); - } + component.trigger('willUpdate'); + component.trigger('willRender'); endUntrackFrame(); @@ -464,11 +451,9 @@ export default class CurlyComponentManager bucket.finalize(); } - didUpdate({ component, isInteractive }: ComponentStateBucket): void { - if (isInteractive) { - component.trigger('didUpdate'); - component.trigger('didRender'); - } + didUpdate({ component }: ComponentStateBucket): void { + component.trigger('didUpdate'); + component.trigger('didRender'); } getDestroyable(bucket: ComponentStateBucket): Nullable { diff --git a/packages/@ember/-internals/glimmer/lib/component-managers/root.ts b/packages/@ember/-internals/glimmer/lib/component-managers/root.ts index a0488f74488..5eeccf96390 100644 --- a/packages/@ember/-internals/glimmer/lib/component-managers/root.ts +++ b/packages/@ember/-internals/glimmer/lib/component-managers/root.ts @@ -33,7 +33,7 @@ class RootComponentManager extends CurlyComponentManager { _owner: Owner, _state: unknown, _args: Nullable, - { isInteractive }: Environment, + _env: Environment, dynamicScope: DynamicScope ) { let component = this.component; @@ -46,15 +46,9 @@ class RootComponentManager extends CurlyComponentManager { // We usually do this in the `didCreateElement`, but that hook doesn't fire for tagless components if (!hasWrappedElement) { - if (isInteractive) { - component.trigger('willRender'); - } - + component.trigger('willRender'); component._transitionTo('hasElement'); - - if (isInteractive) { - component.trigger('willInsertElement'); - } + component.trigger('willInsertElement'); } if (DEBUG) { @@ -66,8 +60,7 @@ class RootComponentManager extends CurlyComponentManager { null, CONSTANT_TAG, finalizer, - hasWrappedElement, - isInteractive + hasWrappedElement ); consumeTag(component[DIRTY_TAG]); diff --git a/packages/@ember/-internals/glimmer/lib/component.ts b/packages/@ember/-internals/glimmer/lib/component.ts index d66842da331..8664355e8b2 100644 --- a/packages/@ember/-internals/glimmer/lib/component.ts +++ b/packages/@ember/-internals/glimmer/lib/component.ts @@ -20,7 +20,7 @@ import { import { guidFor } from '@ember/-internals/utils'; import { assert } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; -import type { Environment, Template, TemplateFactory } from '@glimmer/interfaces'; +import type { Template, TemplateFactory } from '@glimmer/interfaces'; import { setInternalComponentManager } from '@glimmer/manager'; import { isUpdatableRef, updateRef } from '@glimmer/reference'; import { normalizeProperty } from '@glimmer/runtime'; @@ -34,27 +34,14 @@ import { IS_DISPATCHING_ATTRS, getComponentCapturedArgs, } from './component-managers/curly'; -import { hasDOM } from '@ember/-internals/browser-environment'; // Keep track of which component classes have already been processed for lazy event setup. let lazyEventsProcessed = new WeakMap>(); const EMPTY_ARRAY = Object.freeze([]); -/** - Determines if the element matches the specified selector. - - @private - @method matches - @param {DOMElement} el - @param {String} selector -*/ -const elMatches: typeof Element.prototype.matches | undefined = - typeof Element !== 'undefined' ? Element.prototype.matches : undefined; - function matches(el: Element, selector: string): boolean { - assert('cannot call `matches` in fastboot mode', elMatches !== undefined); - return elMatches.call(el, selector); + return Element.prototype.matches.call(el, selector); } /** @@ -939,7 +926,7 @@ class Component } } - if (DEBUG && eventDispatcher && this.renderer._isInteractive && this.tagName === '') { + if (DEBUG && eventDispatcher && this.tagName === '') { let eventNames = []; let events = eventDispatcher.finalEventNameMapping; @@ -997,17 +984,9 @@ class Component let owner = getOwner(this); assert('Component is unexpectedly missing an owner', owner); - if ((owner.lookup('-environment:main') as Environment)!.isInteractive) { - let dispatcher = owner.lookup('event_dispatcher:main'); - assert( - 'Expected dispatcher to be an EventDispatcher', - dispatcher instanceof EventDispatcher - ); - this.__dispatcher = dispatcher; - } else { - // In FastBoot we have no EventDispatcher. Set to null to not try again to look it up. - this.__dispatcher = null; - } + let dispatcher = owner.lookup('event_dispatcher:main'); + assert('Expected dispatcher to be an EventDispatcher', dispatcher instanceof EventDispatcher); + this.__dispatcher = dispatcher; } return this.__dispatcher; @@ -1445,45 +1424,30 @@ class Component @private */ appendTo(selector: string | Element | SimpleElement) { - let target; - - if (hasDOM) { - assert( - `Expected a selector or instance of Element`, - typeof selector === 'string' || selector instanceof Element - ); + assert( + `Expected a selector or instance of Element`, + typeof selector === 'string' || selector instanceof Element + ); - target = typeof selector === 'string' ? document.querySelector(selector) : selector; + let target = typeof selector === 'string' ? document.querySelector(selector) : selector; - assert(`You tried to append to (${selector}) but that isn't in the DOM`, target); - assert('You cannot append to an existing Ember.View.', !matches(target, '.ember-view')); - assert( - 'You cannot append to an existing Ember.View.', - (() => { - let node = target.parentNode; - while (node instanceof Element) { - if (matches(node, '.ember-view')) { - return false; - } - - node = node.parentNode; + assert(`You tried to append to (${selector}) but that isn't in the DOM`, target); + assert('You cannot append to an existing Ember.View.', !matches(target, '.ember-view')); + assert( + 'You cannot append to an existing Ember.View.', + (() => { + let node = target.parentNode; + while (node instanceof Element) { + if (matches(node, '.ember-view')) { + return false; } - return true; - })() - ); - } else { - target = selector; + node = node.parentNode; + } - assert( - `You tried to append to a selector string (${selector}) in an environment without a DOM`, - typeof target !== 'string' - ); - assert( - `You tried to append to a non-Element (${selector}) in an environment without a DOM`, - typeof target.appendChild === 'function' - ); - } + return true; + })() + ); // SAFETY: SimpleElement is supposed to be a subset of Element so this _should_ be safe. // However, the types are more specific in some places which necessitates the `as`. diff --git a/packages/@ember/-internals/glimmer/lib/components/input.ts b/packages/@ember/-internals/glimmer/lib/components/input.ts index 6401d7a2864..afde2674343 100644 --- a/packages/@ember/-internals/glimmer/lib/components/input.ts +++ b/packages/@ember/-internals/glimmer/lib/components/input.ts @@ -1,7 +1,6 @@ /** @module @ember/component */ -import { hasDOM } from '@ember/-internals/browser-environment'; import { type Opaque } from '@ember/-internals/utility-types'; import { assert, warn } from '@ember/debug'; import { action } from '@ember/object'; @@ -11,36 +10,34 @@ import InputTemplate from '../templates/input'; import AbstractInput, { valueFrom } from './abstract-input'; import { type OpaqueInternalComponentConstructor, opaquify } from './internal'; -let isValidInputType: (type: string) => boolean; +const INPUT_TYPES: Record = Object.create(null); +INPUT_TYPES[''] = false; +INPUT_TYPES['text'] = true; +INPUT_TYPES['checkbox'] = true; -if (hasDOM) { - const INPUT_TYPES: Record = Object.create(null); - const INPUT_ELEMENT = document.createElement('input'); - - INPUT_TYPES[''] = false; - INPUT_TYPES['text'] = true; - INPUT_TYPES['checkbox'] = true; - - isValidInputType = (type: string) => { - let isValid = INPUT_TYPES[type]; - - if (isValid === undefined) { - try { - INPUT_ELEMENT.type = type; - isValid = INPUT_ELEMENT.type === type; - } catch (_e) { - isValid = false; - } finally { - INPUT_ELEMENT.type = 'text'; - } +let _inputElement: HTMLInputElement | undefined; +function inputElement(): HTMLInputElement { + return (_inputElement ??= document.createElement('input')); +} - INPUT_TYPES[type] = isValid; +function isValidInputType(type: string): boolean { + let isValid = INPUT_TYPES[type]; + + if (isValid === undefined) { + let probe = inputElement(); + try { + probe.type = type; + isValid = probe.type === type; + } catch (_e) { + isValid = false; + } finally { + probe.type = 'text'; } - return isValid; - }; -} else { - isValidInputType = (type: string) => type !== ''; + INPUT_TYPES[type] = isValid; + } + + return isValid; } /** diff --git a/packages/@ember/-internals/glimmer/lib/environment.ts b/packages/@ember/-internals/glimmer/lib/environment.ts index 2ebcbdea8e5..0254542b186 100644 --- a/packages/@ember/-internals/glimmer/lib/environment.ts +++ b/packages/@ember/-internals/glimmer/lib/environment.ts @@ -1,6 +1,5 @@ import { ENV } from '@ember/-internals/environment'; import { get, set, _getProp, _setProp } from '@ember/-internals/metal'; -import type { InternalOwner } from '@ember/-internals/owner'; import { getDebugName } from '@ember/-internals/utils'; import { constructStyleDeprecationMessage } from '@ember/-internals/views'; import { assert, deprecate, warn } from '@ember/debug'; @@ -127,10 +126,5 @@ const VM_ASSERTION_OVERRIDES: { id: string; message: string }[] = []; export class EmberEnvironmentDelegate implements EnvironmentDelegate { public enableDebugTooling: boolean = ENV._DEBUG_RENDER_TREE; - constructor( - public owner: InternalOwner, - public isInteractive: boolean - ) {} - onTransactionCommit(): void {} } diff --git a/packages/@ember/-internals/glimmer/lib/renderer.ts b/packages/@ember/-internals/glimmer/lib/renderer.ts index cfc74e0e423..0fc9a0e74ca 100644 --- a/packages/@ember/-internals/glimmer/lib/renderer.ts +++ b/packages/@ember/-internals/glimmer/lib/renderer.ts @@ -50,7 +50,6 @@ import { CURRENT_TAG, validateTag, valueForTag } from '@glimmer/validator'; import type { SimpleDocument, SimpleElement, SimpleNode } from '@simple-dom/interface'; import RSVP from 'rsvp'; import type Component from './component'; -import { hasDOM } from '../../browser-environment'; import type ClassicComponent from './component'; import { BOUNDS } from './component-managers/curly'; import { createRootOutlet } from './component-managers/outlet'; @@ -389,7 +388,6 @@ class RendererState { return { roots: this.#roots, inRenderTransaction: this.#inRenderTransaction, - isInteractive: this.isInteractive, }; } @@ -413,10 +411,6 @@ class RendererState { return this.context.env; } - get isInteractive(): boolean { - return this.#data.context.env.isInteractive; - } - renderRoot(root: RootState, renderer: BaseRenderer): RootState { let roots = this.#roots; @@ -613,10 +607,6 @@ export function renderComponent( * Optionally configure the rendering environment */ env?: { - /** - * When false, modifiers will not run. - */ - isInteractive?: boolean; /** * All other options are forwarded to the underlying renderer. * (its API is currently private and out of scope for this RFC, @@ -647,11 +637,7 @@ export function renderComponent( // which can cause tracking frame conflicts let renderer = RENDERER_CACHE.get(owner); if (!renderer) { - renderer = BaseRenderer.strict(owner, document, { - ...env, - isInteractive: env?.isInteractive ?? true, - hasDOM: env && 'hasDOM' in env ? Boolean(env?.['hasDOM']) : true, - }); + renderer = BaseRenderer.strict(owner, document); RENDERER_CACHE.set(owner, renderer); } @@ -718,29 +704,13 @@ const RENDER_CACHE = new WeakMap(); const RENDERER_CACHE = new WeakMap(); class BaseRenderer { - static strict( - owner: object, - document: SimpleDocument | Document, - options: { isInteractive: boolean; hasDOM?: boolean } - ) { - return new BaseRenderer( - owner, - { hasDOM: hasDOM, ...options }, - document as SimpleDocument, - new ResolverImpl(), - clientBuilder - ); + static strict(owner: object, document: SimpleDocument | Document) { + return new BaseRenderer(owner, document as SimpleDocument, new ResolverImpl(), clientBuilder); } readonly state: RendererState; - constructor( - owner: object, - envOptions: { isInteractive: boolean; hasDOM: boolean }, - document: SimpleDocument, - resolver: Resolver, - builder: IBuilder - ) { + constructor(owner: object, document: SimpleDocument, resolver: Resolver, builder: IBuilder) { let sharedArtifacts = artifacts(); /** @@ -750,7 +720,7 @@ class BaseRenderer { * But for actual ember apps, you *need* to implement everything * an app needs (which will actually change and become less over time) */ - let env = new EmberEnvironmentDelegate(owner as InternalOwner, envOptions.isInteractive); + let env = new EmberEnvironmentDelegate(); let options = runtimeOptions({ document }, env, sharedArtifacts, resolver); let context = new EvaluationContextImpl( sharedArtifacts, @@ -808,18 +778,8 @@ class BaseRenderer { } export class Renderer extends BaseRenderer { - static strict( - owner: object, - document: SimpleDocument | Document, - options: { isInteractive: boolean; hasDOM?: boolean } - ): BaseRenderer { - return new BaseRenderer( - owner, - { hasDOM: hasDOM, ...options }, - document as SimpleDocument, - new ResolverImpl(), - clientBuilder - ); + static strict(owner: object, document: SimpleDocument | Document): BaseRenderer { + return new BaseRenderer(owner, document as SimpleDocument, new ResolverImpl(), clientBuilder); } private _rootTemplate: Template; @@ -830,25 +790,20 @@ export class Renderer extends BaseRenderer { let owner = getOwner(props); assert('Renderer is unexpectedly missing an owner', owner); let document = owner.lookup('service:-document') as SimpleDocument; - let env = owner.lookup('-environment:main') as { - isInteractive: boolean; - hasDOM: boolean; - }; let rootTemplate = owner.lookup(P`template:-root`) as TemplateFactory; let builder = owner.lookup('service:-dom-builder') as IBuilder; - return new this(owner, document, env, rootTemplate, _viewRegistry, builder); + return new this(owner, document, rootTemplate, _viewRegistry, builder); } constructor( owner: InternalOwner, document: SimpleDocument, - env: { isInteractive: boolean; hasDOM: boolean }, rootTemplate: TemplateFactory, viewRegistry: ViewRegistry, builder = clientBuilder, resolver = new ResolverImpl() ) { - super(owner, env, document, resolver, builder); + super(owner, document, resolver, builder); this._rootTemplate = rootTemplate(owner); this._viewRegistry = viewRegistry || owner.lookup('-view-registry:main'); } @@ -946,9 +901,7 @@ export class Renderer extends BaseRenderer { this.cleanupRootFor(view); - if (this.state.isInteractive) { - view.trigger('didDestroyElement'); - } + view.trigger('didDestroyElement'); } get _roots() { @@ -959,10 +912,6 @@ export class Renderer extends BaseRenderer { return this.state.debug.inRenderTransaction; } - get _isInteractive() { - return this.state.debug.isInteractive; - } - get _context() { return this.state.context; } @@ -981,13 +930,7 @@ export class Renderer extends BaseRenderer { } getElement(component: View): Nullable { - if (this._isInteractive) { - return getViewElement(component); - } else { - throw new Error( - 'Accessing `this.element` is not allowed in non-interactive environments (such as FastBoot).' - ); - } + return getViewElement(component); } getBounds(component: View): { diff --git a/packages/@ember/-internals/glimmer/lib/utils/curly-component-state-bucket.ts b/packages/@ember/-internals/glimmer/lib/utils/curly-component-state-bucket.ts index 5a4fb529a6f..326c05840be 100644 --- a/packages/@ember/-internals/glimmer/lib/utils/curly-component-state-bucket.ts +++ b/packages/@ember/-internals/glimmer/lib/utils/curly-component-state-bucket.ts @@ -30,8 +30,7 @@ export default class ComponentStateBucket { public args: CapturedNamedArguments | null, public argsTag: Tag, public finalizer: Finalizer, - public hasWrappedElement: boolean, - public isInteractive: boolean + public hasWrappedElement: boolean ) { this.classRef = null; this.argsRevision = args === null ? 0 : valueForTag(argsTag); @@ -42,20 +41,18 @@ export default class ComponentStateBucket { } willDestroy(): void { - let { component, isInteractive } = this; + let { component } = this; - if (isInteractive) { - beginUntrackFrame(); - component.trigger('willDestroyElement'); - component.trigger('willClearRender'); - endUntrackFrame(); + beginUntrackFrame(); + component.trigger('willDestroyElement'); + component.trigger('willClearRender'); + endUntrackFrame(); - let element = getViewElement(component); + let element = getViewElement(component); - if (element) { - clearElementView(element); - clearViewElement(component); - } + if (element) { + clearElementView(element); + clearViewElement(component); } component.renderer.unregister(component); diff --git a/packages/@ember/-internals/glimmer/lib/views/outlet.ts b/packages/@ember/-internals/glimmer/lib/views/outlet.ts index 7cc9be26e73..618d1741b6b 100644 --- a/packages/@ember/-internals/glimmer/lib/views/outlet.ts +++ b/packages/@ember/-internals/glimmer/lib/views/outlet.ts @@ -16,8 +16,6 @@ import type { Renderer } from '../renderer'; import type { OutletState } from '../utils/outlet'; export interface BootEnvironment { - hasDOM: boolean; - isInteractive: boolean; _renderMode?: string; options: BootOptions; } @@ -94,13 +92,7 @@ export default class OutletView { } appendTo(selector: string | SimpleElement): void { - let target; - - if (this._environment.hasDOM) { - target = typeof selector === 'string' ? document.querySelector(selector) : selector; - } else { - target = selector; - } + let target = typeof selector === 'string' ? document.querySelector(selector) : selector; let renderer = this.owner.lookup('renderer:-dom') as Renderer; diff --git a/packages/@ember/-internals/glimmer/tests/integration/components/life-cycle-test.js b/packages/@ember/-internals/glimmer/tests/integration/components/life-cycle-test.js index 911f26a44e4..24ee96dc1f7 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/components/life-cycle-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/components/life-cycle-test.js @@ -27,16 +27,6 @@ class LifeCycleHooksTest extends RenderingTestCase { } } - get isInteractive() { - return true; - } - - getBootOptions() { - return { - isInteractive: this.isInteractive, - }; - } - /* abstract */ get ComponentClass() { throw new Error('Not implemented: `ComponentClass`'); @@ -66,13 +56,8 @@ class LifeCycleHooksTest extends RenderingTestCase { .sort() .filter((id) => id !== topLevelId); - if (this.isInteractive) { - let expected = this.componentRegistry.sort(); - - this.assert.deepEqual(actual, expected, 'registered views - ' + label); - } else { - this.assert.deepEqual(actual, [], 'no views should be registered for non-interactive mode'); - } + let expected = this.componentRegistry.sort(); + this.assert.deepEqual(actual, expected, 'registered views - ' + label); } registerComponent(name, { template = null }) { @@ -113,24 +98,17 @@ class LifeCycleHooksTest extends RenderingTestCase { `element should be present on ${instance} during ${hookName}` ); - if (this.isInteractive) { - this.assert.ok( - instance.element, - `this.element should be present on ${instance} during ${hookName}` - ); - this.assert.equal( - document.body.contains(instance.element), - inDOM, - `element for ${instance} ${ - inDOM ? 'should' : 'should not' - } be in the DOM during ${hookName}` - ); - } else { - this.assert.throws( - () => instance.element, - /Accessing `this.element` is not allowed in non-interactive environments/ - ); - } + this.assert.ok( + instance.element, + `this.element should be present on ${instance} during ${hookName}` + ); + this.assert.equal( + document.body.contains(instance.element), + inDOM, + `element for ${instance} ${ + inDOM ? 'should' : 'should not' + } be in the DOM during ${hookName}` + ); }; let assertNoElement = (hookName, instance) => { @@ -140,18 +118,11 @@ class LifeCycleHooksTest extends RenderingTestCase { `element should not be present in ${hookName}` ); - if (this.isInteractive) { - this.assert.strictEqual( - instance.element, - null, - `this.element should not be present in ${hookName}` - ); - } else { - this.assert.throws( - () => instance.element, - /Accessing `this.element` is not allowed in non-interactive environments/ - ); - } + this.assert.strictEqual( + instance.element, + null, + `this.element should not be present in ${hookName}` + ); }; let assertState = (hookName, expectedState, instance) => { @@ -162,8 +133,6 @@ class LifeCycleHooksTest extends RenderingTestCase { ); }; - let { isInteractive } = this; - let ComponentClass = class extends this.ComponentClass { init() { super.init(...arguments); @@ -192,12 +161,7 @@ class LifeCycleHooksTest extends RenderingTestCase { assertState('didReceiveAttrs', 'preRender', this); } else { assertElement('didReceiveAttrs', this); - - if (isInteractive) { - assertState('didReceiveAttrs', 'inDOM', this); - } else { - assertState('didReceiveAttrs', 'hasElement', this); - } + assertState('didReceiveAttrs', 'inDOM', this); } } @@ -238,12 +202,7 @@ class LifeCycleHooksTest extends RenderingTestCase { didUpdateAttrs(options) { pushHook('didUpdateAttrs', options); assertParentView('didUpdateAttrs', this); - - if (isInteractive) { - assertState('didUpdateAttrs', 'inDOM', this); - } else { - assertState('didUpdateAttrs', 'hasElement', this); - } + assertState('didUpdateAttrs', 'inDOM', this); } willUpdate(options) { @@ -294,9 +253,8 @@ class LifeCycleHooksTest extends RenderingTestCase { this.owner.register(`component:${name}`, ComponentClass); } - assertHooks({ label, interactive, nonInteractive }) { - let rawHooks = this.isInteractive ? interactive : nonInteractive; - let hooks = rawHooks.map((raw) => hook(...raw)); + assertHooks({ label, interactive }) { + let hooks = interactive.map((raw) => hook(...raw)); this.assert.deepEqual(json(this.hooks), json(hooks), label); this.hooks = []; } @@ -1073,38 +1031,25 @@ class LifeCycleHooksTest extends RenderingTestCase { this.assertText('Item: 1Item: 2Item: 3Item: 4Item: 5'); this.assertRegisteredViews('intial render'); - let initialHooks = () => { - let ret = [ - ['an-item', 'init'], - ['an-item', 'on(init)'], - ['an-item', 'didReceiveAttrs'], - ]; - if (this.isInteractive) { - ret.push(['an-item', 'willRender'], ['an-item', 'willInsertElement']); - } - ret.push( - ['nested-item', 'init'], - ['nested-item', 'on(init)'], - ['nested-item', 'didReceiveAttrs'] - ); - if (this.isInteractive) { - ret.push(['nested-item', 'willRender'], ['nested-item', 'willInsertElement']); - } - return ret; - }; - - let initialAfterRenderHooks = () => { - if (this.isInteractive) { - return [ - ['nested-item', 'didInsertElement'], - ['nested-item', 'didRender'], - ['an-item', 'didInsertElement'], - ['an-item', 'didRender'], - ]; - } else { - return []; - } - }; + let initialHooks = () => [ + ['an-item', 'init'], + ['an-item', 'on(init)'], + ['an-item', 'didReceiveAttrs'], + ['an-item', 'willRender'], + ['an-item', 'willInsertElement'], + ['nested-item', 'init'], + ['nested-item', 'on(init)'], + ['nested-item', 'didReceiveAttrs'], + ['nested-item', 'willRender'], + ['nested-item', 'willInsertElement'], + ]; + + let initialAfterRenderHooks = () => [ + ['nested-item', 'didInsertElement'], + ['nested-item', 'didRender'], + ['an-item', 'didInsertElement'], + ['an-item', 'didRender'], + ]; this.assertHooks({ label: 'after initial render', @@ -1142,17 +1087,11 @@ class LifeCycleHooksTest extends RenderingTestCase { ], }); - // TODO: Is this correct? Should childViews be populated in non-interactive mode? - if (this.isInteractive) { - this.assert.equal(this.component.childViews.length, 5, 'childViews precond'); - } + this.assert.equal(this.component.childViews.length, 5, 'childViews precond'); runTask(() => set(this.context, 'items', [])); - // TODO: Is this correct? Should childViews be populated in non-interactive mode? - if (this.isInteractive) { - this.assert.equal(this.component.childViews.length, 1, 'childViews updated'); - } + this.assert.equal(this.component.childViews.length, 1, 'childViews updated'); this.assertText('Nothing to see here'); @@ -1299,51 +1238,16 @@ class CurlyComponentsTest extends LifeCycleHooksTest { } } -moduleFor( - 'Components test: interactive lifecycle hooks (curly components)', - class extends CurlyComponentsTest { - get isInteractive() { - return true; - } - } -); - -moduleFor( - 'Components test: non-interactive lifecycle hooks (curly components)', - class extends CurlyComponentsTest { - get isInteractive() { - return false; - } - } -); - -moduleFor( - 'Components test: interactive lifecycle hooks (tagless curly components)', - class extends CurlyComponentsTest { - get ComponentClass() { - return class extends Component { - tagName = ''; - }; - } - - get isInteractive() { - return true; - } - } -); +moduleFor('Components test: lifecycle hooks (curly components)', CurlyComponentsTest); moduleFor( - 'Components test: non-interactive lifecycle hooks (tagless curly components)', + 'Components test: lifecycle hooks (tagless curly components)', class extends CurlyComponentsTest { get ComponentClass() { return class extends Component { tagName = ''; }; } - - get isInteractive() { - return false; - } } ); diff --git a/packages/@ember/-internals/glimmer/tests/integration/components/render-component-test.ts b/packages/@ember/-internals/glimmer/tests/integration/components/render-component-test.ts index 85319efad4c..b328e75720a 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/components/render-component-test.ts +++ b/packages/@ember/-internals/glimmer/tests/integration/components/render-component-test.ts @@ -61,7 +61,7 @@ class RenderComponentTestCase extends AbstractStrictTestCase { const result = renderComponent(component, { owner, args: 'args' in options ? options.args : {}, - env: { document: document, isInteractive: true, hasDOM: true }, + env: { document: document }, into: this.element, }); this.component = { diff --git a/packages/@ember/-internals/glimmer/tests/integration/custom-modifier-manager-test.js b/packages/@ember/-internals/glimmer/tests/integration/custom-modifier-manager-test.js index 0cf13fec4f2..537d87f9cb1 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/custom-modifier-manager-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/custom-modifier-manager-test.js @@ -548,66 +548,3 @@ moduleFor( } } ); - -moduleFor( - 'Rendering test: non-interactive custom modifiers', - class extends RenderingTestCase { - getBootOptions() { - return { isInteractive: false }; - } - - [`@test doesn't trigger lifecycle hooks when non-interactive: modifierCapabilities('3.22')`]( - assert - ) { - class CustomModifierManager { - capabilities = modifierCapabilities('3.22'); - - constructor(owner) { - this.owner = owner; - } - - createModifier(Modifier, args) { - return Modifier.create(args); - } - - installModifier(instance, element, args) { - instance.element = element; - let { positional, named } = args; - instance.didInsertElement(positional, named); - } - - updateModifier(instance, args) { - let { positional, named } = args; - instance.didUpdate(positional, named); - } - - destroyModifier(instance) { - instance.willDestroyElement(); - } - } - let ModifierClass = setModifierManager( - (owner) => { - return new CustomModifierManager(owner); - }, - class extends EmberObject { - didInsertElement() { - assert.ok(false); - } - didUpdate() { - assert.ok(false); - } - willDestroyElement() { - assert.ok(false); - } - } - ); - - this.registerModifier('foo-bar', ModifierClass); - - this.render('

hello world

'); - runTask(() => this.context.set('baz', 'Hello')); - - this.assertHTML('

hello world

'); - } - } -); diff --git a/packages/@ember/-internals/glimmer/tests/integration/modifiers/on-test.js b/packages/@ember/-internals/glimmer/tests/integration/modifiers/on-test.js index a0f7d8b95d2..feabfbf2af4 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/modifiers/on-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/modifiers/on-test.js @@ -1,12 +1,9 @@ import { moduleFor, RenderingTestCase, runTask } from 'internal-test-helpers'; -import { getInternalModifierManager, setComponentTemplate } from '@glimmer/manager'; +import { getInternalModifierManager } from '@glimmer/manager'; import { on } from '@glimmer/runtime'; -import { precompileTemplate } from '@ember/template-compilation'; import { DEBUG } from '@glimmer/env'; -import { Component } from '../../utils/helpers'; - moduleFor( '{{on}} Modifier', class extends RenderingTestCase { @@ -271,63 +268,3 @@ moduleFor( } } ); - -moduleFor( - 'Rendering test: non-interactive `on` modifier', - class extends RenderingTestCase { - getBootOptions() { - return { isInteractive: false }; - } - - beforeEach() { - // might error if getOnManagerInstance fails - this.startingCounters = this.getOnManagerInstance().counters; - } - - getOnManagerInstance() { - // leveraging private APIs, this can be deleted if these APIs change - // but it has been useful to verify some internal details - return getInternalModifierManager(on); - } - - assertCounts(expected) { - let { counters } = this.getOnManagerInstance(); - - this.assert.deepEqual( - counters, - { - adds: expected.adds + this.startingCounters.adds, - removes: expected.removes + this.startingCounters.removes, - }, - `counters have incremented by ${JSON.stringify(expected)}` - ); - } - - [`@test doesn't trigger lifecycle hooks when non-interactive`](assert) { - this.owner.register( - 'component:foo-bar2', - setComponentTemplate( - precompileTemplate(``), - class extends Component { - tagName = ''; - fire() { - assert.ok(false); - } - } - ) - ); - - this.render('{{#if this.showButton}}{{/if}}', { - showButton: true, - }); - this.assertHTML(''); - this.assertCounts({ adds: 0, removes: 0 }); - - this.$('button').click(); - - runTask(() => this.context.set('showButton', false)); - - this.assertCounts({ adds: 0, removes: 0 }); - } - } -); diff --git a/packages/@ember/-internals/glimmer/tests/unit/outlet-test.js b/packages/@ember/-internals/glimmer/tests/unit/outlet-test.js index f694df1c07a..883ac0c63e7 100644 --- a/packages/@ember/-internals/glimmer/tests/unit/outlet-test.js +++ b/packages/@ember/-internals/glimmer/tests/unit/outlet-test.js @@ -8,7 +8,7 @@ moduleFor( class extends AbstractTestCase { ['@test render in the render queue'](assert) { let didAppendOutletView = 0; - let expectedOutlet = '#foo.bar'; + let expectedOutlet = document.createElement('div'); let renderer = { appendOutletView(view, target) { diff --git a/packages/@ember/-internals/package.json b/packages/@ember/-internals/package.json index cc947bbb6b5..bc9c683bef6 100644 --- a/packages/@ember/-internals/package.json +++ b/packages/@ember/-internals/package.json @@ -5,7 +5,6 @@ "type": "module", "private": true, "exports": { - "./browser-environment": "./browser-environment/index.ts", "./container": "./container/index.ts", "./deprecations": "./deprecations/index.ts", "./environment": "./environment/index.ts", diff --git a/packages/@ember/-internals/runtime/tests/core/is_array_test.js b/packages/@ember/-internals/runtime/tests/core/is_array_test.js index 6416fa4d421..061ce188123 100644 --- a/packages/@ember/-internals/runtime/tests/core/is_array_test.js +++ b/packages/@ember/-internals/runtime/tests/core/is_array_test.js @@ -1,7 +1,6 @@ import { A as emberA, isArray } from '@ember/array'; import ArrayProxy from '@ember/array/proxy'; import EmberObject from '@ember/object'; -import { window } from '@ember/-internals/browser-environment'; import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; const global = this; diff --git a/packages/@ember/-internals/utils/tests/checkHasSuper_test.js b/packages/@ember/-internals/utils/tests/checkHasSuper_test.js index a4fdd5c6f10..19e3639f46a 100644 --- a/packages/@ember/-internals/utils/tests/checkHasSuper_test.js +++ b/packages/@ember/-internals/utils/tests/checkHasSuper_test.js @@ -1,7 +1,10 @@ -import { isChrome, isFirefox } from '@ember/-internals/browser-environment'; import { checkHasSuper } from '..'; import { moduleFor, AbstractTestCase as TestCase } from 'internal-test-helpers'; +const ua = window.navigator.userAgent; +const isChrome = typeof chrome === 'object' && !(typeof opera === 'object'); +const isFirefox = /Firefox|FxiOS/.test(ua); + // Only run this test on browsers that we are certain should have function // source available. This allows the test suite to continue to pass on other // platforms that correctly (for them) fall back to the "always wrap" code. diff --git a/packages/@ember/-internals/views/lib/system/event_dispatcher.ts b/packages/@ember/-internals/views/lib/system/event_dispatcher.ts index 1a023ff6cbd..2d819f1cff2 100644 --- a/packages/@ember/-internals/views/lib/system/event_dispatcher.ts +++ b/packages/@ember/-internals/views/lib/system/event_dispatcher.ts @@ -1,9 +1,7 @@ -import { getOwner } from '@ember/-internals/owner'; import { assert } from '@ember/debug'; import { get, set } from '@ember/-internals/metal'; import EmberObject from '@ember/object'; import { getElementView } from './utils'; -import type { BootEnvironment } from '@ember/-internals/glimmer'; import type Component from '@ember/component'; /** @@ -121,19 +119,6 @@ export default class EventDispatcher extends EmberObject { @param addedEvents {Object} */ setup(addedEvents: Record, _rootElement: string | Element | null): void { - assert( - 'EventDispatcher should never be setup in fastboot mode. Please report this as an Ember bug.', - (() => { - let owner = getOwner(this); - assert('[BUG] Missing owner', owner); - - // SAFETY: This is not guaranteed to be safe, but this is what we expect to be returned. - let environment = owner.lookup('-environment:main') as BootEnvironment; - - return environment.isInteractive; - })() - ); - let events: Record = (this.finalEventNameMapping = { ...get(this, 'events'), ...addedEvents, diff --git a/packages/@ember/application/index.ts b/packages/@ember/application/index.ts index dc6d923835a..ded2d7f9983 100644 --- a/packages/@ember/application/index.ts +++ b/packages/@ember/application/index.ts @@ -5,7 +5,6 @@ import { getOwner as actualGetOwner, setOwner as actualSetOwner } from '@ember/owner'; import { dictionary } from '@ember/-internals/utils'; import { ENV } from '@ember/-internals/environment'; -import { hasDOM } from '@ember/-internals/browser-environment'; import { assert } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; import { join, once, run, schedule } from '@ember/runloop'; @@ -358,11 +357,10 @@ class Application extends Engine { super.init(properties); this.rootElement ??= 'body'; - this._document ??= null; + this._document ??= window.document; this.eventDispatcher ??= null; this.customEvents ??= null; this.autoboot ??= true; - this._document ??= hasDOM ? window.document : null; if (DEBUG) { if (ENV.LOG_VERSION) { diff --git a/packages/@ember/application/instance.ts b/packages/@ember/application/instance.ts index 47ecb342bd4..8fc4d40e113 100644 --- a/packages/@ember/application/instance.ts +++ b/packages/@ember/application/instance.ts @@ -3,7 +3,6 @@ */ import { get, set } from '@ember/object'; -import * as environment from '@ember/-internals/browser-environment'; import EngineInstance from '@ember/engine/instance'; import type { BootOptions } from '@ember/engine/instance'; import type Application from '@ember/application'; @@ -110,9 +109,7 @@ class ApplicationInstance extends EngineInstance { this.application.runInstanceInitializers(this); - if (options.isInteractive) { - this.setupEventDispatcher(); - } + this.setupEventDispatcher(); this._booted = true; @@ -331,17 +328,6 @@ class ApplicationInstance extends EngineInstance { @public */ class _BootOptions { - /** - Interactive mode: whether we need to set up event delegation and invoke - lifecycle callbacks on Components. - - @property isInteractive - @type boolean - @default auto-detected - @private - */ - readonly isInteractive: boolean; - /** @property _renderMode @type string @@ -350,32 +336,6 @@ class _BootOptions { */ readonly _renderMode?: string; - /** - Run in a full browser environment. - - When this flag is set to `false`, it will disable most browser-specific - and interactive features. Specifically: - - * It does not use `jQuery` to append the root view; the `rootElement` - (either specified as a subsequent option or on the application itself) - must already be an `Element` in the given `document` (as opposed to a - string selector). - - * It does not set up an `EventDispatcher`. - - * It does not run any `Component` lifecycle hooks (such as `didInsertElement`). - - * It sets the `location` option to `"none"`. (If you would like to use - the location adapter specified in the app's router instead, you can also - specify `{ location: null }` to specifically opt-out.) - - @property isBrowser - @type boolean - @default auto-detected - @public - */ - readonly isBrowser: boolean; - /** If present, overrides the router's `location` property with this value. This is useful for environments where trying to modify the @@ -406,34 +366,18 @@ class _BootOptions { If present, render into the given `Document` object instead of the global `window.document` object. - In practice, this is only useful in non-browser environment or in - non-interactive mode, because Ember's `jQuery` dependency is - implicitly bound to the current document, causing event delegation - to not work properly when the app is rendered into a foreign - document object (such as an iframe's `contentDocument`). - - In non-browser mode, this could be a "`Document`-like" object as - Ember only interact with a small subset of the DOM API in non- - interactive mode. While the exact requirements have not yet been - formalized, the `SimpleDOM` library's implementation is known to - work. - @property document @type Document @default the global `document` object @public */ - readonly document: Document | null; + readonly document: Document; /** If present, overrides the application's `rootElement` property on the instance. This is useful for testing environment, where you might want to append the root view to a fixture area. - In non-browser mode, because Ember does not have access to jQuery, - this options must be specified as a DOM `Element` object instead of - a selector string. - See the documentation on `Application`'s `rootElement` for details. @@ -445,60 +389,21 @@ class _BootOptions { readonly rootElement?: string | SimpleElement; constructor(options: BootOptions = {}) { - this.isInteractive = Boolean(environment.hasDOM); // This default is overridable below this._renderMode = options._renderMode; - - if (options.isBrowser !== undefined) { - this.isBrowser = Boolean(options.isBrowser); - } else { - this.isBrowser = Boolean(environment.hasDOM); - } - - if (!this.isBrowser) { - this.isInteractive = false; - this.location = 'none'; - } - - if (options.shouldRender !== undefined) { - this.shouldRender = Boolean(options.shouldRender); - } else { - this.shouldRender = true; - } - - if (!this.shouldRender) { - this.isInteractive = false; - } - - if (options.document) { - this.document = options.document; - } else { - this.document = typeof document !== 'undefined' ? document : null; - } + this.shouldRender = options.shouldRender !== undefined ? Boolean(options.shouldRender) : true; + this.document = options.document ?? document; if (options.rootElement) { this.rootElement = options.rootElement; } - // Set these options last to give the user a chance to override the - // defaults from the "combo" options like `isBrowser` (although in - // practice, the resulting combination is probably invalid) - - if (options.location !== undefined) { + if (options.location !== undefined && options.location !== null) { this.location = options.location; } - - if (options.isInteractive !== undefined) { - this.isInteractive = Boolean(options.isInteractive); - } } toEnvironment(): BootEnvironment { - // Do we really want to assign all of this!? return { - ...environment, - // For compatibility with existing code - hasDOM: this.isBrowser, - isInteractive: this.isInteractive, _renderMode: this._renderMode, options: this, }; diff --git a/packages/@ember/application/tests/visit_test.js b/packages/@ember/application/tests/visit_test.js index 2abc2d2cfc0..9a977cba75f 100644 --- a/packages/@ember/application/tests/visit_test.js +++ b/packages/@ember/application/tests/visit_test.js @@ -497,59 +497,6 @@ moduleFor( }); } - [`@test visit() does not setup the event_dispatcher:main if isInteractive is false (with Engines) GH#15615`]( - assert - ) { - assert.expect(3); - - this.router.map(function () { - this.mount('blog'); - }); - - this.add('template:application', precompileTemplate('

Hello world

{{outlet}}')); - this.add('event_dispatcher:main', { - create() { - throw new Error('should not happen!'); - }, - }); - - // Register engine - let BlogEngine = class extends Engine { - Resolver = ModuleBasedTestResolver; - - init(...args) { - super.init(...args); - this.register('template:application', precompileTemplate('{{cache-money}}')); - this.register( - 'component:cache-money', - setComponentTemplate( - precompileTemplate(`

Dis cache money

`), - class extends Component {} - ) - ); - } - }; - this.add('engine:blog', BlogEngine); - - // Register engine route map - let BlogMap = function () {}; - this.add('route-map:blog', BlogMap); - - this.assertEmptyFixture(); - - return this.visit('/blog', { isInteractive: false }).then((instance) => { - assert.ok( - instance instanceof ApplicationInstance, - 'promise is resolved with an ApplicationInstance' - ); - assert.strictEqual( - this.element.querySelector('p').textContent, - 'Dis cache money', - 'Engine component is resolved' - ); - }); - } - [`@test visit() on engine resolves engine component`](assert) { assert.expect(2); diff --git a/packages/@ember/application/type-tests/index.test.ts b/packages/@ember/application/type-tests/index.test.ts index ac786852da8..ec736582cf9 100644 --- a/packages/@ember/application/type-tests/index.test.ts +++ b/packages/@ember/application/type-tests/index.test.ts @@ -32,7 +32,6 @@ expectTypeOf(app.ready()).toEqualTypeOf(); expectTypeOf(app.reset()).toEqualTypeOf(); let bootOptions = { - isBrowser: true, shouldRender: false, document: window.document, rootElement: '#ember-application', @@ -41,10 +40,6 @@ let bootOptions = { app.visit('/my-app', bootOptions); -app.visit('/my-app', { isBrowser: true }); - -// @ts-expect-error Incorrect type -app.visit('/my-app', { isBrowser: 1 }); // @ts-expect-error Incorrect type app.visit('/my-app', { shouldRender: 1 }); // @ts-expect-error Incorrect type diff --git a/packages/@ember/application/type-tests/instance.test.ts b/packages/@ember/application/type-tests/instance.test.ts index 65fac1fc6b6..e29e9bc51de 100644 --- a/packages/@ember/application/type-tests/instance.test.ts +++ b/packages/@ember/application/type-tests/instance.test.ts @@ -37,7 +37,6 @@ instance.hasRegistration(); expectTypeOf(instance.boot()).resolves.toEqualTypeOf(); const bootOptions: BootOptions = { - isBrowser: true, shouldRender: false, document: window.document, rootElement: '#ember-application', @@ -46,10 +45,6 @@ const bootOptions: BootOptions = { instance.boot(bootOptions); -instance.boot({ isBrowser: true }); - -// @ts-expect-error Incorrect type -instance.boot({ isBrowser: 1 }); // @ts-expect-error Incorrect type instance.boot({ shouldRender: 1 }); // @ts-expect-error Incorrect type diff --git a/packages/@ember/debug/index.ts b/packages/@ember/debug/index.ts index 522c0109e78..800cd73ab60 100644 --- a/packages/@ember/debug/index.ts +++ b/packages/@ember/debug/index.ts @@ -1,4 +1,8 @@ -import { isChrome, isFirefox } from '@ember/-internals/browser-environment'; +declare const chrome: unknown; +declare const opera: unknown; +const _hasWindow = typeof window !== 'undefined'; +const isChrome = _hasWindow && typeof chrome === 'object' && !(typeof opera === 'object'); +const isFirefox = _hasWindow && /Firefox|FxiOS/.test(window.navigator.userAgent); import { ENV } from '@ember/-internals/environment'; import type { AnyFn } from '@ember/-internals/utility-types'; import { DEBUG } from '@glimmer/env'; diff --git a/packages/@ember/engine/instance.ts b/packages/@ember/engine/instance.ts index 097b5c6ca6e..e3a8fd8f00d 100644 --- a/packages/@ember/engine/instance.ts +++ b/packages/@ember/engine/instance.ts @@ -18,13 +18,10 @@ import type { BootEnvironment } from '@ember/-internals/glimmer'; import type { SimpleElement } from '@simple-dom/interface'; export interface BootOptions { - isBrowser?: boolean; shouldRender?: boolean; document?: Document | null; rootElement?: string | SimpleElement | null; location?: string | null; - // Private? - isInteractive?: boolean; _renderMode?: string; } @@ -244,12 +241,9 @@ class EngineInstance extends EmberObject.extend(RegistryProxyMixin, ContainerPro '-view-registry:main', `renderer:-dom`, 'service:-document', + 'event_dispatcher:main', ]; - if (env['isInteractive']) { - singletons.push('event_dispatcher:main'); - } - singletons.forEach((key) => { // SAFETY: We already expect this to be a singleton let singleton = parent.lookup(key) as object; diff --git a/packages/@ember/engine/type-tests/instance.test.ts b/packages/@ember/engine/type-tests/instance.test.ts index 7a8356ba7fb..33c0371e15a 100644 --- a/packages/@ember/engine/type-tests/instance.test.ts +++ b/packages/@ember/engine/type-tests/instance.test.ts @@ -14,7 +14,6 @@ let instance = new EngineInstance(owner); expectTypeOf(instance.boot()).resolves.toEqualTypeOf(); let bootOptions = { - isBrowser: true, shouldRender: false, document: window.document, rootElement: '#ember-application', @@ -23,10 +22,6 @@ let bootOptions = { instance.boot(bootOptions); -instance.boot({ isBrowser: true }); - -// @ts-expect-error Incorrect type -instance.boot({ isBrowser: 1 }); // @ts-expect-error Incorrect type instance.boot({ shouldRender: 1 }); // @ts-expect-error Incorrect type diff --git a/packages/@ember/utils/tests/type_of_test.js b/packages/@ember/utils/tests/type_of_test.js index f63116620b0..e7dfbc3053e 100644 --- a/packages/@ember/utils/tests/type_of_test.js +++ b/packages/@ember/utils/tests/type_of_test.js @@ -1,6 +1,5 @@ import { typeOf } from '@ember/utils'; import EmberObject from '@ember/object'; -import { window } from '@ember/-internals/browser-environment'; import { moduleFor, AbstractTestCase } from 'internal-test-helpers'; moduleFor( diff --git a/packages/@glimmer-workspace/integration-tests/lib/base-env.ts b/packages/@glimmer-workspace/integration-tests/lib/base-env.ts index 24dc0280253..c963ee5a2c5 100644 --- a/packages/@glimmer-workspace/integration-tests/lib/base-env.ts +++ b/packages/@glimmer-workspace/integration-tests/lib/base-env.ts @@ -18,8 +18,6 @@ export function scheduleDidDestroy(fn: () => void) { } export const BaseEnv: EnvironmentDelegate = { - isInteractive: true, - enableDebugTooling: false, onTransactionCommit() { diff --git a/packages/@glimmer-workspace/integration-tests/test/env-test.ts b/packages/@glimmer-workspace/integration-tests/test/env-test.ts index 674843170d8..f69978f19bb 100644 --- a/packages/@glimmer-workspace/integration-tests/test/env-test.ts +++ b/packages/@glimmer-workspace/integration-tests/test/env-test.ts @@ -10,7 +10,6 @@ if (DEBUG) { { document: castToSimple(document) }, { onTransactionCommit() {}, - isInteractive: true, enableDebugTooling: false, } ); @@ -27,7 +26,6 @@ QUnit.test('ensure commit cleans up when it can', (assert) => { { document: castToSimple(document) }, { onTransactionCommit() {}, - isInteractive: true, enableDebugTooling: false, } ); diff --git a/packages/@glimmer/interfaces/lib/runtime/environment.d.ts b/packages/@glimmer/interfaces/lib/runtime/environment.d.ts index c47f1c98abb..8e9e7397be2 100644 --- a/packages/@glimmer/interfaces/lib/runtime/environment.d.ts +++ b/packages/@glimmer/interfaces/lib/runtime/environment.d.ts @@ -45,7 +45,6 @@ export interface Environment { getDOM(): GlimmerTreeChanges; getAppendOperations(): GlimmerTreeConstruction; - isInteractive: boolean; debugRenderTree?: DebugRenderTree | undefined; // eslint-disable-next-line @typescript-eslint/no-explicit-any isArgumentCaptureError?: ((error: any) => boolean) | undefined; diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts index 9c0d378b123..5e442900059 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts @@ -149,9 +149,6 @@ APPEND_OPCODES.add(VM_CLOSE_ELEMENT_OP, (vm) => { APPEND_OPCODES.add(VM_MODIFIER_OP, (vm, { op1: handle }) => { let args = check(vm.stack.pop(), CheckArguments); - if (!vm.env.isInteractive) { - return; - } let owner = vm.getOwner(); let definition = vm.constants.getValue(handle); @@ -193,10 +190,6 @@ APPEND_OPCODES.add(VM_DYNAMIC_MODIFIER_OP, (vm) => { let ref = check(stack.pop(), CheckReference); let args = check(stack.pop(), CheckArguments); - if (!vm.env.isInteractive) { - return; - } - let capturedArgs = args.capture(); let { positional: outerPositional, named: outerNamed } = capturedArgs; diff --git a/packages/@glimmer/runtime/lib/environment.ts b/packages/@glimmer/runtime/lib/environment.ts index 9adfe360b61..d81dff73146 100644 --- a/packages/@glimmer/runtime/lib/environment.ts +++ b/packages/@glimmer/runtime/lib/environment.ts @@ -100,9 +100,6 @@ export class EnvironmentImpl implements Environment { declare protected appendOperations: GlimmerTreeConstruction; protected updateOperations?: GlimmerTreeChanges | undefined; - // Delegate methods and values - public isInteractive: boolean; - // eslint-disable-next-line @typescript-eslint/no-explicit-any isArgumentCaptureError: ((error: any) => boolean) | undefined; debugRenderTree: DebugRenderTree | undefined; @@ -111,7 +108,6 @@ export class EnvironmentImpl implements Environment { options: EnvironmentOptions, private delegate: EnvironmentDelegate ) { - this.isInteractive = delegate.isInteractive; this.debugRenderTree = this.delegate.enableDebugTooling ? new DebugRenderTree() : undefined; this.isArgumentCaptureError = this.delegate.enableDebugTooling ? isArgumentError : undefined; if (options.appendOperations) { @@ -160,15 +156,11 @@ export class EnvironmentImpl implements Environment { } scheduleInstallModifier(modifier: ModifierInstance) { - if (this.isInteractive) { - this.transaction.scheduleInstallModifier(modifier); - } + this.transaction.scheduleInstallModifier(modifier); } scheduleUpdateModifier(modifier: ModifierInstance) { - if (this.isInteractive) { - this.transaction.scheduleUpdateModifier(modifier); - } + this.transaction.scheduleUpdateModifier(modifier); } commit() { @@ -183,12 +175,6 @@ export class EnvironmentImpl implements Environment { } export interface EnvironmentDelegate { - /** - * Used to determine the the environment is interactive (e.g. SSR is not - * interactive). Interactive environments schedule modifiers, among other things. - */ - isInteractive: boolean; - /** * Used to enable debug tooling */ diff --git a/packages/internal-test-helpers/lib/test-cases/rendering.ts b/packages/internal-test-helpers/lib/test-cases/rendering.ts index 1dab130a719..7d5628087ea 100644 --- a/packages/internal-test-helpers/lib/test-cases/rendering.ts +++ b/packages/internal-test-helpers/lib/test-cases/rendering.ts @@ -40,10 +40,7 @@ export default abstract class RenderingTestCase extends AbstractTestCase { this.element = document.querySelector('#qunit-fixture')!; this.component = null; - if ( - !bootOptions || - (bootOptions.isInteractive !== false && bootOptions.skipEventDispatcher !== true) - ) { + if (!bootOptions || bootOptions.skipEventDispatcher !== true) { (owner.lookup('event_dispatcher:main') as EventDispatcher).setup( this.getCustomDispatcherEvents(), this.element diff --git a/smoke-tests/node-template/tests/node/app-boot-test.js b/smoke-tests/node-template/tests/node/app-boot-test.js deleted file mode 100644 index dcde0c444bb..00000000000 --- a/smoke-tests/node-template/tests/node/app-boot-test.js +++ /dev/null @@ -1,138 +0,0 @@ -import setupAppTest from './helpers/setup-app.js'; -import { register } from './helpers/assert-html-matches.js'; - -register(); - -QUnit.module('App Boot', function (hooks) { - setupAppTest(hooks); - - QUnit.test('App boots and routes to a URL', function (assert) { - this.visit('/'); - assert.ok(this.app); - }); - - QUnit.test('nested {{component}}', function (assert) { - this.template('index', '{{root-component}}'); - - this.component( - 'root-component', - { - location: 'World', - hasExistence: true, - }, - "\ -

Hello {{#if this.hasExistence}}{{this.location}}{{/if}}

\ -
{{component 'foo-bar'}}
\ - " - ); - - this.component( - 'foo-bar', - undefined, - '\ -

The files are *inside* the computer?!

\ - ' - ); - - return this.renderToHTML('/').then(function (html) { - assert.htmlMatches( - html, - '

Hello World

The files are *inside* the computer?!

' - ); - }); - }); - - QUnit.test('', function (assert) { - this.template('application', "

Go to photos

"); - this.routes(function () { - this.route('photos'); - }); - - return this.renderToHTML('/').then(function (html) { - assert.htmlMatches( - html, - '

Go to photos

' - ); - }); - }); - - QUnit.test('{{link-to}}', function (assert) { - this.template('application', "

{{#link-to route='photos'}}Go to photos{{/link-to}}

"); - this.routes(function () { - this.route('photos'); - }); - - return this.renderToHTML('/').then(function (html) { - assert.htmlMatches( - html, - '

Go to photos

' - ); - }); - }); - - QUnit.test('non-escaped content', function (assert) { - this.routes(function () { - this.route('photos'); - }); - - this.template('application', '

{{{this.title}}}

'); - this.controller('application', { - title: 'Hello world', - }); - - return this.renderToHTML('/').then(function (html) { - assert.htmlMatches(html, '

Hello world

'); - }); - }); - - QUnit.test('outlets', function (assert) { - this.routes(function () { - this.route('photos'); - }); - - this.template('application', '

{{outlet}}

'); - this.template('index', 'index'); - this.template('photos', 'photos'); - - let promises = []; - promises.push( - this.renderToHTML('/').then(function (html) { - assert.htmlMatches(html, '

index

'); - }) - ); - - promises.push( - this.renderToHTML('/photos').then(function (html) { - assert.htmlMatches(html, '

photos

'); - }) - ); - - return this.all(promises); - }); - - QUnit.test('lifecycle hooks disabled', function (assert) { - assert.expect(1); - - this.template('application', "{{my-component foo='bar'}}{{outlet}}"); - - this.component('my-component', { - didReceiveAttrs() { - assert.ok(true, 'should trigger didReceiveAttrs hook'); - }, - willRender() { - assert.ok(false, 'should not trigger willRender hook'); - }, - didRender() { - assert.ok(false, 'should not trigger didRender hook'); - }, - willInsertElement() { - assert.ok(false, 'should not trigger willInsertElement hook'); - }, - didInsertElement() { - assert.ok(false, 'should not trigger didInsertElement hook'); - }, - }); - - return this.renderToHTML('/'); - }); -}); diff --git a/smoke-tests/node-template/tests/node/component-rendering-test.js b/smoke-tests/node-template/tests/node/component-rendering-test.js deleted file mode 100644 index 80b1807b305..00000000000 --- a/smoke-tests/node-template/tests/node/component-rendering-test.js +++ /dev/null @@ -1,45 +0,0 @@ -import setupComponentTest from './helpers/setup-component.js'; - -QUnit.module('Components can be rendered without a DOM dependency', function (hooks) { - setupComponentTest(hooks); - - QUnit.test('Simple component', function (assert) { - let html = this.render('

Hello

'); - - assert.ok(html.match(/

Hello<\/h1>/)); - }); - - QUnit.test('Component with dynamic value', function (assert) { - this.set('location', 'World'); - - let html = this.render('

Hello {{this.location}}

'); - - assert.ok(html.match(/

Hello World<\/h1>/)); - }); - - QUnit.test( - 'Ensure undefined attributes requiring protocol sanitization do not error', - function (assert) { - this.owner.register( - 'component:fake-link', - class extends this.Component { - tagName = 'link'; - attributeBindings = ['href', 'rel']; - rel = 'canonical'; - } - ); - - let html = this.render('{{fake-link}}'); - - assert.ok(html.match(/rel="canonical"/)); - } - ); - - QUnit.test('attributes requiring protocol sanitization do not error', function (assert) { - this.set('someHref', 'https://foo.com/'); - - let html = this.render('Some Link'); - - assert.ok(html.match(/Some Link<\/a>/)); - }); -}); diff --git a/smoke-tests/node-template/tests/node/fastboot-sandbox-test.js b/smoke-tests/node-template/tests/node/fastboot-sandbox-test.js deleted file mode 100644 index 5a329dbcee0..00000000000 --- a/smoke-tests/node-template/tests/node/fastboot-sandbox-test.js +++ /dev/null @@ -1,81 +0,0 @@ -import SimpleDOM from 'simple-dom'; -import Application from 'ember-source/@ember/application/index.js'; -import EmberRouter from 'ember-source/@ember/routing/router.js'; -import { run } from 'ember-source/@ember/runloop/index.js'; -import { precompile } from 'ember-source/ember-template-compiler/index.js'; -import { createTemplateFactory } from 'ember-source/@ember/template-factory/index.js'; - -function compile(templateString, options) { - let templateSpec = precompile(templateString, options); - return createTemplateFactory(JSON.parse(templateSpec)); -} - -// This is based on what fastboot-server does -let HTMLSerializer = new SimpleDOM.HTMLSerializer(SimpleDOM.voidMap); - -async function fastbootVisit(app, url) { - let doc = new SimpleDOM.Document(); - let rootElement = doc.body; - let options = { isBrowser: false, document: doc, rootElement: rootElement }; - - await app.boot(); - - let instance = await app.buildInstance(); - - try { - await instance.boot(options); - await instance.visit(url, options); - - return { - url: instance.getURL(), - title: doc.title, - body: HTMLSerializer.serialize(rootElement), - }; - } finally { - instance.destroy(); - } -} - -QUnit.module('Ember.Application - visit() Integration Tests', function (hooks) { - let app; - - hooks.afterEach(function () { - if (app) { - run(app, 'destroy'); - app = null; - } - }); - - QUnit.test('FastBoot: basic', async function (assert) { - let Router = class extends EmberRouter {}; - Router.map(function () { - this.route('a'); - this.route('b'); - }); - - let registry = { - 'router:main': Router, - 'template:application': compile('

Hello world!

\n{{outlet}}'), - }; - - app = class extends Application {}.create({ - autoboot: false, - Resolver: { - create: () => ({ - resolve(specifier) { - return registry[specifier]; - }, - }), - }, - }); - - let result = await fastbootVisit(app, '/'); - - assert.equal(result.url, '/', 'landed on correct url'); - assert.equal( - result.body, - '

Hello world!

\n', - 'results in expected HTML' - ); - }); -}); diff --git a/smoke-tests/node-template/tests/node/helpers/assert-html-matches.js b/smoke-tests/node-template/tests/node/helpers/assert-html-matches.js deleted file mode 100644 index 9e8233c053c..00000000000 --- a/smoke-tests/node-template/tests/node/helpers/assert-html-matches.js +++ /dev/null @@ -1,26 +0,0 @@ -import { HtmlDiffer } from 'html-differ'; -import QUnit from 'qunit'; - -const htmlDiffer = new HtmlDiffer({ - ignoreAttributes: ['id'], - ignoreWhitespaces: true, -}); - -/* - * This assertion helper tests whether two fragments of Html 'appear' - * to match. In terms of fragments rendered by Ember, we want to explicitly - * ignore whitespace and certain attributes values, such as IDs, which Ember - * auto-generates. Attribute ordering is also ignored. - */ -export function register() { - QUnit.assert.htmlMatches = function (actual, expected, message) { - let isEqual = htmlDiffer.isEqual(actual, expected); - - this.pushResult({ - result: isEqual, - actual, - expected, - message, - }); - }; -} diff --git a/smoke-tests/node-template/tests/node/helpers/build-owner.js b/smoke-tests/node-template/tests/node/helpers/build-owner.js deleted file mode 100644 index 4394b31d5b0..00000000000 --- a/smoke-tests/node-template/tests/node/helpers/build-owner.js +++ /dev/null @@ -1,35 +0,0 @@ -import EmberObject from 'ember-source/@ember/object/index.js'; -import Application from 'ember-source/@ember/application/index.js'; -import ApplicationInstance from 'ember-source/@ember/application/instance.js'; -import RegistryProxyMixin from 'ember-source/@ember/-internals/runtime/lib/mixins/registry_proxy.js'; -import ContainerProxyMixin from 'ember-source/@ember/-internals/runtime/lib/mixins/container_proxy.js'; -import { Registry } from 'ember-source/@ember/-internals/container/index.js'; - -export default function buildOwner(resolver) { - let Owner = EmberObject.extend(RegistryProxyMixin, ContainerProxyMixin); - - let namespace = EmberObject.create({ - Resolver: { - create: function () { - return resolver; - }, - }, - }); - - let fallbackRegistry = Application.buildRegistry(namespace); - let registry = new Registry({ - fallback: fallbackRegistry, - }); - - ApplicationInstance.setupRegistry(registry); - - let owner = Owner.create({ - __registry__: registry, - __container__: null, - }); - - let container = registry.container({ owner: owner }); - owner.__container__ = container; - - return owner; -} diff --git a/smoke-tests/node-template/tests/node/helpers/setup-app.js b/smoke-tests/node-template/tests/node/helpers/setup-app.js deleted file mode 100644 index 91bcf921a8b..00000000000 --- a/smoke-tests/node-template/tests/node/helpers/setup-app.js +++ /dev/null @@ -1,207 +0,0 @@ -/* eslint-disable no-console */ - -import SimpleDOM from 'simple-dom'; -import Application from 'ember-source/@ember/application/index.js'; -import EmberRouter from 'ember-source/@ember/routing/router.js'; -import Component from 'ember-source/@ember/component/index.js'; -import { setComponentTemplate } from 'ember-source/@ember/component/index.js'; -import templateOnly from 'ember-source/@ember/component/template-only.js'; -import Controller from 'ember-source/@ember/controller/index.js'; -import Route from 'ember-source/@ember/routing/route.js'; -import EmberObject from 'ember-source/@ember/object/index.js'; -import { service } from 'ember-source/@ember/service/index.js'; -import { run } from 'ember-source/@ember/runloop/index.js'; -import RSVP from 'ember-source/@ember/-internals/runtime/lib/ext/rsvp.js'; -import { precompile } from 'ember-source/ember-template-compiler/index.js'; -import { createTemplateFactory } from 'ember-source/@ember/template-factory/index.js'; - -function compile(templateString, options) { - let templateSpec = precompile(templateString, options); - return createTemplateFactory(JSON.parse(templateSpec)); -} - -/* - * This helper sets up a QUnit test module with all of the environment and - * helper methods necessary to test an Ember.js application running in the - * server-side environment. - * - * It uses direct ESM imports from ember-source. It uses the `visit()` API - * to simulate a FastBoot environment. - * - * To test an app, register the objects that make up the app. For example, - * to register a component: - * - * this.component('component-name', { - * componentProperty: true - * }); - * - * Or a template: - * - * this.template('application', '{{outlet}}'); - * this.template('components/foo-bar', '

Hello world

'); - * - * Or a controller: - * - * this.controller('controller-name', { - * actions: { - * sendEmail: function() { } - * } - * }); - * - * You can also provide the routes for the application by calling `this.routes()`, - * which is equivalent to `App.Router.map()`: - * - * this.routes(function() { - * this.route('photos'); - * this.route('admin', function() { - * this.route('logout'); - * }); - * }); - * - * Once all of the constituent parts of the app are registered, you can kick off - * app boot by calling either `this.visit(url)` or `this.renderToHTML(url)`. - * - * `visit` returns a promise that resolves to the application instance, and - * `renderToHTML` returns a promise that resolves to the rendered HTML of the - * application. - * - * return this.renderToHTML('/photos').then(function(html) { - * assert.ok(html.matches('

Hello world

')); - * }); - */ - -export default function (hooks) { - hooks.beforeEach(function () { - this.compile = compile; - this.setComponentTemplate = setComponentTemplate; - this.templateOnlyComponent = templateOnly; - - this.run = run; - this.all = RSVP.all.bind(RSVP); - - this.visit = visit; - this.createApplication = createApplication; - this.register = register; - this.template = registerTemplate; - this.component = registerComponent; - this.controller = registerController; - this.route = registerRoute; - this.service = registerService; - this.routes = registerRoutes; - this.registry = {}; - this.renderToHTML = renderToHTML; - }); - - hooks.afterEach(function () { - this.run(this.app, 'destroy'); - }); -} - -function createApplication() { - if (this.app) return this.app; - - let app = class extends Application {}.create({ - autoboot: false, - Resolver: { - create: (specifier) => { - return this.registry[specifier]; - }, - }, - }); - - let Router = class extends EmberRouter { - location = 'none'; - }; - - if (this.routesCallback) { - Router.map(this.routesCallback); - } - - this.register('router:main', Router); - - registerApplicationClasses(app, this.registry); - - // Run application initializers - this.run(app, 'boot'); - - this.app = app; - - return app; -} - -function register(containerKey, klass) { - this.registry[containerKey] = klass; -} - -function visit(url) { - let app = this.createApplication(); - let dom = new SimpleDOM.Document(); - - return this.run(app, 'visit', url, { - isBrowser: false, - document: dom, - rootElement: dom.body, - }).catch(function (error) { - console.error(error.stack); - }); -} - -function renderToHTML(url) { - let app = this.createApplication(); - let dom = new SimpleDOM.Document(); - let root = dom.body; - - return this.run(app, 'visit', url, { - isBrowser: false, - document: dom, - rootElement: root, - }).then(function () { - let serializer = new SimpleDOM.HTMLSerializer(SimpleDOM.voidMap); - return serializer.serialize(root); - }); -} - -function registerApplicationClasses(app, registry) { - app.initializer({ - name: 'register-application-classes', - initialize: function (app) { - for (let key in registry) { - app.register(key, registry[key]); - } - }, - }); -} - -function registerTemplate(name, template) { - this.register('template:' + name, this.compile(template)); -} - -function registerComponent(name, componentProps, templateContents) { - let component = this.setComponentTemplate( - this.compile(templateContents), - componentProps ? Component.extend(componentProps) : this.templateOnlyComponent() - ); - this.register('component:' + name, component); -} - -function registerController(name, controllerProps) { - let controller = Controller.extend(controllerProps); - this.register('controller:' + name, controller); -} - -function registerRoute(name, routeProps) { - let route = Route.extend({ - router: service('router'), - ...routeProps, - }); - this.register('route:' + name, route); -} - -function registerService(name, serviceProps) { - let svc = EmberObject.extend(serviceProps); - this.register('service:' + name, svc); -} - -function registerRoutes(cb) { - this.routesCallback = cb; -} diff --git a/smoke-tests/node-template/tests/node/helpers/setup-component.js b/smoke-tests/node-template/tests/node/helpers/setup-component.js deleted file mode 100644 index e02139422b6..00000000000 --- a/smoke-tests/node-template/tests/node/helpers/setup-component.js +++ /dev/null @@ -1,113 +0,0 @@ -import SimpleDOM from 'simple-dom'; -import Component from 'ember-source/@ember/component/index.js'; -import { set } from 'ember-source/@ember/object/index.js'; -import { run } from 'ember-source/@ember/runloop/index.js'; -import { precompile } from 'ember-source/ember-template-compiler/index.js'; -import { createTemplateFactory } from 'ember-source/@ember/template-factory/index.js'; -import buildOwner from './build-owner.js'; - -function compile(templateString, options) { - let templateSpec = precompile(templateString, options); - return createTemplateFactory(JSON.parse(templateSpec)); -} - -export default function (hooks) { - hooks.beforeEach(function () { - this.compile = compile; - this.Ember = { Component, set }; - this.Component = Component; - - this.run = run; - - setupComponentTest.call(this); - }); - - hooks.afterEach(function () { - let module = this; - - if (this.component) { - this.run(function () { - module.component.destroy(); - }); - - this.component = null; - } - - this.run(this.owner, 'destroy'); - this.owner = null; - }); -} - -function setupComponentTest() { - let module = this; - - module.element = new SimpleDOM.Document(); - module.owner = buildOwner({ resolve: function () {} }); - module.owner.register('service:-document', new SimpleDOM.Document(), { - instantiate: false, - }); - - this._hasRendered = false; - let OutletView = module.owner.factoryFor('view:-outlet'); - let outletTemplateFactory = module.owner.lookup('template:-outlet'); - let environment = module.owner.lookup('-environment:main'); - module.component = OutletView.create({ environment, template: outletTemplateFactory }); - this._outletState = { - render: { - owner: module.owner || undefined, - name: 'application', - controller: module, - model: undefined, - template: outletTemplateFactory(module.owner), - }, - - outlets: {}, - }; - - this.run(function () { - module.component.setOutletState(module._outletState); - }); - - module.render = render; - module.serializeElement = serializeElement; - module.set = function (property, value) { - module.run(function () { - set(module, property, value); - }); - }; -} - -function render(_template) { - let module = this; - let templateFactory = this.compile(_template); - - let stateToRender = { - owner: this.owner, - name: 'index', - controller: this, - model: undefined, - template: templateFactory(this.owner), - }; - - stateToRender.name = 'index'; - this._outletState.outlets.main = { render: stateToRender, outlets: {} }; - - this.run(function () { - module.component.setOutletState(module._outletState); - }); - - if (!this._hasRendered) { - this.run(function () { - module.component.appendTo(module.element); - }); - this._hasRendered = true; - } - - return this.serializeElement(); -} - -function serializeElement() { - let serializer = new SimpleDOM.HTMLSerializer(SimpleDOM.voidMap); - - return serializer.serialize(this.element); -} diff --git a/smoke-tests/node-template/tests/node/visit-test.js b/smoke-tests/node-template/tests/node/visit-test.js deleted file mode 100644 index 59d9b42ac5b..00000000000 --- a/smoke-tests/node-template/tests/node/visit-test.js +++ /dev/null @@ -1,357 +0,0 @@ -import SimpleDOM from 'simple-dom'; -import setupAppTest from './helpers/setup-app.js'; - -function assertHTMLMatches(assert, actualHTML, expectedHTML) { - assert.ok(actualHTML.match(expectedHTML), actualHTML + ' matches ' + expectedHTML); -} - -function handleError(assert) { - return function (error) { - assert.ok(false, error.stack); - }; -} - -// This is based on what fastboot-server does -let HTMLSerializer = new SimpleDOM.HTMLSerializer(SimpleDOM.voidMap); - -function fastbootVisit(App, url) { - let doc = new SimpleDOM.Document(); - let rootElement = doc.body; - let options = { isBrowser: false, document: doc, rootElement: rootElement }; - - return App.visit(url, options).then(function (instance) { - try { - return { - url: instance.getURL(), - title: doc.title, - body: HTMLSerializer.serialize(rootElement), - }; - } finally { - instance.destroy(); - } - }); -} - -function assertFastbootResult(assert, expected) { - return function (actual) { - assert.equal(actual.url, expected.url); - assertHTMLMatches(assert, actual.body, expected.body); - }; -} - -QUnit.module('Ember.Application - visit() Integration Tests', function (hooks) { - setupAppTest(hooks); - - QUnit.test('FastBoot: basic', function (assert) { - this.routes(function () { - this.route('a'); - this.route('b'); - }); - - this.template('application', '

Hello world

\n{{outlet}}'); - this.template('a', '

Welcome to {{x-foo page="A"}}

'); - this.template('b', '

{{x-foo page="B"}}

'); - - let initCalled = false; - let didInsertElementCalled = false; - - this.component( - 'x-foo', - { - tagName: 'span', - init: function () { - this._super(); - initCalled = true; - }, - didInsertElement: function () { - didInsertElementCalled = true; - }, - }, - 'Page {{this.page}}' - ); - - let App = this.createApplication(); - - return Promise.all([ - fastbootVisit(App, '/a').then( - assertFastbootResult(assert, { - url: '/a', - body: '

Hello world

\n

Welcome to Page A

', - }), - handleError(assert) - ), - fastbootVisit(App, '/b').then( - assertFastbootResult(assert, { - url: '/b', - body: '

Hello world

\n

Page B

', - }), - handleError - ), - ]).then(function () { - assert.ok(initCalled, 'Component#init should be called'); - assert.notOk(didInsertElementCalled, 'Component#didInsertElement should not be called'); - }); - }); - - QUnit.test('FastBoot: redirect', function (assert) { - this.routes(function () { - this.route('a'); - this.route('b'); - this.route('c'); - }); - - this.template('a', '

Hello from A

'); - this.template('b', '

Hello from B

'); - this.template('c', '

Hello from C

'); - - this.route('a', { - beforeModel: function () { - this.router.replaceWith('b'); - }, - }); - - this.route('b', { - afterModel: function () { - this.router.transitionTo('c'); - }, - }); - - let App = this.createApplication(); - - return Promise.all([ - fastbootVisit(App, '/a').then( - assertFastbootResult(assert, { - url: '/c', - body: '

Hello from C

', - }), - handleError(assert) - ), - fastbootVisit(App, '/b').then( - assertFastbootResult(assert, { - url: '/c', - body: '

Hello from C

', - }), - handleError(assert) - ), - ]); - }); - - QUnit.test('FastBoot: attributes are sanitized', function (assert) { - this.template('application', '
'); - - this.controller('application', { - test: 'javascript:alert("hello")', - }); - - let App = this.createApplication(); - - return Promise.all([ - fastbootVisit(App, '/').then( - assertFastbootResult(assert, { - url: '/', - body: '', - }), - handleError(assert) - ), - ]); - }); - - QUnit.test('FastBoot: route error', function (assert) { - this.routes(function () { - this.route('a'); - this.route('b'); - }); - - this.template('a', '

Hello from A

'); - this.template('b', '

Hello from B

'); - - this.route('a', { - beforeModel: function () { - throw new Error('Error from A'); - }, - }); - - this.route('b', { - afterModel: function () { - throw new Error('Error from B'); - }, - }); - - let App = this.createApplication(); - - return Promise.all([ - fastbootVisit(App, '/a').then( - function (instance) { - assert.ok(false, 'It should not render'); - instance.destroy(); - }, - function (error) { - assert.equal(error.message, 'Error from A'); - } - ), - fastbootVisit(App, '/b').then( - function (instance) { - assert.ok(false, 'It should not render'); - instance.destroy(); - }, - function (error) { - assert.equal(error.message, 'Error from B'); - } - ), - ]); - }); - - QUnit.test('FastBoot: route error template', function (assert) { - this.routes(function () { - this.route('a'); - }); - - this.template('error', '

Error template rendered!

'); - this.template('a', '

Hello from A

'); - - this.route('a', { - model: function () { - throw new Error('Error from A'); - }, - }); - - let App = this.createApplication(); - - return Promise.all([ - fastbootVisit(App, '/a').then( - assertFastbootResult(assert, { - url: '/a', - body: '

Error template rendered!

', - }), - handleError(assert) - ), - ]); - }); - - QUnit.test('Resource-discovery setup', function (assert) { - class Network { - constructor() { - this.requests = []; - } - - fetch(url) { - this.requests.push(url); - return Promise.resolve(); - } - } - - this.routes(function () { - this.route('a'); - this.route('b'); - this.route('c'); - this.route('d'); - this.route('e'); - }); - - let network; - this.route('a', { - model: function () { - return network.fetch('/a'); - }, - afterModel: function () { - this.router.replaceWith('b'); - }, - }); - - this.route('b', { - model: function () { - return network.fetch('/b'); - }, - afterModel: function () { - this.router.replaceWith('c'); - }, - }); - - this.route('c', { - model: function () { - return network.fetch('/c'); - }, - }); - - this.route('d', { - model: function () { - return network.fetch('/d'); - }, - afterModel: function () { - this.router.replaceWith('e'); - }, - }); - - this.route('e', { - model: function () { - return network.fetch('/e'); - }, - }); - - this.template('a', '{{x-foo}}'); - this.template('b', '{{x-foo}}'); - this.template('c', '{{x-foo}}'); - this.template('d', '{{x-foo}}'); - this.template('e', '{{x-foo}}'); - - let xFooInstances = 0; - - this.component('x-foo', { - init: function () { - this._super(); - xFooInstances++; - }, - }); - - let App = this.createApplication(); - - function assertResources(url, resources) { - network = new Network(); - - return App.visit(url, { isBrowser: false, shouldRender: false }).then(function (instance) { - try { - let viewRegistry = instance.lookup('-view-registry:main'); - assert.strictEqual(Object.keys(viewRegistry).length, 0, 'did not create any views'); - - assert.deepEqual(network.requests, resources); - } finally { - instance.destroy(); - } - }, handleError(assert)); - } - - return assertResources('/a', ['/a', '/b', '/c']) - .then(() => { - return assertResources('/b', ['/b', '/c']); - }) - .then(() => { - return assertResources('/c', ['/c']); - }) - .then(() => { - return assertResources('/d', ['/d', '/e']); - }) - .then(() => { - return assertResources('/e', ['/e']); - }) - .then(() => { - assert.strictEqual(xFooInstances, 0, 'it should not create any x-foo components'); - }); - }); - - QUnit.test('FastBoot: tagless components can render', function (assert) { - this.template('application', "
{{my-component}}
"); - this.component('my-component', { tagName: '' }, '

hello world

'); - - let App = this.createApplication(); - - return Promise.all([ - fastbootVisit(App, '/').then( - assertFastbootResult(assert, { - url: '/', - body: /

hello world<\/h1><\/div>/, - }), - handleError(assert) - ), - ]); - }); -}); diff --git a/tests/docs/expected.js b/tests/docs/expected.js index 275e5cbd6d3..bbc99afd646 100644 --- a/tests/docs/expected.js +++ b/tests/docs/expected.js @@ -275,7 +275,6 @@ module.exports = { 'isAny', 'isArray', 'isBlank', - 'isBrowser', 'isClassicDecorator', 'isComputed', 'isConst', @@ -289,7 +288,6 @@ module.exports = { 'isFulfilled', 'isHTMLSafe', 'isTrustedHTML', - 'isInteractive', 'isNone', 'isObject', 'isPending', @@ -319,7 +317,6 @@ module.exports = { 'map', 'mapBy', 'match', - 'matches', 'max', 'mergedProperties', 'meta',