import FocusTrap from '../../../src/util/focustrap' import EventHandler from '../../../src/dom/event-handler' import SelectorEngine from '../../../src/dom/selector-engine' import { clearFixture, createEvent, getFixture } from '../../helpers/fixture' describe('FocusTrap', () => { let fixtureEl beforeAll(() => { fixtureEl = getFixture() }) afterEach(() => { clearFixture() }) describe('activate', () => { it('should autofocus itself by default', () => { fixtureEl.innerHTML = '
' const trapElement = fixtureEl.querySelector('div') spyOn(trapElement, 'focus') const focustrap = new FocusTrap({ trapElement }) focustrap.activate() expect(trapElement.focus).toHaveBeenCalled() }) it('if configured not to autofocus, should not autofocus itself', () => { fixtureEl.innerHTML = '
' const trapElement = fixtureEl.querySelector('div') spyOn(trapElement, 'focus') const focustrap = new FocusTrap({ trapElement, autofocus: false }) focustrap.activate() expect(trapElement.focus).not.toHaveBeenCalled() }) it('should force focus inside focus trap if it can', () => { return new Promise(resolve => { fixtureEl.innerHTML = [ 'outside', '
', ' inside', '
' ].join('') const trapElement = fixtureEl.querySelector('div') const focustrap = new FocusTrap({ trapElement }) focustrap.activate() const inside = document.getElementById('inside') const focusInListener = () => { expect(inside.focus).toHaveBeenCalled() document.removeEventListener('focusin', focusInListener) resolve() } 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', '
', ' first', ' inside', ' last', '
' ].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]) spyOn(first, 'focus').and.callThrough() const focusInListener = () => { expect(first.focus).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', '
', ' first', ' inside', ' last', '
' ].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]) spyOn(last, 'focus').and.callThrough() const focusInListener = () => { expect(last.focus).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(focustrap._config.trapElement.focus).toHaveBeenCalled() document.removeEventListener('focusin', focusInListener) resolve() } 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() spyOn(EventHandler, 'off') focustrap.deactivate() expect(EventHandler.off).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 }) spyOn(EventHandler, 'off') focustrap.deactivate() expect(EventHandler.off).not.toHaveBeenCalled() }) }) })