import EventHandler from '../../../src/dom/event-handler' import { clearFixture, getFixture } from '../../helpers/fixture' import { noop } from '../../../src/util' describe('EventHandler', () => { let fixtureEl beforeAll(() => { fixtureEl = getFixture() }) afterEach(() => { clearFixture() }) describe('on', () => { it('should not add event listener if the event is not a string', () => { fixtureEl.innerHTML = '
' const div = fixtureEl.querySelector('div') EventHandler.on(div, null, noop) EventHandler.on(null, 'click', noop) expect().nothing() }) it('should add event listener', () => { return new Promise(resolve => { fixtureEl.innerHTML = '
' const div = fixtureEl.querySelector('div') EventHandler.on(div, 'click', () => { expect().nothing() resolve() }) div.click() }) }) it('should add namespaced event listener', () => { return new Promise(resolve => { fixtureEl.innerHTML = '
' const div = fixtureEl.querySelector('div') EventHandler.on(div, 'bs.namespace', () => { expect().nothing() resolve() }) EventHandler.trigger(div, 'bs.namespace') }) }) it('should add native namespaced event listener', () => { return new Promise(resolve => { fixtureEl.innerHTML = '
' const div = fixtureEl.querySelector('div') EventHandler.on(div, 'click.namespace', () => { expect().nothing() resolve() }) EventHandler.trigger(div, 'click') }) }) it('should handle event delegation', () => { return new Promise(resolve => { EventHandler.on(document, 'click', '.test', () => { expect().nothing() resolve() }) fixtureEl.innerHTML = '
' const div = fixtureEl.querySelector('div') div.click() }) }) it('should handle mouseenter/mouseleave like the native counterpart', () => { return new Promise(resolve => { fixtureEl.innerHTML = [ '
', '
', '
', '
', '
', '
', '
', '
' ].join('') const outer = fixtureEl.querySelector('.outer') const inner = fixtureEl.querySelector('.inner') const nested = fixtureEl.querySelector('.nested') const deep = fixtureEl.querySelector('.deep') const sibling = fixtureEl.querySelector('.sibling') const enterSpy = jasmine.createSpy('mouseenter') const leaveSpy = jasmine.createSpy('mouseleave') const delegateEnterSpy = jasmine.createSpy('mouseenter') const delegateLeaveSpy = jasmine.createSpy('mouseleave') EventHandler.on(inner, 'mouseenter', enterSpy) EventHandler.on(inner, 'mouseleave', leaveSpy) EventHandler.on(outer, 'mouseenter', '.inner', delegateEnterSpy) EventHandler.on(outer, 'mouseleave', '.inner', delegateLeaveSpy) EventHandler.on(sibling, 'mouseenter', () => { expect(enterSpy.calls.count()).toEqual(2) expect(leaveSpy.calls.count()).toEqual(2) expect(delegateEnterSpy.calls.count()).toEqual(2) expect(delegateLeaveSpy.calls.count()).toEqual(2) resolve() }) const moveMouse = (from, to) => { from.dispatchEvent(new MouseEvent('mouseout', { bubbles: true, relatedTarget: to })) to.dispatchEvent(new MouseEvent('mouseover', { bubbles: true, relatedTarget: from })) } // from outer to deep and back to outer (nested) moveMouse(outer, inner) moveMouse(inner, nested) moveMouse(nested, deep) moveMouse(deep, nested) moveMouse(nested, inner) moveMouse(inner, outer) setTimeout(() => { expect(enterSpy.calls.count()).toEqual(1) expect(leaveSpy.calls.count()).toEqual(1) expect(delegateEnterSpy.calls.count()).toEqual(1) expect(delegateLeaveSpy.calls.count()).toEqual(1) // from outer to inner to sibling (adjacent) moveMouse(outer, inner) moveMouse(inner, sibling) }, 20) }) }) }) describe('one', () => { it('should call listener just once', () => { return new Promise(resolve => { fixtureEl.innerHTML = '
' let called = 0 const div = fixtureEl.querySelector('div') const obj = { oneListener() { called++ } } EventHandler.one(div, 'bootstrap', obj.oneListener) EventHandler.trigger(div, 'bootstrap') EventHandler.trigger(div, 'bootstrap') setTimeout(() => { expect(called).toEqual(1) resolve() }, 20) }) }) it('should call delegated listener just once', () => { return new Promise(resolve => { fixtureEl.innerHTML = '
' let called = 0 const div = fixtureEl.querySelector('div') const obj = { oneListener() { called++ } } EventHandler.one(fixtureEl, 'bootstrap', 'div', obj.oneListener) EventHandler.trigger(div, 'bootstrap') EventHandler.trigger(div, 'bootstrap') setTimeout(() => { expect(called).toEqual(1) resolve() }, 20) }) }) }) describe('off', () => { it('should not remove a listener', () => { fixtureEl.innerHTML = '
' const div = fixtureEl.querySelector('div') EventHandler.off(div, null, noop) EventHandler.off(null, 'click', noop) expect().nothing() }) it('should remove a listener', () => { return new Promise(resolve => { fixtureEl.innerHTML = '
' const div = fixtureEl.querySelector('div') let called = 0 const handler = () => { called++ } EventHandler.on(div, 'foobar', handler) EventHandler.trigger(div, 'foobar') EventHandler.off(div, 'foobar', handler) EventHandler.trigger(div, 'foobar') setTimeout(() => { expect(called).toEqual(1) resolve() }, 20) }) }) it('should remove all the events', () => { return new Promise(resolve => { fixtureEl.innerHTML = '
' const div = fixtureEl.querySelector('div') let called = 0 EventHandler.on(div, 'foobar', () => { called++ }) EventHandler.on(div, 'foobar', () => { called++ }) EventHandler.trigger(div, 'foobar') EventHandler.off(div, 'foobar') EventHandler.trigger(div, 'foobar') setTimeout(() => { expect(called).toEqual(2) resolve() }, 20) }) }) it('should remove all the namespaced listeners if namespace is passed', () => { return new Promise(resolve => { fixtureEl.innerHTML = '
' const div = fixtureEl.querySelector('div') let called = 0 EventHandler.on(div, 'foobar.namespace', () => { called++ }) EventHandler.on(div, 'foofoo.namespace', () => { called++ }) EventHandler.trigger(div, 'foobar.namespace') EventHandler.trigger(div, 'foofoo.namespace') EventHandler.off(div, '.namespace') EventHandler.trigger(div, 'foobar.namespace') EventHandler.trigger(div, 'foofoo.namespace') setTimeout(() => { expect(called).toEqual(2) resolve() }, 20) }) }) it('should remove the namespaced listeners', () => { return new Promise(resolve => { fixtureEl.innerHTML = '
' const div = fixtureEl.querySelector('div') let calledCallback1 = 0 let calledCallback2 = 0 EventHandler.on(div, 'foobar.namespace', () => { calledCallback1++ }) EventHandler.on(div, 'foofoo.namespace', () => { calledCallback2++ }) EventHandler.trigger(div, 'foobar.namespace') EventHandler.off(div, 'foobar.namespace') EventHandler.trigger(div, 'foobar.namespace') EventHandler.trigger(div, 'foofoo.namespace') setTimeout(() => { expect(calledCallback1).toEqual(1) expect(calledCallback2).toEqual(1) resolve() }, 20) }) }) it('should remove the all the namespaced listeners for native events', () => { return new Promise(resolve => { fixtureEl.innerHTML = '
' const div = fixtureEl.querySelector('div') let called = 0 EventHandler.on(div, 'click.namespace', () => { called++ }) EventHandler.on(div, 'click.namespace2', () => { called++ }) EventHandler.trigger(div, 'click') EventHandler.off(div, 'click') EventHandler.trigger(div, 'click') setTimeout(() => { expect(called).toEqual(2) resolve() }, 20) }) }) it('should remove the specified namespaced listeners for native events', () => { return new Promise(resolve => { fixtureEl.innerHTML = '
' const div = fixtureEl.querySelector('div') let called1 = 0 let called2 = 0 EventHandler.on(div, 'click.namespace', () => { called1++ }) EventHandler.on(div, 'click.namespace2', () => { called2++ }) EventHandler.trigger(div, 'click') EventHandler.off(div, 'click.namespace') EventHandler.trigger(div, 'click') setTimeout(() => { expect(called1).toEqual(1) expect(called2).toEqual(2) resolve() }, 20) }) }) it('should remove a listener registered by .one', () => { return new Promise((resolve, reject) => { fixtureEl.innerHTML = '
' const div = fixtureEl.querySelector('div') const handler = () => { reject(new Error('called')) } EventHandler.one(div, 'foobar', handler) EventHandler.off(div, 'foobar', handler) EventHandler.trigger(div, 'foobar') setTimeout(() => { expect().nothing() resolve() }, 20) }) }) it('should remove the correct delegated event listener', () => { const element = document.createElement('div') const subelement = document.createElement('span') element.append(subelement) const anchor = document.createElement('a') element.append(anchor) let i = 0 const handler = () => { i++ } EventHandler.on(element, 'click', 'a', handler) EventHandler.on(element, 'click', 'span', handler) fixtureEl.append(element) EventHandler.trigger(anchor, 'click') EventHandler.trigger(subelement, 'click') // first listeners called expect(i).toEqual(2) EventHandler.off(element, 'click', 'span', handler) EventHandler.trigger(subelement, 'click') // removed listener not called expect(i).toEqual(2) EventHandler.trigger(anchor, 'click') // not removed listener called expect(i).toEqual(3) EventHandler.on(element, 'click', 'span', handler) EventHandler.trigger(anchor, 'click') EventHandler.trigger(subelement, 'click') // listener re-registered expect(i).toEqual(5) EventHandler.off(element, 'click', 'span') EventHandler.trigger(subelement, 'click') // listener removed again expect(i).toEqual(5) }) }) describe('general functionality', () => { it('should hydrate properties, and make them configurable', () => { return new Promise(resolve => { fixtureEl.innerHTML = [ '
', '
', '
', '
' ].join('') const div1 = fixtureEl.querySelector('#div1') const div2 = fixtureEl.querySelector('#div2') EventHandler.on(div1, 'click', event => { expect(event.currentTarget).toBe(div2) expect(event.delegateTarget).toBe(div1) expect(event.originalTarget).toBeNull() Object.defineProperty(event, 'currentTarget', { configurable: true, get() { return div1 } }) expect(event.currentTarget).toBe(div1) resolve() }) expect(() => { EventHandler.trigger(div1, 'click', { originalTarget: null, currentTarget: div2 }) }).not.toThrowError(TypeError) }) }) }) })