import Dropdown from '../../src/dropdown'
import EventHandler from '../../src/dom/event-handler'
import { noop } from '../../src/util'
/** Test helpers */
import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'
describe('Dropdown', () => {
let fixtureEl
beforeAll(() => {
fixtureEl = getFixture()
})
afterEach(() => {
clearFixture()
})
describe('VERSION', () => {
it('should return plugin version', () => {
expect(Dropdown.VERSION).toEqual(jasmine.any(String))
})
})
describe('Default', () => {
it('should return plugin default config', () => {
expect(Dropdown.Default).toEqual(jasmine.any(Object))
})
})
describe('DefaultType', () => {
it('should return plugin default type config', () => {
expect(Dropdown.DefaultType).toEqual(jasmine.any(Object))
})
})
describe('DATA_KEY', () => {
it('should return plugin data key', () => {
expect(Dropdown.DATA_KEY).toEqual('bs.dropdown')
})
})
describe('constructor', () => {
it('should take care of element either passed as a CSS selector or DOM element', () => {
fixtureEl.innerHTML = [
'
',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdownBySelector = new Dropdown('[data-bs-toggle="dropdown"]')
const dropdownByElement = new Dropdown(btnDropdown)
expect(dropdownBySelector._element).toEqual(btnDropdown)
expect(dropdownByElement._element).toEqual(btnDropdown)
})
it('should create offset modifier correctly when offset option is a function', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const getOffset = jasmine.createSpy('getOffset').and.returnValue([10, 20])
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown, {
offset: getOffset,
popperConfig: {
onFirstUpdate: state => {
expect(getOffset).toHaveBeenCalledWith({
popper: state.rects.popper,
reference: state.rects.reference,
placement: state.placement
}, btnDropdown)
done()
}
}
})
const offset = dropdown._getOffset()
expect(typeof offset).toEqual('function')
dropdown.show()
})
it('should create offset modifier correctly when offset option is a string into data attribute', () => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
expect(dropdown._getOffset()).toEqual([10, 20])
})
it('should allow to pass config to Popper with `popperConfig`', () => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown, {
popperConfig: {
placement: 'left'
}
})
const popperConfig = dropdown._getPopperConfig()
expect(popperConfig.placement).toEqual('left')
})
it('should allow to pass config to Popper with `popperConfig` as a function', () => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const getPopperConfig = jasmine.createSpy('getPopperConfig').and.returnValue({ placement: 'left' })
const dropdown = new Dropdown(btnDropdown, {
popperConfig: getPopperConfig
})
const popperConfig = dropdown._getPopperConfig()
expect(getPopperConfig).toHaveBeenCalled()
expect(popperConfig.placement).toEqual('left')
})
})
describe('toggle', () => {
it('should toggle a dropdown', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown.classList.contains('show')).toEqual(true)
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
done()
})
dropdown.toggle()
})
it('should destroy old popper references on toggle', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
',
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown1 = fixtureEl.querySelector('.firstBtn')
const btnDropdown2 = fixtureEl.querySelector('.secondBtn')
const firstDropdownEl = fixtureEl.querySelector('.first')
const secondDropdownEl = fixtureEl.querySelector('.second')
const dropdown1 = new Dropdown(btnDropdown1)
firstDropdownEl.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown1.classList.contains('show')).toEqual(true)
spyOn(dropdown1._popper, 'destroy')
btnDropdown2.click()
})
secondDropdownEl.addEventListener('shown.bs.dropdown', () => setTimeout(() => {
expect(dropdown1._popper.destroy).toHaveBeenCalled()
done()
}))
dropdown1.toggle()
})
it('should toggle a dropdown and add/remove event listener on mobile', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const defaultValueOnTouchStart = document.documentElement.ontouchstart
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
document.documentElement.ontouchstart = () => {}
spyOn(EventHandler, 'on')
spyOn(EventHandler, 'off')
btnDropdown.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown.classList.contains('show')).toEqual(true)
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
expect(EventHandler.on).toHaveBeenCalledWith(jasmine.any(Object), 'mouseover', noop)
dropdown.toggle()
})
btnDropdown.addEventListener('hidden.bs.dropdown', () => {
expect(btnDropdown.classList.contains('show')).toEqual(false)
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false')
expect(EventHandler.off).toHaveBeenCalledWith(jasmine.any(Object), 'mouseover', noop)
document.documentElement.ontouchstart = defaultValueOnTouchStart
done()
})
dropdown.toggle()
})
it('should toggle a dropdown at the right', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown.classList.contains('show')).toEqual(true)
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
done()
})
dropdown.toggle()
})
it('should toggle a dropup', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropupEl = fixtureEl.querySelector('.dropup')
const dropdown = new Dropdown(btnDropdown)
dropupEl.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown.classList.contains('show')).toEqual(true)
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
done()
})
dropdown.toggle()
})
it('should toggle a dropup at the right', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropupEl = fixtureEl.querySelector('.dropup')
const dropdown = new Dropdown(btnDropdown)
dropupEl.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown.classList.contains('show')).toEqual(true)
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
done()
})
dropdown.toggle()
})
it('should toggle a dropend', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropendEl = fixtureEl.querySelector('.dropend')
const dropdown = new Dropdown(btnDropdown)
dropendEl.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown.classList.contains('show')).toEqual(true)
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
done()
})
dropdown.toggle()
})
it('should toggle a dropstart', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropstartEl = fixtureEl.querySelector('.dropstart')
const dropdown = new Dropdown(btnDropdown)
dropstartEl.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown.classList.contains('show')).toEqual(true)
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
done()
})
dropdown.toggle()
})
it('should toggle a dropdown with parent reference', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown, {
reference: 'parent'
})
btnDropdown.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown.classList.contains('show')).toEqual(true)
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
done()
})
dropdown.toggle()
})
it('should toggle a dropdown with a dom node reference', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown, {
reference: fixtureEl
})
btnDropdown.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown.classList.contains('show')).toEqual(true)
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
done()
})
dropdown.toggle()
})
it('should toggle a dropdown with a jquery object reference', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown, {
reference: { 0: fixtureEl, jquery: 'jQuery' }
})
btnDropdown.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown.classList.contains('show')).toEqual(true)
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
done()
})
dropdown.toggle()
})
it('should toggle a dropdown with a valid virtual element reference', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const virtualElement = {
nodeType: 1,
getBoundingClientRect() {
return {
width: 0,
height: 0,
top: 0,
right: 0,
bottom: 0,
left: 0
}
}
}
expect(() => new Dropdown(btnDropdown, {
reference: {}
})).toThrowError(TypeError, 'DROPDOWN: Option "reference" provided type "object" without a required "getBoundingClientRect" method.')
expect(() => new Dropdown(btnDropdown, {
reference: {
getBoundingClientRect: 'not-a-function'
}
})).toThrowError(TypeError, 'DROPDOWN: Option "reference" provided type "object" without a required "getBoundingClientRect" method.')
// use onFirstUpdate as Poppers internal update is executed async
const dropdown = new Dropdown(btnDropdown, {
reference: virtualElement,
popperConfig: {
onFirstUpdate() {
expect(virtualElement.getBoundingClientRect).toHaveBeenCalled()
expect(btnDropdown.classList.contains('show')).toEqual(true)
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
done()
}
}
})
spyOn(virtualElement, 'getBoundingClientRect').and.callThrough()
dropdown.toggle()
})
it('should not toggle a dropdown if the element is disabled', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('shown.bs.dropdown', () => {
throw new Error('should not throw shown.bs.dropdown event')
})
dropdown.toggle()
setTimeout(() => {
expect().nothing()
done()
})
})
it('should not toggle a dropdown if the element contains .disabled', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('shown.bs.dropdown', () => {
throw new Error('should not throw shown.bs.dropdown event')
})
dropdown.toggle()
setTimeout(() => {
expect().nothing()
done()
})
})
it('should not toggle a dropdown if the menu is shown', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('shown.bs.dropdown', () => {
throw new Error('should not throw shown.bs.dropdown event')
})
dropdown.toggle()
setTimeout(() => {
expect().nothing()
done()
})
})
it('should not toggle a dropdown if show event is prevented', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('show.bs.dropdown', e => {
e.preventDefault()
})
btnDropdown.addEventListener('shown.bs.dropdown', () => {
throw new Error('should not throw shown.bs.dropdown event')
})
dropdown.toggle()
setTimeout(() => {
expect().nothing()
done()
})
})
})
describe('show', () => {
it('should show a dropdown', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown.classList.contains('show')).toEqual(true)
done()
})
dropdown.show()
})
it('should not show a dropdown if the element is disabled', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('shown.bs.dropdown', () => {
throw new Error('should not throw shown.bs.dropdown event')
})
dropdown.show()
setTimeout(() => {
expect().nothing()
done()
}, 10)
})
it('should not show a dropdown if the element contains .disabled', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('shown.bs.dropdown', () => {
throw new Error('should not throw shown.bs.dropdown event')
})
dropdown.show()
setTimeout(() => {
expect().nothing()
done()
}, 10)
})
it('should not show a dropdown if the menu is shown', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('shown.bs.dropdown', () => {
throw new Error('should not throw shown.bs.dropdown event')
})
dropdown.show()
setTimeout(() => {
expect().nothing()
done()
}, 10)
})
it('should not show a dropdown if show event is prevented', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('show.bs.dropdown', e => {
e.preventDefault()
})
btnDropdown.addEventListener('shown.bs.dropdown', () => {
throw new Error('should not throw shown.bs.dropdown event')
})
dropdown.show()
setTimeout(() => {
expect().nothing()
done()
}, 10)
})
})
describe('hide', () => {
it('should hide a dropdown', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('hidden.bs.dropdown', () => {
expect(dropdownMenu.classList.contains('show')).toEqual(false)
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false')
done()
})
dropdown.hide()
})
it('should hide a dropdown and destroy popper', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('shown.bs.dropdown', () => {
spyOn(dropdown._popper, 'destroy')
dropdown.hide()
})
btnDropdown.addEventListener('hidden.bs.dropdown', () => {
expect(dropdown._popper.destroy).toHaveBeenCalled()
done()
})
dropdown.show()
})
it('should not hide a dropdown if the element is disabled', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('hidden.bs.dropdown', () => {
throw new Error('should not throw hidden.bs.dropdown event')
})
dropdown.hide()
setTimeout(() => {
expect(dropdownMenu.classList.contains('show')).toEqual(true)
done()
}, 10)
})
it('should not hide a dropdown if the element contains .disabled', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('hidden.bs.dropdown', () => {
throw new Error('should not throw hidden.bs.dropdown event')
})
dropdown.hide()
setTimeout(() => {
expect(dropdownMenu.classList.contains('show')).toEqual(true)
done()
}, 10)
})
it('should not hide a dropdown if the menu is not shown', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('hidden.bs.dropdown', () => {
throw new Error('should not throw hidden.bs.dropdown event')
})
dropdown.hide()
setTimeout(() => {
expect().nothing()
done()
}, 10)
})
it('should not hide a dropdown if hide event is prevented', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('hide.bs.dropdown', e => {
e.preventDefault()
})
btnDropdown.addEventListener('hidden.bs.dropdown', () => {
throw new Error('should not throw hidden.bs.dropdown event')
})
dropdown.hide()
setTimeout(() => {
expect(dropdownMenu.classList.contains('show')).toEqual(true)
done()
})
})
it('should remove event listener on touch-enabled device that was added in show method', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const defaultValueOnTouchStart = document.documentElement.ontouchstart
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
document.documentElement.ontouchstart = () => {}
spyOn(EventHandler, 'off')
btnDropdown.addEventListener('shown.bs.dropdown', () => {
dropdown.hide()
})
btnDropdown.addEventListener('hidden.bs.dropdown', () => {
expect(btnDropdown.classList.contains('show')).toEqual(false)
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false')
expect(EventHandler.off).toHaveBeenCalled()
document.documentElement.ontouchstart = defaultValueOnTouchStart
done()
})
dropdown.show()
})
})
describe('dispose', () => {
it('should dispose dropdown', () => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
expect(dropdown._popper).toBeNull()
expect(dropdown._menu).not.toBeNull()
expect(dropdown._element).not.toBeNull()
spyOn(EventHandler, 'off')
dropdown.dispose()
expect(dropdown._menu).toBeNull()
expect(dropdown._element).toBeNull()
expect(EventHandler.off).toHaveBeenCalledWith(btnDropdown, Dropdown.EVENT_KEY)
})
it('should dispose dropdown with Popper', () => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
dropdown.toggle()
expect(dropdown._popper).not.toBeNull()
expect(dropdown._menu).not.toBeNull()
expect(dropdown._element).not.toBeNull()
dropdown.dispose()
expect(dropdown._popper).toBeNull()
expect(dropdown._menu).toBeNull()
expect(dropdown._element).toBeNull()
})
})
describe('update', () => {
it('should call Popper and detect navbar on update', () => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
dropdown.toggle()
expect(dropdown._popper).not.toBeNull()
spyOn(dropdown._popper, 'update')
spyOn(dropdown, '_detectNavbar')
dropdown.update()
expect(dropdown._popper.update).toHaveBeenCalled()
expect(dropdown._detectNavbar).toHaveBeenCalled()
})
it('should just detect navbar on update', () => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
spyOn(dropdown, '_detectNavbar')
dropdown.update()
expect(dropdown._popper).toBeNull()
expect(dropdown._detectNavbar).toHaveBeenCalled()
})
})
describe('data-api', () => {
it('should show and hide a dropdown', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
let showEventTriggered = false
let hideEventTriggered = false
btnDropdown.addEventListener('show.bs.dropdown', () => {
showEventTriggered = true
})
btnDropdown.addEventListener('shown.bs.dropdown', e => setTimeout(() => {
expect(btnDropdown.classList.contains('show')).toEqual(true)
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
expect(showEventTriggered).toEqual(true)
expect(e.relatedTarget).toEqual(btnDropdown)
document.body.click()
}))
btnDropdown.addEventListener('hide.bs.dropdown', () => {
hideEventTriggered = true
})
btnDropdown.addEventListener('hidden.bs.dropdown', e => {
expect(btnDropdown.classList.contains('show')).toEqual(false)
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false')
expect(hideEventTriggered).toEqual(true)
expect(e.relatedTarget).toEqual(btnDropdown)
done()
})
btnDropdown.click()
})
it('should not use Popper in navbar', done => {
fixtureEl.innerHTML = [
'',
' ',
' Dropdown ',
' ',
'
',
' '
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('shown.bs.dropdown', () => {
expect(dropdown._popper).toBeNull()
expect(dropdownMenu.getAttribute('style')).toEqual(null, 'no inline style applied by Popper')
done()
})
dropdown.show()
})
it('should not collapse the dropdown when clicking a select option nested in the dropdown', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
const dropdown = new Dropdown(btnDropdown)
const hideSpy = spyOn(dropdown, '_completeHide')
btnDropdown.addEventListener('shown.bs.dropdown', () => {
const clickEvent = new MouseEvent('click', {
bubbles: true
})
dropdownMenu.querySelector('option').dispatchEvent(clickEvent)
})
dropdownMenu.addEventListener('click', event => {
expect(event.target.tagName).toMatch(/select|option/i)
Dropdown.clearMenus(event)
setTimeout(() => {
expect(hideSpy).not.toHaveBeenCalled()
done()
}, 10)
})
dropdown.show()
})
it('should manage bs attribute `data-bs-popper`="none" when dropdown is in navbar', done => {
fixtureEl.innerHTML = [
'',
' ',
' Dropdown ',
' ',
'
',
' '
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('shown.bs.dropdown', () => {
expect(dropdownMenu.getAttribute('data-bs-popper')).toEqual('none')
dropdown.hide()
})
btnDropdown.addEventListener('hidden.bs.dropdown', () => {
expect(dropdownMenu.getAttribute('data-bs-popper')).toBeNull()
done()
})
dropdown.show()
})
it('should not use Popper if display set to static', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
btnDropdown.addEventListener('shown.bs.dropdown', () => {
// Popper adds this attribute when we use it
expect(dropdownMenu.getAttribute('data-popper-placement')).toEqual(null)
done()
})
btnDropdown.click()
})
it('should manage bs attribute `data-bs-popper`="static" when display set to static', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdownMenu = fixtureEl.querySelector('.dropdown-menu')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('shown.bs.dropdown', () => {
expect(dropdownMenu.getAttribute('data-bs-popper')).toEqual('static')
dropdown.hide()
})
btnDropdown.addEventListener('hidden.bs.dropdown', () => {
expect(dropdownMenu.getAttribute('data-bs-popper')).toBeNull()
done()
})
dropdown.show()
})
it('should remove "show" class if tabbing outside of menu', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
btnDropdown.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown.classList.contains('show')).toEqual(true)
const keyup = createEvent('keyup')
keyup.key = 'Tab'
document.dispatchEvent(keyup)
})
btnDropdown.addEventListener('hidden.bs.dropdown', () => {
expect(btnDropdown.classList.contains('show')).toEqual(false)
done()
})
btnDropdown.click()
})
it('should remove "show" class if body is clicked, with multiple dropdowns', done => {
fixtureEl.innerHTML = [
'',
' ',
'
',
'',
' Actions ',
' ',
' ',
'
'
].join('')
const triggerDropdownList = fixtureEl.querySelectorAll('[data-bs-toggle="dropdown"]')
expect(triggerDropdownList.length).toEqual(2)
const [triggerDropdownFirst, triggerDropdownLast] = triggerDropdownList
triggerDropdownFirst.addEventListener('shown.bs.dropdown', () => {
expect(triggerDropdownFirst.classList.contains('show')).toEqual(true)
expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(1)
document.body.click()
})
triggerDropdownFirst.addEventListener('hidden.bs.dropdown', () => {
expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(0)
triggerDropdownLast.click()
})
triggerDropdownLast.addEventListener('shown.bs.dropdown', () => {
expect(triggerDropdownLast.classList.contains('show')).toEqual(true)
expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(1)
document.body.click()
})
triggerDropdownLast.addEventListener('hidden.bs.dropdown', () => {
expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(0)
done()
})
triggerDropdownFirst.click()
})
it('should remove "show" class if body if tabbing outside of menu, with multiple dropdowns', done => {
fixtureEl.innerHTML = [
'',
'',
' Actions ',
' ',
' ',
'
'
].join('')
const triggerDropdownList = fixtureEl.querySelectorAll('[data-bs-toggle="dropdown"]')
expect(triggerDropdownList.length).toEqual(2)
const [triggerDropdownFirst, triggerDropdownLast] = triggerDropdownList
triggerDropdownFirst.addEventListener('shown.bs.dropdown', () => {
expect(triggerDropdownFirst.classList.contains('show')).toEqual(true, '"show" class added on click')
expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(1, 'only one dropdown is shown')
const keyup = createEvent('keyup')
keyup.key = 'Tab'
document.dispatchEvent(keyup)
})
triggerDropdownFirst.addEventListener('hidden.bs.dropdown', () => {
expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(0, '"show" class removed')
triggerDropdownLast.click()
})
triggerDropdownLast.addEventListener('shown.bs.dropdown', () => {
expect(triggerDropdownLast.classList.contains('show')).toEqual(true, '"show" class added on click')
expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(1, 'only one dropdown is shown')
const keyup = createEvent('keyup')
keyup.key = 'Tab'
document.dispatchEvent(keyup)
})
triggerDropdownLast.addEventListener('hidden.bs.dropdown', () => {
expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(0, '"show" class removed')
done()
})
triggerDropdownFirst.click()
})
it('should fire hide and hidden event without a clickEvent if event type is not click', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
triggerDropdown.addEventListener('hide.bs.dropdown', e => {
expect(e.clickEvent).toBeUndefined()
})
triggerDropdown.addEventListener('hidden.bs.dropdown', e => {
expect(e.clickEvent).toBeUndefined()
done()
})
triggerDropdown.addEventListener('shown.bs.dropdown', () => {
const keydown = createEvent('keydown')
keydown.key = 'Escape'
triggerDropdown.dispatchEvent(keydown)
})
triggerDropdown.click()
})
it('should bubble up the events to the parent elements', done => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdownParent = fixtureEl.querySelector('.dropdown')
const dropdown = new Dropdown(triggerDropdown)
const showFunction = jasmine.createSpy('showFunction')
dropdownParent.addEventListener('show.bs.dropdown', showFunction)
const shownFunction = jasmine.createSpy('shownFunction')
dropdownParent.addEventListener('shown.bs.dropdown', () => {
shownFunction()
dropdown.hide()
})
const hideFunction = jasmine.createSpy('hideFunction')
dropdownParent.addEventListener('hide.bs.dropdown', hideFunction)
dropdownParent.addEventListener('hidden.bs.dropdown', () => {
expect(showFunction).toHaveBeenCalled()
expect(shownFunction).toHaveBeenCalled()
expect(hideFunction).toHaveBeenCalled()
done()
})
dropdown.show()
})
it('should ignore keyboard events within s and