summaryrefslogtreecommitdiff
path: root/preact/test/browser/events.test.js
diff options
context:
space:
mode:
Diffstat (limited to 'preact/test/browser/events.test.js')
-rw-r--r--preact/test/browser/events.test.js202
1 files changed, 202 insertions, 0 deletions
diff --git a/preact/test/browser/events.test.js b/preact/test/browser/events.test.js
new file mode 100644
index 0000000..2e43cba
--- /dev/null
+++ b/preact/test/browser/events.test.js
@@ -0,0 +1,202 @@
+import { createElement, render } from 'preact';
+import {
+ setupScratch,
+ teardown,
+ supportsPassiveEvents
+} from '../_util/helpers';
+
+/** @jsx createElement */
+
+describe('event handling', () => {
+ let scratch, proto;
+
+ function fireEvent(on, type) {
+ let e = document.createEvent('Event');
+ e.initEvent(type, true, true);
+ on.dispatchEvent(e);
+ }
+
+ beforeEach(() => {
+ scratch = setupScratch();
+
+ proto = document.createElement('div').constructor.prototype;
+
+ sinon.spy(proto, 'addEventListener');
+ sinon.spy(proto, 'removeEventListener');
+ });
+
+ afterEach(() => {
+ teardown(scratch);
+
+ proto.addEventListener.restore();
+ proto.removeEventListener.restore();
+ });
+
+ it('should only register on* functions as handlers', () => {
+ let click = () => {},
+ onclick = () => {};
+
+ render(<div click={click} onClick={onclick} />, scratch);
+
+ expect(scratch.childNodes[0].attributes.length).to.equal(0);
+
+ expect(
+ proto.addEventListener
+ ).to.have.been.calledOnce.and.to.have.been.calledWithExactly(
+ 'click',
+ sinon.match.func,
+ false
+ );
+ });
+
+ it('should only register truthy values as handlers', () => {
+ function fooHandler() {}
+ const falsyHandler = false;
+
+ render(<div onClick={falsyHandler} onOtherClick={fooHandler} />, scratch);
+
+ expect(
+ proto.addEventListener
+ ).to.have.been.calledOnce.and.to.have.been.calledWithExactly(
+ 'OtherClick',
+ sinon.match.func,
+ false
+ );
+
+ expect(proto.addEventListener).not.to.have.been.calledWith('Click');
+ expect(proto.addEventListener).not.to.have.been.calledWith('click');
+ });
+
+ it('should support native event names', () => {
+ let click = sinon.spy(),
+ mousedown = sinon.spy();
+
+ render(<div onclick={() => click(1)} onmousedown={mousedown} />, scratch);
+
+ expect(proto.addEventListener)
+ .to.have.been.calledTwice.and.to.have.been.calledWith('click')
+ .and.calledWith('mousedown');
+
+ fireEvent(scratch.childNodes[0], 'click');
+ expect(click).to.have.been.calledOnce.and.calledWith(1);
+ });
+
+ it('should support camel-case event names', () => {
+ let click = sinon.spy(),
+ mousedown = sinon.spy();
+
+ render(<div onClick={() => click(1)} onMouseDown={mousedown} />, scratch);
+
+ expect(proto.addEventListener)
+ .to.have.been.calledTwice.and.to.have.been.calledWith('click')
+ .and.calledWith('mousedown');
+
+ fireEvent(scratch.childNodes[0], 'click');
+ expect(click).to.have.been.calledOnce.and.calledWith(1);
+ });
+
+ it('should update event handlers', () => {
+ let click1 = sinon.spy();
+ let click2 = sinon.spy();
+
+ render(<div onClick={click1} />, scratch);
+
+ fireEvent(scratch.childNodes[0], 'click');
+ expect(click1).to.have.been.calledOnce;
+ expect(click2).to.not.have.been.called;
+
+ click1.resetHistory();
+ click2.resetHistory();
+
+ render(<div onClick={click2} />, scratch);
+
+ fireEvent(scratch.childNodes[0], 'click');
+ expect(click1).to.not.have.been.called;
+ expect(click2).to.have.been.called;
+ });
+
+ it('should remove event handlers', () => {
+ let click = sinon.spy(),
+ mousedown = sinon.spy();
+
+ render(<div onClick={() => click(1)} onMouseDown={mousedown} />, scratch);
+ render(<div onClick={() => click(2)} />, scratch);
+
+ expect(proto.removeEventListener).to.have.been.calledWith('mousedown');
+
+ fireEvent(scratch.childNodes[0], 'mousedown');
+ expect(mousedown).not.to.have.been.called;
+
+ proto.removeEventListener.resetHistory();
+ click.resetHistory();
+ mousedown.resetHistory();
+
+ render(<div />, scratch);
+
+ expect(proto.removeEventListener).to.have.been.calledWith('click');
+
+ fireEvent(scratch.childNodes[0], 'click');
+ expect(click).not.to.have.been.called;
+ });
+
+ it('should register events not appearing on dom nodes', () => {
+ let onAnimationEnd = () => {};
+
+ render(<div onanimationend={onAnimationEnd} />, scratch);
+ expect(
+ proto.addEventListener
+ ).to.have.been.calledOnce.and.to.have.been.calledWithExactly(
+ 'animationend',
+ sinon.match.func,
+ false
+ );
+ });
+
+ // Skip test if browser doesn't support passive events
+ if (supportsPassiveEvents()) {
+ it('should use capturing for event props ending with *Capture', () => {
+ let click = sinon.spy(),
+ focus = sinon.spy();
+
+ render(
+ <div onClickCapture={click} onFocusCapture={focus}>
+ <button />
+ </div>,
+ scratch
+ );
+
+ let root = scratch.firstChild;
+ root.firstElementChild.click();
+ root.firstElementChild.focus();
+
+ expect(click, 'click').to.have.been.calledOnce;
+
+ // Focus delegation requires a 50b hack I'm not sure we want to incur
+ expect(focus, 'focus').to.have.been.calledOnce;
+
+ // IE doesn't set it
+ if (!/Edge/.test(navigator.userAgent)) {
+ expect(click).to.have.been.calledWithMatch({ eventPhase: 0 }); // capturing
+ expect(focus).to.have.been.calledWithMatch({ eventPhase: 0 }); // capturing
+ }
+ });
+
+ it('should support both capturing and non-capturing events on the same element', () => {
+ let click = sinon.spy(),
+ clickCapture = sinon.spy();
+
+ render(
+ <div onClick={click} onClickCapture={clickCapture}>
+ <button />
+ </div>,
+ scratch
+ );
+
+ let root = scratch.firstChild;
+ root.firstElementChild.click();
+
+ expect(clickCapture, 'click').to.have.been.calledOnce;
+ expect(click, 'click').to.have.been.calledOnce;
+ });
+ }
+});