import { describe, it, expect } from 'vitest'
import { addEventListeners, TypedEventTarget, createRoot } from '../index.ts'
import type { Dispatched } from '../lib/event-listeners.ts'
import type { Assert, Equal } from './utils.ts'
import type { Handle } from '../lib/component.ts'
describe('addEventListeners', () => {
it('adds listeners to an event target', () => {
let controller = new AbortController()
let clickCount = 0
addEventListeners(document, controller.signal, {
click: () => {
clickCount++
},
})
document.dispatchEvent(new MouseEvent('click'))
expect(clickCount).toBe(1)
document.dispatchEvent(new MouseEvent('click'))
expect(clickCount).toBe(2)
})
it('removes listeners when signal aborts', () => {
let controller = new AbortController()
let clickCount = 0
addEventListeners(document, controller.signal, {
click: () => {
clickCount++
},
})
document.dispatchEvent(new MouseEvent('click'))
expect(clickCount).toBe(1)
controller.abort()
document.dispatchEvent(new MouseEvent('click'))
expect(clickCount).toBe(1)
})
it('works with component handle signal for auto cleanup', () => {
let container = document.createElement('div')
let root = createRoot(container)
let clickCount = 0
function App(handle: Handle) {
addEventListeners(document, handle.signal, {
click: () => {
clickCount++
},
})
return () =>
App
}
root.render()
root.flush()
document.dispatchEvent(new MouseEvent('click'))
expect(clickCount).toBe(1)
root.render(null)
root.flush()
document.dispatchEvent(new MouseEvent('click'))
expect(clickCount).toBe(1)
})
it('does not pass a re-entry signal to one-argument listeners', () => {
let controller = new AbortController()
let receivedSignal: AbortSignal | undefined
addEventListeners(document, controller.signal, {
click(event) {
void event
receivedSignal = arguments[1] as AbortSignal | undefined
},
})
document.dispatchEvent(new MouseEvent('click'))
expect(receivedSignal).toBeUndefined()
})
it('aborts re-entry signal for two-argument listeners', () => {
let controller = new AbortController()
let signals: AbortSignal[] = []
addEventListeners(document, controller.signal, {
click(_event, signal) {
signals.push(signal)
},
})
document.dispatchEvent(new MouseEvent('click'))
expect(signals).toHaveLength(1)
expect(signals[0]?.aborted).toBe(false)
document.dispatchEvent(new MouseEvent('click'))
expect(signals).toHaveLength(2)
expect(signals[0]?.aborted).toBe(true)
expect(signals[1]?.aborted).toBe(false)
controller.abort()
expect(signals[1]?.aborted).toBe(true)
})
describe('types', () => {
it('provides literal event and target types for document', () => {
function _App(handle: Handle) {
addEventListeners(document, handle.signal, {
keydown: (event) => {
type _test = Assert>>
},
})
return () => App
}
})
it('provides abort signal as required second listener argument', () => {
function _App(handle: Handle) {
addEventListeners(document, handle.signal, {
keydown: (event, signal) => {
type _eventTest = Assert>>
type _signalTest = Assert>
},
})
return () => App
}
})
it('infers events from TypedEventTarget event map', () => {
type PingEventMap = {
ping: CustomEvent<{ value: number }>
}
class PingTarget extends TypedEventTarget {}
let target = new PingTarget()
let controller = new AbortController()
addEventListeners(target, controller.signal, {
ping: (event) => {
type _test = Assert<
Equal, PingTarget>>
>
void event.detail.value
},
})
})
})
})