import sinon from 'sinon'
import { Dom } from '../../dom'
import { Core } from '../../dom/event/core'
// import { Hook } from './hook'
describe('EventDom', () => {
describe('events', () => {
const tree = `
`
class EventDom {
public node: Element
constructor(elem?: HTMLElement) {
if (!elem) {
this.node = document.createElement('div')
} else {
this.node = elem
}
}
on(p1: any, p2?: any, p3?: any, p4?: any) {
Dom.Event.on(this.node, p1, p2, p3, p4)
return this
}
once(p1: any, p2?: any, p3?: any, p4?: any) {
Dom.Event.once(this.node, p1, p2, p3, p4)
return this
}
off(p1?: any, p2?: any, p3?: any) {
Dom.Event.off(this.node, p1, p2, p3)
return this
}
trigger(p1: any, p2?: any, p3?: any) {
Dom.Event.trigger(this.node, p1, p2, p3)
return this
}
findOne(selector: string) {
const one = this.node.querySelector(selector) as HTMLElement
return new EventDom(one)
}
appendTo(container: HTMLElement) {
if (this.node) {
container.appendChild(this.node)
}
return this
}
remove() {
if (this.node && this.node.parentNode) {
this.node.parentNode.removeChild(this.node)
}
}
}
describe('on()', () => {
it('should bind single event', () => {
const div = new EventDom()
const spy = sinon.spy()
div.on('click', spy)
div.trigger('click')
expect(spy.callCount).toEqual(1)
div.trigger('click')
expect(spy.callCount).toEqual(2)
})
it('should bind events with event-handler object', () => {
const div = new EventDom()
const spy1 = sinon.spy()
const spy2 = sinon.spy()
div.on({
click: spy1,
dblclick: spy2,
})
div.trigger('click')
div.trigger('dblclick')
expect(spy1.callCount).toEqual(1)
expect(spy2.callCount).toEqual(1)
div.trigger('click')
div.trigger('dblclick')
expect(spy1.callCount).toEqual(2)
expect(spy2.callCount).toEqual(2)
})
it('should bind event with handler object', () => {
const div = new EventDom()
const spy = sinon.spy()
div.on('click', { handler: spy })
div.trigger('click')
expect(spy.callCount).toEqual(1)
div.trigger('click')
expect(spy.callCount).toEqual(2)
})
it('should not bind event on invalid target', () => {
const text = document.createTextNode('foo') as any
const spy = sinon.spy()
Dom.Event.on(text, 'click', spy)
Dom.Event.trigger(text, 'click')
expect(spy.callCount).toEqual(0)
})
it('should delegate event', () => {
const div = document.createElement('div')
div.innerHTML = tree
const container = new EventDom(div)
const spy = sinon.spy()
container.on('click', '.one', spy)
const child = container.findOne('.one')!
child.trigger('click')
expect(spy.callCount).toEqual(1)
child.trigger('click')
expect(spy.callCount).toEqual(2)
})
// it('should throw an error when delegating with invalid selector', () => {
// const div = document.createElement('div')
// div.innerHTML = tree
// const container = new EventDom(div)
// const spy = sinon.spy()
// expect(() => container.on('click', '.unknown', spy)).toThrowError()
// })
it('should support data', () => {
const div = new EventDom()
const spy = sinon.spy()
div.on('click', { foo: 'bar' }, spy)
div.trigger('click')
expect(spy.callCount).toEqual(1)
div.trigger('click')
expect(spy.callCount).toEqual(2)
const e1 = spy.args[0][0]
const e2 = spy.args[1][0]
expect(e1.data).toEqual({ foo: 'bar' })
expect(e2.data).toEqual({ foo: 'bar' })
})
it('should bind false as event handler', () => {
const div = new EventDom()
expect(() => div.on('click', false)).not.toThrow()
})
it('should ignore invalid handler', () => {
const div = new EventDom()
expect(() => div.on('click', null as any)).not.toThrow()
})
it('should not no attaching namespace-only handlers', () => {
const div = new EventDom()
const spy = sinon.spy()
expect(() => div.on('.ns', spy)).not.toThrow()
})
})
describe('once()', () => {
it('should bind single event', () => {
const div = new EventDom()
const spy = sinon.spy()
div.once('click', spy)
div.trigger('click')
expect(spy.callCount).toEqual(1)
div.trigger('click')
expect(spy.callCount).toEqual(1)
})
it('should bind event with namespace', () => {
const div = new EventDom()
const spy = sinon.spy()
div.once('click.ns', spy)
div.trigger('click')
expect(spy.callCount).toEqual(1)
div.trigger('click')
expect(spy.callCount).toEqual(1)
})
})
describe('off()', () => {
it('should unbind single event', () => {
const div = new EventDom()
const spy1 = sinon.spy()
const spy2 = sinon.spy()
div.on('click', spy1)
div.on('click', spy2)
div.trigger('click')
expect(spy1.callCount).toEqual(1)
expect(spy2.callCount).toEqual(1)
div.off('click', spy1)
div.trigger('click')
expect(spy1.callCount).toEqual(1)
expect(spy2.callCount).toEqual(2)
})
it('should unbind events by the given event type', () => {
const div = new EventDom()
const spy1 = sinon.spy()
const spy2 = sinon.spy()
div.on('click', spy1)
div.on('click', spy2)
div.trigger('click')
expect(spy1.callCount).toEqual(1)
expect(spy2.callCount).toEqual(1)
div.off('click')
div.trigger('click')
expect(spy1.callCount).toEqual(1)
expect(spy2.callCount).toEqual(1)
})
it('should unbind events by the given namespace', () => {
const div = new EventDom()
const spy1 = sinon.spy()
const spy2 = sinon.spy()
const spy3 = sinon.spy()
div.on('click.ns', spy1)
div.on('click.ns', spy2)
div.on('click', spy3)
div.trigger('click')
expect(spy1.callCount).toEqual(1)
expect(spy2.callCount).toEqual(1)
expect(spy3.callCount).toEqual(1)
div.off('.ns')
div.trigger('click')
expect(spy1.callCount).toEqual(1)
expect(spy2.callCount).toEqual(1)
expect(spy3.callCount).toEqual(2)
})
it('should unbind events with event-handler object', () => {
const div = new EventDom()
const spy1 = sinon.spy()
const spy2 = sinon.spy()
const spy3 = sinon.spy()
div.on('click.ns', spy1)
div.on('click.ns', spy2)
div.on('click', spy3)
div.trigger('click')
expect(spy1.callCount).toEqual(1)
expect(spy2.callCount).toEqual(1)
expect(spy3.callCount).toEqual(1)
div.off({ 'click.ns': spy1, click: spy3 })
div.trigger('click')
expect(spy1.callCount).toEqual(1)
expect(spy2.callCount).toEqual(2)
expect(spy3.callCount).toEqual(1)
})
it('should unbind delegated events', () => {
const div = document.createElement('div')
div.innerHTML = tree
const container = new EventDom(div)
const spy = sinon.spy()
container.on('click', '.one', spy)
const child = container.findOne('.one')!
child.trigger('click')
expect(spy.callCount).toEqual(1)
container.off('click', '.one')
child.trigger('click')
expect(spy.callCount).toEqual(1)
})
it('should unbind delegated events with "**" selector', () => {
const div = document.createElement('div')
div.innerHTML = tree
const container = new EventDom(div)
const spy = sinon.spy()
container.on('click', '.one', spy)
const child = container.findOne('.one')!
child.trigger('click')
expect(spy.callCount).toEqual(1)
container.off('click', '**')
child.trigger('click')
expect(spy.callCount).toEqual(1)
})
it('should unbind "false" event handler', () => {
const div = new EventDom()
expect(() => div.off('click', false)).not.toThrowError()
})
it('should do nothing for elem which do not bind any events', () => {
const div = new EventDom()
expect(() => div.off()).not.toThrowError()
})
it('should do nothing for unexist event', () => {
const div = new EventDom()
div.on('whatever', () => {})
expect(() => div.off('unexist')).not.toThrowError()
})
})
describe('trigger()', () => {
it('should trigger event with namespace', () => {
const div = new EventDom().appendTo(document.body)
const spy1 = sinon.spy()
const spy2 = sinon.spy()
const spy3 = sinon.spy()
div.on('click.ns', spy1)
div.on('click.ns', spy2)
div.on('click', spy3)
div.trigger('click')
expect(spy1.callCount).toEqual(1)
expect(spy2.callCount).toEqual(1)
expect(spy3.callCount).toEqual(1)
div.trigger('click.ns')
expect(spy1.callCount).toEqual(2)
expect(spy2.callCount).toEqual(2)
expect(spy3.callCount).toEqual(1)
div.remove()
})
it('should also trigger inline binded event', () => {
const div = new EventDom()
const spy1 = sinon.spy()
const spy2 = sinon.spy(() => false)
div.on('click', spy1)
const node = div.node as HTMLDivElement
node.onclick = spy2
div.trigger('click')
expect(spy1.callCount).toEqual(1)
expect(spy2.callCount).toEqual(1)
})
it('should trigger event with EventObject', () => {
const div = new EventDom()
const spy1 = sinon.spy()
const spy2 = sinon.spy()
const spy3 = sinon.spy()
div.on('click.ns', spy1)
div.on('click.ns', spy2)
div.on('click', spy3)
div.trigger('click')
expect(spy1.callCount).toEqual(1)
expect(spy2.callCount).toEqual(1)
expect(spy3.callCount).toEqual(1)
div.trigger(new Dom.EventObject('click', { namespace: 'ns' }))
expect(spy1.callCount).toEqual(2)
expect(spy2.callCount).toEqual(2)
expect(spy3.callCount).toEqual(1)
})
it('should trigger event with EventObject created with native event', () => {
const div = new EventDom()
const spy1 = sinon.spy()
const spy2 = sinon.spy()
const spy3 = sinon.spy()
div.on('click.ns', spy1)
div.on('click.ns', spy2)
div.on('click', spy3)
const evt = document.createEvent('MouseEvents')
evt.initEvent('click', true, true)
evt.preventDefault()
div.trigger(new Dom.EventObject(evt))
expect(spy1.callCount).toEqual(1)
expect(spy2.callCount).toEqual(1)
expect(spy3.callCount).toEqual(1)
})
it('should trigger event with EventLikeObject', () => {
const div = new EventDom()
const spy1 = sinon.spy()
const spy2 = sinon.spy()
const spy3 = sinon.spy()
div.on('click.ns', spy1)
div.on('click.ns', spy2)
div.on('click', spy3)
div.trigger({ type: 'click' })
expect(spy1.callCount).toEqual(1)
expect(spy2.callCount).toEqual(1)
expect(spy3.callCount).toEqual(1)
})
it('should trigger custom event', () => {
const div = new EventDom()
const spy = sinon.spy()
div.on('foo', spy)
div.trigger('foo')
expect(spy.callCount).toEqual(1)
})
it('should bind and trigger event on any object', () => {
const obj = {}
const spy = sinon.spy()
Core.on(obj, 'foo', spy)
Core.trigger('foo', [], obj)
expect(spy.callCount).toEqual(1)
})
it('should trggier event with the given args', () => {
const div = new EventDom()
const spy = sinon.spy()
div.on('click', spy)
div.trigger('click')
expect(spy.callCount).toEqual(1)
div.trigger('click', 1)
expect(spy.callCount).toEqual(2)
expect(spy.args[1][1]).toEqual(1)
div.trigger('click', [1, { foo: 'bar' }])
expect(spy.callCount).toEqual(3)
expect(spy.args[2][1]).toEqual(1)
expect(spy.args[2][2]).toEqual({ foo: 'bar' })
})
it('should stop propagation when handler return `false`', () => {
const div = document.createElement('div')
div.innerHTML = tree
const container = new EventDom(div)
const spy1 = sinon.spy()
const spy2 = sinon.spy()
const spy3 = sinon.spy(() => false)
container.on('click', spy1)
container.on('click', '.one', spy2)
const child = container.findOne('.one')!
child.trigger('click')
expect(spy1.callCount).toEqual(1)
expect(spy2.callCount).toEqual(1)
expect(spy3.callCount).toEqual(0)
container.off('click', '.one', spy2)
container.on('click', '.one', spy3)
child.trigger('click')
expect(spy1.callCount).toEqual(1)
expect(spy2.callCount).toEqual(1)
expect(spy3.callCount).toEqual(1)
})
it('should stopImmediatePropagation 1', () => {
const div = document.createElement('div')
div.innerHTML = tree
const container = new EventDom(div)
const spy1 = sinon.spy()
const spy2 = sinon.spy((e: Dom.EventObject) => {
e.stopImmediatePropagation()
})
const spy3 = sinon.spy()
const spy4 = sinon.spy()
container.on('click', spy1)
container.on('click', '.one', spy2)
container.on('click', '.two', spy3)
container.on('click', '.three', spy4)
container.findOne('.one')!.trigger('click')
expect(spy1.callCount).toEqual(0)
expect(spy2.callCount).toEqual(1)
expect(spy3.callCount).toEqual(0)
expect(spy4.callCount).toEqual(0)
})
it('should stopImmediatePropagation 2', () => {
const div = document.createElement('div')
div.innerHTML = tree
const container = new EventDom(div)
const spy1 = sinon.spy()
const spy2 = sinon.spy((e: Dom.EventObject) => {
e.stopImmediatePropagation()
})
const spy3 = sinon.spy()
const spy4 = sinon.spy()
container.on('click', spy1)
container.on('click', '.one', spy2)
container.on('click', '.two', spy3)
container.on('click', '.three', spy4)
const evt = document.createEvent('MouseEvents')
evt.initEvent('click', true, true)
const node = container.findOne('.one')!.node as HTMLDivElement
node.dispatchEvent(evt)
expect(spy1.callCount).toEqual(0)
expect(spy2.callCount).toEqual(1)
expect(spy3.callCount).toEqual(0)
expect(spy4.callCount).toEqual(0)
})
it('should prevent default action', (done) => {
const div = document.createElement('div')
div.innerHTML = tree
const container = new EventDom(div)
container
.on('click', (e: any) => {
expect(e.isDefaultPrevented()).toBeTrue()
done()
})
.findOne('.three')!
.on('click', (e: any) => {
e.preventDefault()
})
container.findOne('.four')?.trigger('click')
})
it('should do the default action', () => {
const div = document.createElement('div')
div.innerHTML = tree
const container = new EventDom(div)
const spy1 = sinon.spy()
const spy2 = sinon.spy()
const spy3 = sinon.spy()
const spy4 = sinon.spy((e: Dom.EventObject) => {
e.stopPropagation()
})
container.on('click', spy1)
container.on('click', '.one', spy2)
const child = container.findOne('.one')!
const node = child.node as HTMLDivElement
node.onclick = spy3
child.on('click', spy4)
node.dispatchEvent(new Event('click'))
expect(spy1.callCount).toEqual(0)
expect(spy2.callCount).toEqual(0)
expect(spy3.callCount).toEqual(1)
expect(spy4.callCount).toEqual(1)
})
it('should not propagation when `onlyHandlers` is `true`', () => {
const div = document.createElement('div')
div.innerHTML = tree
const container = new EventDom(div)
const spy1 = sinon.spy()
const spy2 = sinon.spy()
const spy3 = sinon.spy()
container.on('click', spy1)
container.on('click', '.one', spy2)
const child = container.findOne('.one')!
child.on('click', spy3)
child.trigger('click', [], true)
expect(spy1.callCount).toEqual(0)
expect(spy2.callCount).toEqual(0)
expect(spy3.callCount).toEqual(1)
})
})
describe('hooks', () => {
it('should get event properties on natively-triggered event', (done) => {
const a = document.createElement('a')
const lk = new EventDom(a)
.appendTo(document.body)
.on('click', function (e: any) {
expect('detail' in e).toBeTrue()
expect('cancelable' in e).toBeTrue()
expect('bubbles' in e).toBeTrue()
expect(e.clientX).toEqual(10)
done()
})
const evt = document.createEvent('MouseEvents')
evt.initEvent('click', true, true)
lk.trigger(new Dom.EventObject(evt, { clientX: 10 }))
lk.remove()
})
it('should get event properties added by `addProperty`', (done) => {
const div = new EventDom().on('click', (e: any) => {
expect(typeof e.clientX === 'number').toBeTrue()
done()
})
const node = div.node as HTMLDivElement
const evt = document.createEvent('MouseEvents')
evt.initEvent('click')
node.dispatchEvent(evt)
})
it('shoud add custom event property with `addProperty`', (done) => {
Dom.EventObject.addProperty('testProperty', () => 42)
const div = new EventDom().on('click', (e: any) => {
expect(e.testProperty).toEqual(42)
done()
})
const node = div.node as HTMLDivElement
const evt = document.createEvent('MouseEvents')
evt.initEvent('click')
node.dispatchEvent(evt)
})
it('should apply hook to prevent triggered `image.load` events from bubbling to `window.load`', () => {
const div = new EventDom()
const win = new EventDom(window as any)
const spy1 = sinon.spy()
const spy2 = sinon.spy()
div.on('load', spy1)
win.on('load', spy2)
const node = div.node as HTMLElement
node.dispatchEvent(new Event('load'))
expect(spy1.callCount).toEqual(1)
expect(spy2.callCount).toEqual(0)
})
// it('should apply hook to prevent window to unload', () => {
// const win = new EventDom(window as any)
// const spy1 = sinon.spy(() => {
// return false
// })
// const spy2 = sinon.spy()
// win.on('beforeunload', spy1)
// win.on('unload', spy2)
// const node = win.node as HTMLElement
// node.dispatchEvent(new Event('beforeunload'))
// expect(spy1.callCount).toEqual(1)
// expect(spy2.callCount).toEqual(0)
// })
it('should call hooks', () => {
const addHook = sinon.spy()
const removeHook = sinon.spy()
const setupHook = sinon.spy()
const teardownHook = sinon.spy()
const handleHook = sinon.spy()
const triggerHook = sinon.spy()
const preDispatchHook = sinon.spy()
const postDispatchHook = sinon.spy()
Dom.EventHook.register('dblclick', {
add: addHook,
remove: removeHook,
setup: setupHook,
teardown: teardownHook,
handle: handleHook,
trigger: triggerHook,
preDispatch: preDispatchHook,
postDispatch: postDispatchHook,
})
const div = new EventDom()
const spyHandler = sinon.spy()
div.on('dblclick', spyHandler)
div.trigger('dblclick')
div.off('dblclick')
expect(addHook.callCount).toEqual(1)
expect(removeHook.callCount).toEqual(1)
expect(setupHook.callCount).toEqual(1)
expect(teardownHook.callCount).toEqual(1)
expect(handleHook.callCount).toEqual(1)
expect(triggerHook.callCount).toEqual(1)
expect(preDispatchHook.callCount).toEqual(1)
expect(postDispatchHook.callCount).toEqual(1)
Dom.EventHook.unregister('dblclick')
})
it('should not trigger event when `preDispatch` hook return `false`', () => {
const preDispatchHook = sinon.spy(() => false)
Dom.EventHook.register('dblclick', {
preDispatch: preDispatchHook as any,
})
const div = new EventDom()
const spyHandler = sinon.spy()
div.on('dblclick', spyHandler)
div.trigger('dblclick')
Dom.EventHook.unregister('dblclick')
expect(spyHandler.callCount).toEqual(0)
})
it('should not trigger event when `trigger` hook return `false`', () => {
const hook = sinon.spy(() => false)
Dom.EventHook.register('dblclick', {
trigger: hook as any,
})
const div = new EventDom()
const spyHandler = sinon.spy()
div.on('dblclick', spyHandler)
div.trigger('dblclick')
Dom.EventHook.unregister('dblclick')
expect(spyHandler.callCount).toEqual(0)
})
it('should not prevent default when `preventDefault` hook return `false`', () => {
const hook = sinon.spy(() => false)
Dom.EventHook.register('click', {
preventDefault: hook as any,
})
const div = new EventDom()
const spy1 = sinon.spy()
const spy2 = sinon.spy()
const spy3 = sinon.spy()
const node = div.node as HTMLDivElement
node.click = spy1
node.onclick = spy2
div.on('click', spy3)
div.trigger('click')
Dom.EventHook.unregister('click')
expect(spy1.callCount).toEqual(1)
expect(spy2.callCount).toEqual(1)
expect(spy3.callCount).toEqual(1)
})
})
})
})