import EventHandler from '../../../src/dom/event-handler.js'
import SelectorEngine from '../../../src/dom/selector-engine.js'
import FocusTrap from '../../../src/util/focustrap.js'
import { clearFixture, createEvent, getFixture } from '../../helpers/fixture.js'
describe('FocusTrap', () => {
let fixtureEl
beforeAll(() => {
fixtureEl = getFixture()
})
afterEach(() => {
clearFixture()
})
describe('activate', () => {
it('should autofocus itself by default', () => {
fixtureEl.innerHTML = '
'
const trapElement = fixtureEl.querySelector('div')
const spy = spyOn(trapElement, 'focus')
const focustrap = new FocusTrap({ trapElement })
focustrap.activate()
expect(spy).toHaveBeenCalled()
})
it('if configured not to autofocus, should not autofocus itself', () => {
fixtureEl.innerHTML = ''
const trapElement = fixtureEl.querySelector('div')
const spy = spyOn(trapElement, 'focus')
const focustrap = new FocusTrap({ trapElement, autofocus: false })
focustrap.activate()
expect(spy).not.toHaveBeenCalled()
})
it('should force focus inside focus trap if it can', () => {
return new Promise(resolve => {
fixtureEl.innerHTML = [
'outside',
''
].join('')
const trapElement = fixtureEl.querySelector('div')
const focustrap = new FocusTrap({ trapElement })
focustrap.activate()
const inside = document.getElementById('inside')
const focusInListener = () => {
expect(spy).toHaveBeenCalled()
document.removeEventListener('focusin', focusInListener)
resolve()
}
const spy = spyOn(inside, 'focus')
spyOn(SelectorEngine, 'focusableChildren').and.callFake(() => [inside])
document.addEventListener('focusin', focusInListener)
const focusInEvent = createEvent('focusin', { bubbles: true })
Object.defineProperty(focusInEvent, 'target', {
value: document.getElementById('outside')
})
document.dispatchEvent(focusInEvent)
})
})
it('should wrap focus around forward on tab', () => {
return new Promise(resolve => {
fixtureEl.innerHTML = [
'outside',
''
].join('')
const trapElement = fixtureEl.querySelector('div')
const focustrap = new FocusTrap({ trapElement })
focustrap.activate()
const first = document.getElementById('first')
const inside = document.getElementById('inside')
const last = document.getElementById('last')
const outside = document.getElementById('outside')
spyOn(SelectorEngine, 'focusableChildren').and.callFake(() => [first, inside, last])
const spy = spyOn(first, 'focus').and.callThrough()
const focusInListener = () => {
expect(spy).toHaveBeenCalled()
first.removeEventListener('focusin', focusInListener)
resolve()
}
first.addEventListener('focusin', focusInListener)
const keydown = createEvent('keydown')
keydown.key = 'Tab'
document.dispatchEvent(keydown)
outside.focus()
})
})
it('should wrap focus around backwards on shift-tab', () => {
return new Promise(resolve => {
fixtureEl.innerHTML = [
'outside',
''
].join('')
const trapElement = fixtureEl.querySelector('div')
const focustrap = new FocusTrap({ trapElement })
focustrap.activate()
const first = document.getElementById('first')
const inside = document.getElementById('inside')
const last = document.getElementById('last')
const outside = document.getElementById('outside')
spyOn(SelectorEngine, 'focusableChildren').and.callFake(() => [first, inside, last])
const spy = spyOn(last, 'focus').and.callThrough()
const focusInListener = () => {
expect(spy).toHaveBeenCalled()
last.removeEventListener('focusin', focusInListener)
resolve()
}
last.addEventListener('focusin', focusInListener)
const keydown = createEvent('keydown')
keydown.key = 'Tab'
keydown.shiftKey = true
document.dispatchEvent(keydown)
outside.focus()
})
})
it('should force focus on itself if there is no focusable content', () => {
return new Promise(resolve => {
fixtureEl.innerHTML = [
'outside',
''
].join('')
const trapElement = fixtureEl.querySelector('div')
const focustrap = new FocusTrap({ trapElement })
focustrap.activate()
const focusInListener = () => {
expect(spy).toHaveBeenCalled()
document.removeEventListener('focusin', focusInListener)
resolve()
}
const spy = spyOn(focustrap._config.trapElement, 'focus')
document.addEventListener('focusin', focusInListener)
const focusInEvent = createEvent('focusin', { bubbles: true })
Object.defineProperty(focusInEvent, 'target', {
value: document.getElementById('outside')
})
document.dispatchEvent(focusInEvent)
})
})
})
describe('deactivate', () => {
it('should flag itself as no longer active', () => {
const focustrap = new FocusTrap({ trapElement: fixtureEl })
focustrap.activate()
expect(focustrap._isActive).toBeTrue()
focustrap.deactivate()
expect(focustrap._isActive).toBeFalse()
})
it('should remove all event listeners', () => {
const focustrap = new FocusTrap({ trapElement: fixtureEl })
focustrap.activate()
const spy = spyOn(EventHandler, 'off')
focustrap.deactivate()
expect(spy).toHaveBeenCalled()
})
it('doesn\'t try removing event listeners unless it needs to (in case it hasn\'t been activated)', () => {
const focustrap = new FocusTrap({ trapElement: fixtureEl })
const spy = spyOn(EventHandler, 'off')
focustrap.deactivate()
expect(spy).not.toHaveBeenCalled()
})
})
})