Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix event handler removal in dropdown/carousel dispose #33000

Merged
merged 8 commits into from
Feb 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .bundlewatch.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
},
{
"path": "./dist/js/bootstrap.bundle.min.js",
"maxSize": "21.5 kB"
"maxSize": "21.75 kB"
},
{
"path": "./dist/js/bootstrap.esm.js",
Expand All @@ -54,7 +54,7 @@
},
{
"path": "./dist/js/bootstrap.min.js",
"maxSize": "15.5 kB"
"maxSize": "15.75 kB"
}
],
"ci": {
Expand Down
3 changes: 2 additions & 1 deletion js/src/carousel.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,6 @@ class Carousel extends BaseComponent {
}

dispose() {
super.dispose()
EventHandler.off(this._element, EVENT_KEY)

this._items = null
Expand All @@ -226,6 +225,8 @@ class Carousel extends BaseComponent {
this._isSliding = null
this._activeElement = null
this._indicatorsElement = null

super.dispose()
}

// Private
Expand Down
3 changes: 2 additions & 1 deletion js/src/dropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,14 +232,15 @@ class Dropdown extends BaseComponent {
}

dispose() {
super.dispose()
EventHandler.off(this._element, EVENT_KEY)
this._menu = null

if (this._popper) {
this._popper.destroy()
this._popper = null
}

super.dispose()
}

update() {
Expand Down
23 changes: 21 additions & 2 deletions js/tests/unit/carousel.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,8 @@ describe('Carousel', () => {

spyOn(Carousel.prototype, '_addTouchEventListeners')

// Headless browser does not support touch events, so need to fake it
// to test that touch events are add properly.
document.documentElement.ontouchstart = () => {}
const carousel = new Carousel(carouselEl)

Expand Down Expand Up @@ -1056,13 +1058,30 @@ describe('Carousel', () => {
].join('')

const carouselEl = fixtureEl.querySelector('#myCarousel')
const addEventSpy = spyOn(carouselEl, 'addEventListener').and.callThrough()
const removeEventSpy = spyOn(carouselEl, 'removeEventListener').and.callThrough()

// Headless browser does not support touch events, so need to fake it
// to test that touch events are add/removed properly.
document.documentElement.ontouchstart = () => {}

const carousel = new Carousel(carouselEl)

spyOn(EventHandler, 'off').and.callThrough()
const expectedArgs = [
['keydown', jasmine.any(Function), jasmine.any(Boolean)],
['mouseover', jasmine.any(Function), jasmine.any(Boolean)],
['mouseout', jasmine.any(Function), jasmine.any(Boolean)],
['pointerdown', jasmine.any(Function), jasmine.any(Boolean)],
['pointerup', jasmine.any(Function), jasmine.any(Boolean)]
]

expect(addEventSpy.calls.allArgs()).toEqual(expectedArgs)

carousel.dispose()

expect(EventHandler.off).toHaveBeenCalled()
expect(removeEventSpy.calls.allArgs()).toEqual(expectedArgs)

delete document.documentElement.ontouchstart
})
})

Expand Down
5 changes: 5 additions & 0 deletions js/tests/unit/dropdown.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -884,16 +884,21 @@ describe('Dropdown', () => {
].join('')

const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
spyOn(btnDropdown, 'addEventListener').and.callThrough()
spyOn(btnDropdown, 'removeEventListener').and.callThrough()

const dropdown = new Dropdown(btnDropdown)

expect(dropdown._popper).toBeNull()
expect(dropdown._menu).toBeDefined()
expect(dropdown._element).toBeDefined()
expect(btnDropdown.addEventListener).toHaveBeenCalledWith('click', jasmine.any(Function), jasmine.any(Boolean))

dropdown.dispose()

expect(dropdown._menu).toBeNull()
expect(dropdown._element).toBeNull()
expect(btnDropdown.removeEventListener).toHaveBeenCalledWith('click', jasmine.any(Function), jasmine.any(Boolean))
})

it('should dispose dropdown with Popper', () => {
Expand Down
9 changes: 6 additions & 3 deletions js/tests/unit/scrollspy.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import ScrollSpy from '../../src/scrollspy'
import Manipulator from '../../src/dom/manipulator'
import EventHandler from '../../src/dom/event-handler'

/** Test helpers */
import { getFixture, clearFixture, createEvent, jQueryMock } from '../helpers/fixture'
Expand Down Expand Up @@ -560,14 +559,18 @@ describe('ScrollSpy', () => {

describe('dispose', () => {
it('should dispose a scrollspy', () => {
spyOn(EventHandler, 'off')
fixtureEl.innerHTML = '<div style="display: none;"></div>'

const divEl = fixtureEl.querySelector('div')
spyOn(divEl, 'addEventListener').and.callThrough()
spyOn(divEl, 'removeEventListener').and.callThrough()

const scrollSpy = new ScrollSpy(divEl)
expect(divEl.addEventListener).toHaveBeenCalledWith('scroll', jasmine.any(Function), jasmine.any(Boolean))

scrollSpy.dispose()
expect(EventHandler.off).toHaveBeenCalledWith(divEl, '.bs.scrollspy')

expect(divEl.removeEventListener).toHaveBeenCalledWith('scroll', jasmine.any(Function), jasmine.any(Boolean))
})
})

Expand Down
5 changes: 5 additions & 0 deletions js/tests/unit/toast.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,13 +274,18 @@ describe('Toast', () => {
fixtureEl.innerHTML = '<div></div>'

const toastEl = fixtureEl.querySelector('div')
spyOn(toastEl, 'addEventListener').and.callThrough()
spyOn(toastEl, 'removeEventListener').and.callThrough()

const toast = new Toast(toastEl)

expect(Toast.getInstance(toastEl)).toBeDefined()
expect(toastEl.addEventListener).toHaveBeenCalledWith('click', jasmine.any(Function), jasmine.any(Boolean))

toast.dispose()

expect(Toast.getInstance(toastEl)).toBeNull()
expect(toastEl.removeEventListener).toHaveBeenCalledWith('click', jasmine.any(Function), jasmine.any(Boolean))
})

it('should allow to destroy toast and hide it before that', done => {
Expand Down
13 changes: 13 additions & 0 deletions js/tests/unit/tooltip.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -327,13 +327,26 @@ describe('Tooltip', () => {
fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'

const tooltipEl = fixtureEl.querySelector('a')
const addEventSpy = spyOn(tooltipEl, 'addEventListener').and.callThrough()
const removeEventSpy = spyOn(tooltipEl, 'removeEventListener').and.callThrough()

const tooltip = new Tooltip(tooltipEl)

expect(Tooltip.getInstance(tooltipEl)).toEqual(tooltip)

const expectedArgs = [
['mouseover', jasmine.any(Function), jasmine.any(Boolean)],
['mouseout', jasmine.any(Function), jasmine.any(Boolean)],
['focusin', jasmine.any(Function), jasmine.any(Boolean)],
['focusout', jasmine.any(Function), jasmine.any(Boolean)]
]

expect(addEventSpy.calls.allArgs()).toEqual(expectedArgs)

tooltip.dispose()

expect(Tooltip.getInstance(tooltipEl)).toEqual(null)
expect(removeEventSpy.calls.allArgs()).toEqual(expectedArgs)
})

it('should destroy a tooltip after it is shown and hidden', done => {
Expand Down