Merge pull request #157 from ing-bank/fix/button
[button] Fix redispatching of click event
This commit is contained in:
commit
a2634b58bf
2 changed files with 96 additions and 11 deletions
|
|
@ -140,7 +140,6 @@ export class LionButton extends DelegateMixin(SlotMixin(LionLitElement)) {
|
||||||
this.disabled = false;
|
this.disabled = false;
|
||||||
this.role = 'button';
|
this.role = 'button';
|
||||||
this.tabindex = 0;
|
this.tabindex = 0;
|
||||||
this.__keydownDelegationHandler = this.__keydownDelegationHandler.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
|
|
@ -153,9 +152,29 @@ export class LionButton extends DelegateMixin(SlotMixin(LionLitElement)) {
|
||||||
this.__teardownDelegation();
|
this.__teardownDelegation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_redispatchClickEvent(oldEvent) {
|
||||||
|
// replacing `MouseEvent` with `oldEvent.constructor` breaks IE
|
||||||
|
const newEvent = new MouseEvent(oldEvent.type, oldEvent);
|
||||||
|
this.__enforceHostEventTarget(newEvent);
|
||||||
|
this.$$slot('_button').dispatchEvent(newEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent click on the fake element and cause click on the native button.
|
||||||
|
*/
|
||||||
__clickDelegationHandler(e) {
|
__clickDelegationHandler(e) {
|
||||||
e.stopPropagation(); // prevent click on the fake element and cause click on the native button
|
e.stopPropagation();
|
||||||
this.$$slot('_button').click();
|
this._redispatchClickEvent(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
__enforceHostEventTarget(event) {
|
||||||
|
try {
|
||||||
|
// this is for IE11 (and works in others), because `Object.defineProperty` does not give any effect there
|
||||||
|
event.__defineGetter__('target', () => this); // eslint-disable-line no-restricted-properties
|
||||||
|
} catch (error) {
|
||||||
|
// in case `__defineGetter__` is removed from the platform
|
||||||
|
Object.defineProperty(event, 'target', { writable: false, value: this });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
__setupDelegation() {
|
__setupDelegation() {
|
||||||
|
|
@ -181,7 +200,7 @@ export class LionButton extends DelegateMixin(SlotMixin(LionLitElement)) {
|
||||||
if (e.keyCode === 32 /* space */ || e.keyCode === 13 /* enter */) {
|
if (e.keyCode === 32 /* space */ || e.keyCode === 13 /* enter */) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.shadowRoot.querySelector('.btn').removeAttribute('active');
|
this.shadowRoot.querySelector('.btn').removeAttribute('active');
|
||||||
this.$$slot('_button').click();
|
this.shadowRoot.querySelector('.click-area').click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,20 @@
|
||||||
import { expect, fixture, html, aTimeout } from '@open-wc/testing';
|
import { expect, fixture, html, aTimeout, oneEvent } from '@open-wc/testing';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
import { pressEnter, pressSpace } from '@polymer/iron-test-helpers/mock-interactions.js';
|
import {
|
||||||
|
makeMouseEvent,
|
||||||
|
pressEnter,
|
||||||
|
pressSpace,
|
||||||
|
} from '@polymer/iron-test-helpers/mock-interactions.js';
|
||||||
|
|
||||||
import '../lion-button.js';
|
import '../lion-button.js';
|
||||||
|
|
||||||
|
function getTopElement(el) {
|
||||||
|
const { left, top } = el.getBoundingClientRect();
|
||||||
|
// to support elementFromPoint() in polyfilled browsers we have to use document
|
||||||
|
const crossBrowserRoot = el.shadowRoot.elementFromPoint ? el.shadowRoot : document;
|
||||||
|
return crossBrowserRoot.elementFromPoint(left, top);
|
||||||
|
}
|
||||||
|
|
||||||
describe('lion-button', () => {
|
describe('lion-button', () => {
|
||||||
it('behaves like native `button` in terms of a11y', async () => {
|
it('behaves like native `button` in terms of a11y', async () => {
|
||||||
const el = await fixture(`<lion-button>foo</lion-button>`);
|
const el = await fixture(`<lion-button>foo</lion-button>`);
|
||||||
|
|
@ -99,11 +110,7 @@ describe('lion-button', () => {
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const button = form.querySelector('lion-button');
|
const button = form.querySelector('lion-button');
|
||||||
const { left, top } = button.getBoundingClientRect();
|
getTopElement(button).click();
|
||||||
// to support elementFromPoint() in polyfilled browsers we have to use document
|
|
||||||
const crossBrowserRoot = button.shadowRoot.elementFromPoint ? button.shadowRoot : document;
|
|
||||||
const shadowClickAreaElement = crossBrowserRoot.elementFromPoint(left, top);
|
|
||||||
shadowClickAreaElement.click();
|
|
||||||
|
|
||||||
expect(formSubmitSpy.called).to.be.true;
|
expect(formSubmitSpy.called).to.be.true;
|
||||||
});
|
});
|
||||||
|
|
@ -138,4 +145,63 @@ describe('lion-button', () => {
|
||||||
expect(formSubmitSpy.called).to.be.true;
|
expect(formSubmitSpy.called).to.be.true;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('click event', () => {
|
||||||
|
it('is fired once', async () => {
|
||||||
|
const clickSpy = sinon.spy();
|
||||||
|
const el = await fixture(
|
||||||
|
html`
|
||||||
|
<lion-button @click="${clickSpy}"></lion-button>
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
getTopElement(el).click();
|
||||||
|
|
||||||
|
// trying to wait for other possible redispatched events
|
||||||
|
await aTimeout();
|
||||||
|
await aTimeout();
|
||||||
|
|
||||||
|
expect(clickSpy.callCount).to.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('event after redispatching', async () => {
|
||||||
|
async function prepareClickEvent(el, host) {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (host) {
|
||||||
|
// click on host like in native button
|
||||||
|
makeMouseEvent('click', { x: 11, y: 11 }, el);
|
||||||
|
} else {
|
||||||
|
// click on click-area which is then redispatched
|
||||||
|
makeMouseEvent('click', { x: 11, y: 11 }, getTopElement(el));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return oneEvent(el, 'click');
|
||||||
|
}
|
||||||
|
|
||||||
|
let hostEvent;
|
||||||
|
let redispatchedEvent;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
const el = await fixture('<lion-button></lion-button>');
|
||||||
|
hostEvent = await prepareClickEvent(el, true);
|
||||||
|
redispatchedEvent = await prepareClickEvent(el, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
const sameProperties = [
|
||||||
|
'constructor',
|
||||||
|
'composed',
|
||||||
|
'bubbles',
|
||||||
|
'cancelable',
|
||||||
|
'clientX',
|
||||||
|
'clientY',
|
||||||
|
'target',
|
||||||
|
];
|
||||||
|
|
||||||
|
sameProperties.forEach(property => {
|
||||||
|
it(`has same value of the property "${property}"`, async () => {
|
||||||
|
expect(redispatchedEvent[property]).to.equal(hostEvent[property]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue