From 3770b7b9e3fd92b164a58caef05a4d9cd650a86a Mon Sep 17 00:00:00 2001 From: Rohit Sharma Date: Thu, 4 Feb 2021 01:21:19 +0530 Subject: =?UTF-8?q?Dropdown=20=E2=80=94=20Emit=20events=20on=20the=20`.dro?= =?UTF-8?q?pdown-toggle`=20button=20(#32625)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Emit events on the dropdown button Emit the events on `.dropdown-toggle` button and then bubble up * Add migration note for events * Update the docs for events * Add unit test to check the event bubbling Co-authored-by: XhmikosR --- js/tests/unit/dropdown.spec.js | 202 ++++++++++++++++++++--------------------- 1 file changed, 99 insertions(+), 103 deletions(-) (limited to 'js/tests') diff --git a/js/tests/unit/dropdown.spec.js b/js/tests/unit/dropdown.spec.js index 8b477ba38f..bb137efed8 100644 --- a/js/tests/unit/dropdown.spec.js +++ b/js/tests/unit/dropdown.spec.js @@ -137,10 +137,9 @@ describe('Dropdown', () => { ].join('') const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') const dropdown = new Dropdown(btnDropdown) - dropdownEl.addEventListener('shown.bs.dropdown', () => { + btnDropdown.addEventListener('shown.bs.dropdown', () => { expect(btnDropdown.classList.contains('show')).toEqual(true) expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') done() @@ -198,14 +197,13 @@ describe('Dropdown', () => { const defaultValueOnTouchStart = document.documentElement.ontouchstart const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') const dropdown = new Dropdown(btnDropdown) document.documentElement.ontouchstart = () => {} spyOn(EventHandler, 'on') spyOn(EventHandler, 'off') - dropdownEl.addEventListener('shown.bs.dropdown', () => { + btnDropdown.addEventListener('shown.bs.dropdown', () => { expect(btnDropdown.classList.contains('show')).toEqual(true) expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') expect(EventHandler.on).toHaveBeenCalled() @@ -213,7 +211,7 @@ describe('Dropdown', () => { dropdown.toggle() }) - dropdownEl.addEventListener('hidden.bs.dropdown', () => { + btnDropdown.addEventListener('hidden.bs.dropdown', () => { expect(btnDropdown.classList.contains('show')).toEqual(false) expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false') expect(EventHandler.off).toHaveBeenCalled() @@ -236,10 +234,9 @@ describe('Dropdown', () => { ].join('') const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') const dropdown = new Dropdown(btnDropdown) - dropdownEl.addEventListener('shown.bs.dropdown', () => { + btnDropdown.addEventListener('shown.bs.dropdown', () => { expect(btnDropdown.classList.contains('show')).toEqual(true) expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') done() @@ -351,12 +348,11 @@ describe('Dropdown', () => { ].join('') const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') const dropdown = new Dropdown(btnDropdown, { reference: 'parent' }) - dropdownEl.addEventListener('shown.bs.dropdown', () => { + btnDropdown.addEventListener('shown.bs.dropdown', () => { expect(btnDropdown.classList.contains('show')).toEqual(true) expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') done() @@ -376,12 +372,11 @@ describe('Dropdown', () => { ].join('') const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') const dropdown = new Dropdown(btnDropdown, { reference: fixtureEl }) - dropdownEl.addEventListener('shown.bs.dropdown', () => { + btnDropdown.addEventListener('shown.bs.dropdown', () => { expect(btnDropdown.classList.contains('show')).toEqual(true) expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') done() @@ -401,12 +396,11 @@ describe('Dropdown', () => { ].join('') const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') const dropdown = new Dropdown(btnDropdown, { reference: { 0: fixtureEl, jquery: 'jQuery' } }) - dropdownEl.addEventListener('shown.bs.dropdown', () => { + btnDropdown.addEventListener('shown.bs.dropdown', () => { expect(btnDropdown.classList.contains('show')).toEqual(true) expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') done() @@ -478,10 +472,9 @@ describe('Dropdown', () => { ].join('') const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') const dropdown = new Dropdown(btnDropdown) - dropdownEl.addEventListener('shown.bs.dropdown', () => { + btnDropdown.addEventListener('shown.bs.dropdown', () => { throw new Error('should not throw shown.bs.dropdown event') }) @@ -504,10 +497,9 @@ describe('Dropdown', () => { ].join('') const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') const dropdown = new Dropdown(btnDropdown) - dropdownEl.addEventListener('shown.bs.dropdown', () => { + btnDropdown.addEventListener('shown.bs.dropdown', () => { throw new Error('should not throw shown.bs.dropdown event') }) @@ -530,10 +522,9 @@ describe('Dropdown', () => { ].join('') const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') const dropdown = new Dropdown(btnDropdown) - dropdownEl.addEventListener('shown.bs.dropdown', () => { + btnDropdown.addEventListener('shown.bs.dropdown', () => { throw new Error('should not throw shown.bs.dropdown event') }) @@ -556,14 +547,13 @@ describe('Dropdown', () => { ].join('') const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') const dropdown = new Dropdown(btnDropdown) - dropdownEl.addEventListener('show.bs.dropdown', e => { + btnDropdown.addEventListener('show.bs.dropdown', e => { e.preventDefault() }) - dropdownEl.addEventListener('shown.bs.dropdown', () => { + btnDropdown.addEventListener('shown.bs.dropdown', () => { throw new Error('should not throw shown.bs.dropdown event') }) @@ -588,10 +578,9 @@ describe('Dropdown', () => { ].join('') const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') const dropdown = new Dropdown(btnDropdown) - dropdownEl.addEventListener('shown.bs.dropdown', () => { + btnDropdown.addEventListener('shown.bs.dropdown', () => { expect(btnDropdown.classList.contains('show')).toEqual(true) done() }) @@ -610,10 +599,9 @@ describe('Dropdown', () => { ].join('') const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') const dropdown = new Dropdown(btnDropdown) - dropdownEl.addEventListener('shown.bs.dropdown', () => { + btnDropdown.addEventListener('shown.bs.dropdown', () => { throw new Error('should not throw shown.bs.dropdown event') }) @@ -636,10 +624,9 @@ describe('Dropdown', () => { ].join('') const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') const dropdown = new Dropdown(btnDropdown) - dropdownEl.addEventListener('shown.bs.dropdown', () => { + btnDropdown.addEventListener('shown.bs.dropdown', () => { throw new Error('should not throw shown.bs.dropdown event') }) @@ -662,10 +649,9 @@ describe('Dropdown', () => { ].join('') const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') const dropdown = new Dropdown(btnDropdown) - dropdownEl.addEventListener('shown.bs.dropdown', () => { + btnDropdown.addEventListener('shown.bs.dropdown', () => { throw new Error('should not throw shown.bs.dropdown event') }) @@ -688,14 +674,13 @@ describe('Dropdown', () => { ].join('') const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') const dropdown = new Dropdown(btnDropdown) - dropdownEl.addEventListener('show.bs.dropdown', e => { + btnDropdown.addEventListener('show.bs.dropdown', e => { e.preventDefault() }) - dropdownEl.addEventListener('shown.bs.dropdown', () => { + btnDropdown.addEventListener('shown.bs.dropdown', () => { throw new Error('should not throw shown.bs.dropdown event') }) @@ -720,11 +705,10 @@ describe('Dropdown', () => { ].join('') const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') const dropdownMenu = fixtureEl.querySelector('.dropdown-menu') const dropdown = new Dropdown(btnDropdown) - dropdownEl.addEventListener('hidden.bs.dropdown', () => { + btnDropdown.addEventListener('hidden.bs.dropdown', () => { expect(dropdownMenu.classList.contains('show')).toEqual(false) done() }) @@ -743,15 +727,14 @@ describe('Dropdown', () => { ].join('') const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') const dropdown = new Dropdown(btnDropdown) - dropdownEl.addEventListener('shown.bs.dropdown', () => { + btnDropdown.addEventListener('shown.bs.dropdown', () => { spyOn(dropdown._popper, 'destroy') dropdown.hide() }) - dropdownEl.addEventListener('hidden.bs.dropdown', () => { + btnDropdown.addEventListener('hidden.bs.dropdown', () => { expect(dropdown._popper.destroy).toHaveBeenCalled() done() }) @@ -770,11 +753,10 @@ describe('Dropdown', () => { ].join('') const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') const dropdownMenu = fixtureEl.querySelector('.dropdown-menu') const dropdown = new Dropdown(btnDropdown) - dropdownEl.addEventListener('hidden.bs.dropdown', () => { + btnDropdown.addEventListener('hidden.bs.dropdown', () => { throw new Error('should not throw hidden.bs.dropdown event') }) @@ -797,11 +779,10 @@ describe('Dropdown', () => { ].join('') const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') const dropdownMenu = fixtureEl.querySelector('.dropdown-menu') const dropdown = new Dropdown(btnDropdown) - dropdownEl.addEventListener('hidden.bs.dropdown', () => { + btnDropdown.addEventListener('hidden.bs.dropdown', () => { throw new Error('should not throw hidden.bs.dropdown event') }) @@ -824,10 +805,9 @@ describe('Dropdown', () => { ].join('') const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') const dropdown = new Dropdown(btnDropdown) - dropdownEl.addEventListener('hidden.bs.dropdown', () => { + btnDropdown.addEventListener('hidden.bs.dropdown', () => { throw new Error('should not throw hidden.bs.dropdown event') }) @@ -850,15 +830,14 @@ describe('Dropdown', () => { ].join('') const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') const dropdownMenu = fixtureEl.querySelector('.dropdown-menu') const dropdown = new Dropdown(btnDropdown) - dropdownEl.addEventListener('hide.bs.dropdown', e => { + btnDropdown.addEventListener('hide.bs.dropdown', e => { e.preventDefault() }) - dropdownEl.addEventListener('hidden.bs.dropdown', () => { + btnDropdown.addEventListener('hidden.bs.dropdown', () => { throw new Error('should not throw hidden.bs.dropdown event') }) @@ -983,15 +962,14 @@ describe('Dropdown', () => { ].join('') const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') let showEventTriggered = false let hideEventTriggered = false - dropdownEl.addEventListener('show.bs.dropdown', () => { + btnDropdown.addEventListener('show.bs.dropdown', () => { showEventTriggered = true }) - dropdownEl.addEventListener('shown.bs.dropdown', e => { + btnDropdown.addEventListener('shown.bs.dropdown', e => { expect(btnDropdown.classList.contains('show')).toEqual(true) expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') expect(showEventTriggered).toEqual(true) @@ -999,11 +977,11 @@ describe('Dropdown', () => { document.body.click() }) - dropdownEl.addEventListener('hide.bs.dropdown', () => { + btnDropdown.addEventListener('hide.bs.dropdown', () => { hideEventTriggered = true }) - dropdownEl.addEventListener('hidden.bs.dropdown', e => { + btnDropdown.addEventListener('hidden.bs.dropdown', e => { expect(btnDropdown.classList.contains('show')).toEqual(false) expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false') expect(hideEventTriggered).toEqual(true) @@ -1027,10 +1005,9 @@ describe('Dropdown', () => { ].join('') const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') const dropdownMenu = fixtureEl.querySelector('.dropdown-menu') - dropdownEl.addEventListener('shown.bs.dropdown', () => { + btnDropdown.addEventListener('shown.bs.dropdown', () => { expect(dropdownMenu.getAttribute('style')).toEqual(null, 'no inline style applied by Popper') done() }) @@ -1049,10 +1026,9 @@ describe('Dropdown', () => { ].join('') const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') const dropdownMenu = fixtureEl.querySelector('.dropdown-menu') - dropdownEl.addEventListener('shown.bs.dropdown', () => { + btnDropdown.addEventListener('shown.bs.dropdown', () => { // Popper adds this attribute when we use it expect(dropdownMenu.getAttribute('x-placement')).toEqual(null) done() @@ -1072,9 +1048,8 @@ describe('Dropdown', () => { ].join('') const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') - dropdownEl.addEventListener('shown.bs.dropdown', () => { + btnDropdown.addEventListener('shown.bs.dropdown', () => { expect(btnDropdown.classList.contains('show')).toEqual(true) const keyup = createEvent('keyup') @@ -1083,7 +1058,7 @@ describe('Dropdown', () => { document.dispatchEvent(keyup) }) - dropdownEl.addEventListener('hidden.bs.dropdown', () => { + btnDropdown.addEventListener('hidden.bs.dropdown', () => { expect(btnDropdown.classList.contains('show')).toEqual(false) done() }) @@ -1114,34 +1089,31 @@ describe('Dropdown', () => { expect(triggerDropdownList.length).toEqual(2) - const first = triggerDropdownList[0] - const last = triggerDropdownList[1] - const dropdownTestMenu = first.parentNode - const btnGroup = last.parentNode + const [triggerDropdownFirst, triggerDropdownLast] = triggerDropdownList - dropdownTestMenu.addEventListener('shown.bs.dropdown', () => { - expect(first.classList.contains('show')).toEqual(true) + triggerDropdownFirst.addEventListener('shown.bs.dropdown', () => { + expect(triggerDropdownFirst.classList.contains('show')).toEqual(true) expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(1) document.body.click() }) - dropdownTestMenu.addEventListener('hidden.bs.dropdown', () => { + triggerDropdownFirst.addEventListener('hidden.bs.dropdown', () => { expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(0) - last.click() + triggerDropdownLast.click() }) - btnGroup.addEventListener('shown.bs.dropdown', () => { - expect(last.classList.contains('show')).toEqual(true) + triggerDropdownLast.addEventListener('shown.bs.dropdown', () => { + expect(triggerDropdownLast.classList.contains('show')).toEqual(true) expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(1) document.body.click() }) - btnGroup.addEventListener('hidden.bs.dropdown', () => { + triggerDropdownLast.addEventListener('hidden.bs.dropdown', () => { expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(0) done() }) - first.click() + triggerDropdownFirst.click() }) it('should remove "show" class if body if tabbing outside of menu, with multiple dropdowns', done => { @@ -1165,13 +1137,10 @@ describe('Dropdown', () => { expect(triggerDropdownList.length).toEqual(2) - const first = triggerDropdownList[0] - const last = triggerDropdownList[1] - const dropdownTestMenu = first.parentNode - const btnGroup = last.parentNode + const [triggerDropdownFirst, triggerDropdownLast] = triggerDropdownList - dropdownTestMenu.addEventListener('shown.bs.dropdown', () => { - expect(first.classList.contains('show')).toEqual(true, '"show" class added on click') + 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') @@ -1180,13 +1149,13 @@ describe('Dropdown', () => { document.dispatchEvent(keyup) }) - dropdownTestMenu.addEventListener('hidden.bs.dropdown', () => { + triggerDropdownFirst.addEventListener('hidden.bs.dropdown', () => { expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(0, '"show" class removed') - last.click() + triggerDropdownLast.click() }) - btnGroup.addEventListener('shown.bs.dropdown', () => { - expect(last.classList.contains('show')).toEqual(true, '"show" class added on 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') @@ -1195,12 +1164,12 @@ describe('Dropdown', () => { document.dispatchEvent(keyup) }) - btnGroup.addEventListener('hidden.bs.dropdown', () => { + triggerDropdownLast.addEventListener('hidden.bs.dropdown', () => { expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(0, '"show" class removed') done() }) - first.click() + triggerDropdownFirst.click() }) it('should fire hide and hidden event without a clickEvent if event type is not click', done => { @@ -1214,18 +1183,17 @@ describe('Dropdown', () => { ].join('') const triggerDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') - const dropdown = fixtureEl.querySelector('.dropdown') - dropdown.addEventListener('hide.bs.dropdown', e => { + triggerDropdown.addEventListener('hide.bs.dropdown', e => { expect(e.clickEvent).toBeUndefined() }) - dropdown.addEventListener('hidden.bs.dropdown', e => { + triggerDropdown.addEventListener('hidden.bs.dropdown', e => { expect(e.clickEvent).toBeUndefined() done() }) - dropdown.addEventListener('shown.bs.dropdown', () => { + triggerDropdown.addEventListener('shown.bs.dropdown', () => { const keydown = createEvent('keydown') keydown.key = 'Escape' @@ -1235,6 +1203,42 @@ describe('Dropdown', () => { triggerDropdown.click() }) + it('should bubble up the events to the parent elements', done => { + fixtureEl.innerHTML = [ + '' + ].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