From d7f9ebe4b415454fe8482e009382849abf519c4a Mon Sep 17 00:00:00 2001 From: Peter Wagenet Date: Wed, 20 Mar 2019 09:05:52 -0700 Subject: [PATCH] [BUGFIX] Tests and support for native DOM events on Inputs --- .../-internals/glimmer/lib/component.ts | 4 +- .../glimmer/lib/components/input.ts | 7 ++- .../glimmer/lib/templates/input.hbs | 50 +++++++++++++++ .../components/input-angle-test.js | 61 +++++++++++++++++++ .../components/input-curly-test.js | 61 +++++++++++++++++++ 5 files changed, 181 insertions(+), 2 deletions(-) diff --git a/packages/@ember/-internals/glimmer/lib/component.ts b/packages/@ember/-internals/glimmer/lib/component.ts index 2e76976be4a..426c2f15528 100644 --- a/packages/@ember/-internals/glimmer/lib/component.ts +++ b/packages/@ember/-internals/glimmer/lib/component.ts @@ -23,6 +23,7 @@ export const ROOT_REF = symbol('ROOT_REF'); export const IS_DISPATCHING_ATTRS = symbol('IS_DISPATCHING_ATTRS'); export const HAS_BLOCK = symbol('HAS_BLOCK'); export const BOUNDS = symbol('BOUNDS'); +export const DISABLE_TAGLESS_EVENT_CHECK = symbol('DISABLE_TAGLESS_EVENT_CHECK'); /** @module @ember/component @@ -650,7 +651,8 @@ const Component = CoreView.extend( assert( // tslint:disable-next-line:max-line-length `You can not define a function that handles DOM events in the \`${this}\` tagless component since it doesn't have any DOM element.`, - this.tagName !== '' || + this[DISABLE_TAGLESS_EVENT_CHECK] || + this.tagName !== '' || !this.renderer._destinedForDOM || !(() => { let eventDispatcher = getOwner(this).lookup('event_dispatcher:main'); diff --git a/packages/@ember/-internals/glimmer/lib/components/input.ts b/packages/@ember/-internals/glimmer/lib/components/input.ts index fbccb87a825..6a0ae9bf1f8 100644 --- a/packages/@ember/-internals/glimmer/lib/components/input.ts +++ b/packages/@ember/-internals/glimmer/lib/components/input.ts @@ -5,7 +5,7 @@ import { computed } from '@ember/-internals/metal'; import { EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS } from '@ember/canary-features'; import { assert } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; -import Component from '../component'; +import Component, { DISABLE_TAGLESS_EVENT_CHECK } from '../component'; let Input: any; @@ -148,6 +148,11 @@ if (EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS) { Input = Component.extend({ tagName: '', + init() { + this[DISABLE_TAGLESS_EVENT_CHECK] = true; + this._super(...arguments); + }, + isCheckbox: computed('type', function(this: { type?: unknown }) { return this.type === 'checkbox'; }), diff --git a/packages/@ember/-internals/glimmer/lib/templates/input.hbs b/packages/@ember/-internals/glimmer/lib/templates/input.hbs index 3c66f23252f..d215ce30e34 100644 --- a/packages/@ember/-internals/glimmer/lib/templates/input.hbs +++ b/packages/@ember/-internals/glimmer/lib/templates/input.hbs @@ -11,6 +11,31 @@ @autofocus={{@autofocus}} @required={{@required}} @form={{@form}} + + @touchStart={{@touchStart}} + @touchMove={{@touchMove}} + @touchEnd={{@touchEnd}} + @touchCancel={{@touchCancel}} + @keyDown={{@keyDown}} + @keyUp={{@keyUp}} + @keyPress={{@keyPress}} + @mouseDown={{@mouseDown}} + @mouseUp={{@mouseUp}} + @contextMenu={{@contextMenu}} + @click={{@click}} + @doubleClick={{@doubleClick}} + @mouseMove={{@mouseMove}} + @submit={{@submit}} + @input={{@input}} + @change={{@change}} + @dragStart={{@dragStart}} + @drag={{@drag}} + @dragEnter={{@dragEnter}} + @dragLeave={{@dragLeave}} + @dragOver={{@dragOver}} + @drop={{@drop}} + @dragEnd={{@dragEnd}} + ...attributes /> {{~else~}} @@ -64,6 +89,31 @@ @type={{@type}} @value={{@value}} @width={{@width}} + + @touchStart={{@touchStart}} + @touchMove={{@touchMove}} + @touchEnd={{@touchEnd}} + @touchCancel={{@touchCancel}} + @keyDown={{@keyDown}} + @keyUp={{@keyUp}} + @keyPress={{@keyPress}} + @mouseDown={{@mouseDown}} + @mouseUp={{@mouseUp}} + @contextMenu={{@contextMenu}} + @click={{@click}} + @doubleClick={{@doubleClick}} + @mouseMove={{@mouseMove}} + @submit={{@submit}} + @input={{@input}} + @change={{@change}} + @dragStart={{@dragStart}} + @drag={{@drag}} + @dragEnter={{@dragEnter}} + @dragLeave={{@dragLeave}} + @dragOver={{@dragOver}} + @drop={{@drop}} + @dragEnd={{@dragEnd}} + ...attributes /> {{~/if~}} diff --git a/packages/@ember/-internals/glimmer/tests/integration/components/input-angle-test.js b/packages/@ember/-internals/glimmer/tests/integration/components/input-angle-test.js index 61aa01c5e9a..d01842568fe 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/components/input-angle-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/components/input-angle-test.js @@ -81,6 +81,59 @@ if (EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS) { element.dispatchEvent(event); }); } + + assertTriggersNativeDOMEvents(type) { + // Defaults from EventDispatcher + let 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', + mousemove: 'mouseMove', + // These four are specially handled elsewhere + // focusin: 'focusIn', + // focusout: 'focusOut', + // mouseenter: 'mouseEnter', + // mouseleave: 'mouseLeave', + submit: 'submit', + input: 'input', + change: 'change', + dragstart: 'dragStart', + drag: 'drag', + dragenter: 'dragEnter', + dragleave: 'dragLeave', + dragover: 'dragOver', + drop: 'drop', + dragend: 'dragEnd', + }; + + let triggeredEvents = []; + + let typeAttr = type ? `type="${type}" ` : ''; + let actionAttrs = Object.keys(events).map(evt => `@${events[evt]}={{action 'run_${evt}'}}`); + let template = ``; + + let actions = {}; + Object.keys(events).forEach(evt => { + actions[`run_${evt}`] = function() { + triggeredEvents.push(evt); + }; + }); + + this.render(template, { actions }); + + Object.keys(events).forEach(evt => this.triggerEvent(evt)); + + this.assert.deepEqual(triggeredEvents, Object.keys(events), 'called for all events'); + } } moduleFor( @@ -645,6 +698,10 @@ if (EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS) { this.assert.equal(this.$input()[0].type, 'text'); this.assert.equal(this.$input()[1].type, 'file'); } + + ['@test sends an action with `` for native DOM events']() { + this.assertTriggersNativeDOMEvents(); + } } ); @@ -892,6 +949,10 @@ if (EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS) { this.assertAttr('tabindex', '10'); this.assertAttr('name', 'original-name'); } + + ['@test sends an action with `` for native DOM events']() { + this.assertTriggersNativeDOMEvents('checkbox'); + } } ); diff --git a/packages/@ember/-internals/glimmer/tests/integration/components/input-curly-test.js b/packages/@ember/-internals/glimmer/tests/integration/components/input-curly-test.js index e58971339bc..9c6cd00f02f 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/components/input-curly-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/components/input-curly-test.js @@ -76,6 +76,59 @@ class InputRenderingTest extends RenderingTestCase { element.dispatchEvent(event); }); } + + assertTriggersNativeDOMEvents(type) { + // Defaults from EventDispatcher + let 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', + mousemove: 'mouseMove', + // These four are specially handled elsewhere + // focusin: 'focusIn', + // focusout: 'focusOut', + // mouseenter: 'mouseEnter', + // mouseleave: 'mouseLeave', + submit: 'submit', + input: 'input', + change: 'change', + dragstart: 'dragStart', + drag: 'drag', + dragenter: 'dragEnter', + dragleave: 'dragLeave', + dragover: 'dragOver', + drop: 'drop', + dragend: 'dragEnd', + }; + + let triggeredEvents = []; + + let typeAttr = type ? `type="${type}" ` : ''; + let actionAttrs = Object.keys(events).map(evt => `${events[evt]}=(action 'run_${evt}')`); + let template = `{{input ${typeAttr}${actionAttrs.join(' ')}}}`; + + let actions = {}; + Object.keys(events).forEach(evt => { + actions[`run_${evt}`] = function() { + triggeredEvents.push(evt); + }; + }); + + this.render(template, { actions }); + + Object.keys(events).forEach(evt => this.triggerEvent(evt)); + + this.assert.deepEqual(triggeredEvents, Object.keys(events), 'called for all events'); + } } moduleFor( @@ -552,6 +605,10 @@ moduleFor( this.assert.equal(this.$input()[0].type, 'text'); this.assert.equal(this.$input()[1].type, 'file'); } + + ['@test sends an action with `{{input EVENT=(action "foo")}}` for native DOM events']() { + this.assertTriggersNativeDOMEvents(); + } } ); @@ -731,6 +788,10 @@ moduleFor( this.assertAttr('tabindex', '10'); this.assertAttr('name', 'original-name'); } + + ['@test sends an action with `{{input EVENT=(action "foo")}}` for native DOM events']() { + this.assertTriggersNativeDOMEvents('checkbox'); + } } );