import EventHandler from '../../src/dom/event-handler.js'
import Dropdown from '../../src/dropdown.js'
import { noop } from '../../src/util/index.js'
import {
clearFixture, createEvent, getFixture, jQueryMock
} from '../helpers/fixture.js'
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 work on invalid markup', () => {
return new Promise(resolve => {
// TODO: REMOVE in v6
fixtureEl.innerHTML = [
'',
' ',
'
'
].join('')
const dropdownElem = fixtureEl.querySelector('.dropdown-menu')
const dropdown = new Dropdown(dropdownElem)
dropdownElem.addEventListener('shown.bs.dropdown', () => {
resolve()
})
expect().nothing()
dropdown.show()
})
})
it('should create offset modifier correctly when offset option is a function', () => {
return new Promise(resolve => {
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)
resolve()
}
}
})
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', () => {
return new Promise(resolve => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown).toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
resolve()
})
dropdown.toggle()
})
})
it('should destroy old popper references on toggle', () => {
return new Promise(resolve => {
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).toHaveClass('show')
spyOn(dropdown1._popper, 'destroy')
btnDropdown2.click()
})
secondDropdownEl.addEventListener('shown.bs.dropdown', () => setTimeout(() => {
expect(dropdown1._popper.destroy).toHaveBeenCalled()
resolve()
}))
dropdown1.toggle()
})
})
it('should toggle a dropdown and add/remove event listener on mobile', () => {
return new Promise(resolve => {
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 = noop
const spy = spyOn(EventHandler, 'on')
const spyOff = spyOn(EventHandler, 'off')
btnDropdown.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown).toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
expect(spy).toHaveBeenCalledWith(jasmine.any(Object), 'mouseover', noop)
dropdown.toggle()
})
btnDropdown.addEventListener('hidden.bs.dropdown', () => {
expect(btnDropdown).not.toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false')
expect(spyOff).toHaveBeenCalledWith(jasmine.any(Object), 'mouseover', noop)
document.documentElement.ontouchstart = defaultValueOnTouchStart
resolve()
})
dropdown.toggle()
})
})
it('should toggle a dropdown at the right', () => {
return new Promise(resolve => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown).toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
resolve()
})
dropdown.toggle()
})
})
it('should toggle a centered dropdown', () => {
return new Promise(resolve => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown).toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
resolve()
})
dropdown.toggle()
})
})
it('should toggle a dropup', () => {
return new Promise(resolve => {
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).toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
resolve()
})
dropdown.toggle()
})
})
it('should toggle a dropup centered', () => {
return new Promise(resolve => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropupEl = fixtureEl.querySelector('.dropup-center')
const dropdown = new Dropdown(btnDropdown)
dropupEl.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown).toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
resolve()
})
dropdown.toggle()
})
})
it('should toggle a dropup at the right', () => {
return new Promise(resolve => {
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).toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
resolve()
})
dropdown.toggle()
})
})
it('should toggle a dropend', () => {
return new Promise(resolve => {
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).toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
resolve()
})
dropdown.toggle()
})
})
it('should toggle a dropstart', () => {
return new Promise(resolve => {
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).toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
resolve()
})
dropdown.toggle()
})
})
it('should toggle a dropdown with parent reference', () => {
return new Promise(resolve => {
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).toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
resolve()
})
dropdown.toggle()
})
})
it('should toggle a dropdown with a dom node reference', () => {
return new Promise(resolve => {
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).toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
resolve()
})
dropdown.toggle()
})
})
it('should toggle a dropdown with a jquery object reference', () => {
return new Promise(resolve => {
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).toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
resolve()
})
dropdown.toggle()
})
})
it('should toggle a dropdown with a valid virtual element reference', () => {
return new Promise(resolve => {
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(spy).toHaveBeenCalled()
expect(btnDropdown).toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
resolve()
}
}
})
const spy = spyOn(virtualElement, 'getBoundingClientRect').and.callThrough()
dropdown.toggle()
})
})
it('should not toggle a dropdown if the element is disabled', () => {
return new Promise((resolve, reject) => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('shown.bs.dropdown', () => {
reject(new Error('should not throw shown.bs.dropdown event'))
})
dropdown.toggle()
setTimeout(() => {
expect().nothing()
resolve()
})
})
})
it('should not toggle a dropdown if the element contains .disabled', () => {
return new Promise((resolve, reject) => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('shown.bs.dropdown', () => {
reject(new Error('should not throw shown.bs.dropdown event'))
})
dropdown.toggle()
setTimeout(() => {
expect().nothing()
resolve()
})
})
})
it('should not toggle a dropdown if the menu is shown', () => {
return new Promise((resolve, reject) => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('shown.bs.dropdown', () => {
reject(new Error('should not throw shown.bs.dropdown event'))
})
dropdown.toggle()
setTimeout(() => {
expect().nothing()
resolve()
})
})
})
it('should not toggle a dropdown if show event is prevented', () => {
return new Promise((resolve, reject) => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('show.bs.dropdown', event => {
event.preventDefault()
})
btnDropdown.addEventListener('shown.bs.dropdown', () => {
reject(new Error('should not throw shown.bs.dropdown event'))
})
dropdown.toggle()
setTimeout(() => {
expect().nothing()
resolve()
})
})
})
})
describe('show', () => {
it('should show a dropdown', () => {
return new Promise(resolve => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown).toHaveClass('show')
resolve()
})
dropdown.show()
})
})
it('should not show a dropdown if the element is disabled', () => {
return new Promise((resolve, reject) => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('shown.bs.dropdown', () => {
reject(new Error('should not throw shown.bs.dropdown event'))
})
dropdown.show()
setTimeout(() => {
expect().nothing()
resolve()
}, 10)
})
})
it('should not show a dropdown if the element contains .disabled', () => {
return new Promise((resolve, reject) => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('shown.bs.dropdown', () => {
reject(new Error('should not throw shown.bs.dropdown event'))
})
dropdown.show()
setTimeout(() => {
expect().nothing()
resolve()
}, 10)
})
})
it('should not show a dropdown if the menu is shown', () => {
return new Promise((resolve, reject) => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('shown.bs.dropdown', () => {
reject(new Error('should not throw shown.bs.dropdown event'))
})
dropdown.show()
setTimeout(() => {
expect().nothing()
resolve()
}, 10)
})
})
it('should not show a dropdown if show event is prevented', () => {
return new Promise((resolve, reject) => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('show.bs.dropdown', event => {
event.preventDefault()
})
btnDropdown.addEventListener('shown.bs.dropdown', () => {
reject(new Error('should not throw shown.bs.dropdown event'))
})
dropdown.show()
setTimeout(() => {
expect().nothing()
resolve()
}, 10)
})
})
})
describe('hide', () => {
it('should hide a dropdown', () => {
return new Promise(resolve => {
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).not.toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false')
resolve()
})
dropdown.hide()
})
})
it('should hide a dropdown and destroy popper', () => {
return new Promise(resolve => {
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()
resolve()
})
dropdown.show()
})
})
it('should not hide a dropdown if the element is disabled', () => {
return new Promise((resolve, reject) => {
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', () => {
reject(new Error('should not throw hidden.bs.dropdown event'))
})
dropdown.hide()
setTimeout(() => {
expect(dropdownMenu).toHaveClass('show')
resolve()
}, 10)
})
})
it('should not hide a dropdown if the element contains .disabled', () => {
return new Promise((resolve, reject) => {
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', () => {
reject(new Error('should not throw hidden.bs.dropdown event'))
})
dropdown.hide()
setTimeout(() => {
expect(dropdownMenu).toHaveClass('show')
resolve()
}, 10)
})
})
it('should not hide a dropdown if the menu is not shown', () => {
return new Promise((resolve, reject) => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
const dropdown = new Dropdown(btnDropdown)
btnDropdown.addEventListener('hidden.bs.dropdown', () => {
reject(new Error('should not throw hidden.bs.dropdown event'))
})
dropdown.hide()
setTimeout(() => {
expect().nothing()
resolve()
}, 10)
})
})
it('should not hide a dropdown if hide event is prevented', () => {
return new Promise((resolve, reject) => {
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', event => {
event.preventDefault()
})
btnDropdown.addEventListener('hidden.bs.dropdown', () => {
reject(new Error('should not throw hidden.bs.dropdown event'))
})
dropdown.hide()
setTimeout(() => {
expect(dropdownMenu).toHaveClass('show')
resolve()
})
})
})
it('should remove event listener on touch-enabled device that was added in show method', () => {
return new Promise(resolve => {
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 = noop
const spy = spyOn(EventHandler, 'off')
btnDropdown.addEventListener('shown.bs.dropdown', () => {
dropdown.hide()
})
btnDropdown.addEventListener('hidden.bs.dropdown', () => {
expect(btnDropdown).not.toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false')
expect(spy).toHaveBeenCalled()
document.documentElement.ontouchstart = defaultValueOnTouchStart
resolve()
})
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()
const spy = spyOn(EventHandler, 'off')
dropdown.dispose()
expect(dropdown._menu).toBeNull()
expect(dropdown._element).toBeNull()
expect(spy).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()
const spyUpdate = spyOn(dropdown._popper, 'update')
const spyDetect = spyOn(dropdown, '_detectNavbar')
dropdown.update()
expect(spyUpdate).toHaveBeenCalled()
expect(spyDetect).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)
const spy = spyOn(dropdown, '_detectNavbar')
dropdown.update()
expect(dropdown._popper).toBeNull()
expect(spy).toHaveBeenCalled()
})
})
describe('data-api', () => {
it('should show and hide a dropdown', () => {
return new Promise(resolve => {
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', event => setTimeout(() => {
expect(btnDropdown).toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
expect(showEventTriggered).toBeTrue()
expect(event.relatedTarget).toEqual(btnDropdown)
document.body.click()
}))
btnDropdown.addEventListener('hide.bs.dropdown', () => {
hideEventTriggered = true
})
btnDropdown.addEventListener('hidden.bs.dropdown', event => {
expect(btnDropdown).not.toHaveClass('show')
expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false')
expect(hideEventTriggered).toBeTrue()
expect(event.relatedTarget).toEqual(btnDropdown)
resolve()
})
btnDropdown.click()
})
})
it('should not use "static" Popper in navbar', () => {
return new Promise(resolve => {
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).not.toBeNull()
expect(dropdownMenu.getAttribute('data-bs-popper')).toEqual('static')
resolve()
})
dropdown.show()
})
})
it('should not collapse the dropdown when clicking a select option nested in the dropdown', () => {
return new Promise(resolve => {
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()
resolve()
}, 10)
})
dropdown.show()
})
})
it('should manage bs attribute `data-bs-popper`="static" when dropdown is in navbar', () => {
return new Promise(resolve => {
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()
resolve()
})
dropdown.show()
})
})
it('should not use Popper if display set to static', () => {
return new Promise(resolve => {
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')).toBeNull()
resolve()
})
btnDropdown.click()
})
})
it('should manage bs attribute `data-bs-popper`="static" when display set to static', () => {
return new Promise(resolve => {
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()
resolve()
})
dropdown.show()
})
})
it('should remove "show" class if tabbing outside of menu', () => {
return new Promise(resolve => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
btnDropdown.addEventListener('shown.bs.dropdown', () => {
expect(btnDropdown).toHaveClass('show')
const keyup = createEvent('keyup')
keyup.key = 'Tab'
document.dispatchEvent(keyup)
})
btnDropdown.addEventListener('hidden.bs.dropdown', () => {
expect(btnDropdown).not.toHaveClass('show')
resolve()
})
btnDropdown.click()
})
})
it('should remove "show" class if body is clicked, with multiple dropdowns', () => {
return new Promise(resolve => {
fixtureEl.innerHTML = [
'',
' ',
'
',
'',
' Actions ',
' ',
' ',
'
'
].join('')
const triggerDropdownList = fixtureEl.querySelectorAll('[data-bs-toggle="dropdown"]')
expect(triggerDropdownList).toHaveSize(2)
const [triggerDropdownFirst, triggerDropdownLast] = triggerDropdownList
triggerDropdownFirst.addEventListener('shown.bs.dropdown', () => {
expect(triggerDropdownFirst).toHaveClass('show')
expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(1)
document.body.click()
})
triggerDropdownFirst.addEventListener('hidden.bs.dropdown', () => {
expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(0)
triggerDropdownLast.click()
})
triggerDropdownLast.addEventListener('shown.bs.dropdown', () => {
expect(triggerDropdownLast).toHaveClass('show')
expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(1)
document.body.click()
})
triggerDropdownLast.addEventListener('hidden.bs.dropdown', () => {
expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(0)
resolve()
})
triggerDropdownFirst.click()
})
})
it('should remove "show" class if body if tabbing outside of menu, with multiple dropdowns', () => {
return new Promise(resolve => {
fixtureEl.innerHTML = [
'',
'',
' Actions ',
' ',
' ',
'
'
].join('')
const triggerDropdownList = fixtureEl.querySelectorAll('[data-bs-toggle="dropdown"]')
expect(triggerDropdownList).toHaveSize(2)
const [triggerDropdownFirst, triggerDropdownLast] = triggerDropdownList
triggerDropdownFirst.addEventListener('shown.bs.dropdown', () => {
expect(triggerDropdownFirst).toHaveClass('show')
expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(1)
const keyup = createEvent('keyup')
keyup.key = 'Tab'
document.dispatchEvent(keyup)
})
triggerDropdownFirst.addEventListener('hidden.bs.dropdown', () => {
expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(0)
triggerDropdownLast.click()
})
triggerDropdownLast.addEventListener('shown.bs.dropdown', () => {
expect(triggerDropdownLast).toHaveClass('show')
expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(1)
const keyup = createEvent('keyup')
keyup.key = 'Tab'
document.dispatchEvent(keyup)
})
triggerDropdownLast.addEventListener('hidden.bs.dropdown', () => {
expect(fixtureEl.querySelectorAll('.dropdown-menu.show')).toHaveSize(0)
resolve()
})
triggerDropdownFirst.click()
})
})
it('should be able to identify clicked dropdown, even with multiple dropdowns in the same tag', () => {
fixtureEl.innerHTML = [
'',
' Dropdown toggle ',
' ',
' Dropdown toggle ',
' ',
'
'
].join('')
const dropdownToggle1 = fixtureEl.querySelector('#dropdown1')
const dropdownToggle2 = fixtureEl.querySelector('#dropdown2')
const dropdownMenu1 = fixtureEl.querySelector('#menu1')
const dropdownMenu2 = fixtureEl.querySelector('#menu2')
const spy = spyOn(Dropdown, 'getOrCreateInstance').and.callThrough()
dropdownToggle1.click()
expect(spy).toHaveBeenCalledWith(dropdownToggle1)
dropdownToggle2.click()
expect(spy).toHaveBeenCalledWith(dropdownToggle2)
dropdownMenu1.click()
expect(spy).toHaveBeenCalledWith(dropdownToggle1)
dropdownMenu2.click()
expect(spy).toHaveBeenCalledWith(dropdownToggle2)
})
it('should be able to show the proper menu, even with multiple dropdowns in the same tag', () => {
fixtureEl.innerHTML = [
'',
' Dropdown toggle ',
' ',
' Dropdown toggle ',
' ',
'
'
].join('')
const dropdownToggle1 = fixtureEl.querySelector('#dropdown1')
const dropdownToggle2 = fixtureEl.querySelector('#dropdown2')
const dropdownMenu1 = fixtureEl.querySelector('#menu1')
const dropdownMenu2 = fixtureEl.querySelector('#menu2')
dropdownToggle1.click()
expect(dropdownMenu1).toHaveClass('show')
expect(dropdownMenu2).not.toHaveClass('show')
dropdownToggle2.click()
expect(dropdownMenu1).not.toHaveClass('show')
expect(dropdownMenu2).toHaveClass('show')
})
it('should fire hide and hidden event without a clickEvent if event type is not click', () => {
return new Promise(resolve => {
fixtureEl.innerHTML = [
'',
' Dropdown ',
' ',
'
'
].join('')
const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
triggerDropdown.addEventListener('hide.bs.dropdown', event => {
expect(event.clickEvent).toBeUndefined()
})
triggerDropdown.addEventListener('hidden.bs.dropdown', event => {
expect(event.clickEvent).toBeUndefined()
resolve()
})
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', () => {
return new Promise(resolve => {
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()
resolve()
})
dropdown.show()
})
})
it('should ignore keyboard events within s and