diff --git a/packages/@ember/-internals/glimmer/lib/component.ts b/packages/@ember/-internals/glimmer/lib/component.ts index ce9101c237b..79ad7d0dec7 100644 --- a/packages/@ember/-internals/glimmer/lib/component.ts +++ b/packages/@ember/-internals/glimmer/lib/component.ts @@ -18,6 +18,7 @@ import { isUpdatableRef, updateRef } from '@glimmer/reference'; import { normalizeProperty } from '@glimmer/runtime'; import { createTag, dirtyTag } from '@glimmer/validator'; import { Namespace } from '@simple-dom/interface'; +import { EmberVMEnvironment } from './environment'; export const ARGS = enumerableSymbol('ARGS'); export const HAS_BLOCK = enumerableSymbol('HAS_BLOCK'); @@ -787,6 +788,21 @@ const Component = CoreView.extend( ); }, + on(eventName: string) { + let owner = getOwner(this); + if (owner.lookup('-environment:main')!.isInteractive) { + let eventDispatcher = owner.lookup('event_dispatcher:main'); + if ( + eventDispatcher && + Array.from(eventDispatcher.lazyEvents.values()).includes(eventName) + ) { + eventDispatcher.setupHandlerForEmberEvent(eventName); + } + } + + return this._super(...arguments); + }, + rerender() { dirtyTag(this[DIRTY_TAG]); this._super(); diff --git a/packages/@ember/-internals/glimmer/lib/components/link-to.ts b/packages/@ember/-internals/glimmer/lib/components/link-to.ts index f596bcbbc4e..5b34abd73e8 100644 --- a/packages/@ember/-internals/glimmer/lib/components/link-to.ts +++ b/packages/@ember/-internals/glimmer/lib/components/link-to.ts @@ -3,14 +3,12 @@ */ import { alias, computed } from '@ember/-internals/metal'; -import { getOwner } from '@ember/-internals/owner'; -import { EventDispatcher, isSimpleClick } from '@ember/-internals/views'; +import { isSimpleClick } from '@ember/-internals/views'; import { assert, warn } from '@ember/debug'; import { flaggedInstrument } from '@ember/instrumentation'; import { inject as injectService } from '@ember/service'; import { DEBUG } from '@glimmer/env'; import EmberComponent, { HAS_BLOCK } from '../component'; -import { EmberVMEnvironment } from '../environment'; import layout from '../templates/link-to'; /** @@ -492,16 +490,6 @@ const LinkComponent = EmberComponent.extend({ // Map desired event name to invoke function let { eventName } = this; this.on(eventName, this, this._invoke); - - // As our EventDispatcher adds event listeners lazily, and does not recognize the dynamic event pattern here, - // we must tell it explicitly that we need to listen to `eventName` events - let owner = getOwner(this); - if (owner.lookup('-environment:main')!.isInteractive) { - let eventDispatcher = owner.lookup('event_dispatcher:main'); - if (eventDispatcher) { - eventDispatcher.setupHandlerForEmberEvent(eventName); - } - } }, _routing: injectService('-routing'), diff --git a/packages/@ember/-internals/glimmer/tests/integration/event-dispatcher-test.js b/packages/@ember/-internals/glimmer/tests/integration/event-dispatcher-test.js index 1db813b3050..7f9a9554505 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/event-dispatcher-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/event-dispatcher-test.js @@ -20,9 +20,109 @@ function fireNativeWithDataTransfer(node, type, dataTransfer) { node.dispatchEvent(event); } +function triggerEvent(node, event) { + switch (event) { + case 'focusin': + return node.focus(); + case 'focusout': + return node.blur(); + default: + return node.trigger(event); + } +} + +const SUPPORTED_EMBER_EVENTS = { + touchstart: 'touchStart', + touchmove: 'touchMove', + touchend: 'touchEnd', + touchcancel: 'touchCancel', + keydown: 'keyDown', + keyup: 'keyUp', + keypress: 'keyPress', + mousedown: 'mouseDown', + mouseup: 'mouseUp', + contextmenu: 'contextMenu', + click: 'click', + dblclick: 'doubleClick', + focusin: 'focusIn', + focusout: 'focusOut', + submit: 'submit', + input: 'input', + change: 'change', + dragstart: 'dragStart', + drag: 'drag', + dragenter: 'dragEnter', + dragleave: 'dragLeave', + dragover: 'dragOver', + drop: 'drop', + dragend: 'dragEnd', +}; + moduleFor( 'EventDispatcher', class extends RenderingTestCase { + ['@test event handler methods are called when event is triggered'](assert) { + let receivedEvent; + let browserEvent; + + this.registerComponent('x-button', { + ComponentClass: Component.extend( + { + tagName: 'button', + }, + Object.entries(SUPPORTED_EMBER_EVENTS) + .map(([, emberEvent]) => ({ + [emberEvent](event) { + receivedEvent = event; + }, + })) + .reduce((result, singleEventHandler) => ({ ...result, ...singleEventHandler }), {}) + ), + }); + + this.render(`{{x-button}}`); + + let elementNode = this.$('button'); + let element = elementNode[0]; + + for (browserEvent in SUPPORTED_EMBER_EVENTS) { + receivedEvent = null; + runTask(() => triggerEvent(elementNode, browserEvent)); + assert.ok(receivedEvent, `${browserEvent} event was triggered`); + assert.strictEqual(receivedEvent.target, element); + } + } + + ['@test event listeners are called when event is triggered'](assert) { + let receivedEvent; + let browserEvent; + + this.registerComponent('x-button', { + ComponentClass: Component.extend({ + tagName: 'button', + init() { + this._super(); + Object.values(SUPPORTED_EMBER_EVENTS).forEach((emberEvent) => { + console.log(emberEvent); + this.on(emberEvent, (event) => (receivedEvent = event)); + }); + }, + }), + }); + + this.render(`{{x-button}}`); + + let elementNode = this.$('button'); + let element = elementNode[0]; + + for (browserEvent in SUPPORTED_EMBER_EVENTS) { + receivedEvent = null; + runTask(() => triggerEvent(elementNode, browserEvent)); + assert.ok(receivedEvent, `${browserEvent} event was triggered`); + assert.strictEqual(receivedEvent.target, element); + } + } + ['@test events bubble view hierarchy for form elements'](assert) { let receivedEvent; diff --git a/packages/internal-test-helpers/lib/node-query.js b/packages/internal-test-helpers/lib/node-query.js index 493675cf895..50c78b1a8ea 100644 --- a/packages/internal-test-helpers/lib/node-query.js +++ b/packages/internal-test-helpers/lib/node-query.js @@ -1,7 +1,7 @@ /* global Node */ import { assert } from '@ember/debug'; -import { fireEvent, focus, matches } from './system/synthetic-events'; +import { blur, fireEvent, focus, matches } from './system/synthetic-events'; export default class NodeQuery { static query(selector, context = document) { @@ -54,6 +54,10 @@ export default class NodeQuery { this.nodes.forEach(focus); } + blur() { + this.nodes.forEach(blur); + } + text() { return this.nodes.map((node) => node.textContent).join(''); }