this._element = element
this._config = this._getConfig(config)
+ // Dispose any existing instance bound to this element before registering the new one,
+ // so its event listeners and timers are cleaned up instead of leaking
+ const existingInstance = Data.get(this._element, this.constructor.DATA_KEY)
+ if (existingInstance) {
+ existingInstance.dispose()
+ }
+
Data.set(this._element, this.constructor.DATA_KEY, this)
}
// Private
_queueCallback(callback, element, isAnimated = true) {
- executeAfterTransition(callback, element, isAnimated)
+ executeAfterTransition(() => {
+ // Don't run the completion callback if the instance was disposed mid-transition
+ if (!this._element) {
+ return
+ }
+
+ callback()
+ }, element, isAnimated)
}
_getConfig(config) {
const alertEl = fixtureEl.querySelector('.alert')
const alertBySelector = new Alert('.alert')
- const alertByElement = new Alert(alertEl)
-
expect(alertBySelector._element).toEqual(alertEl)
+
+ const alertByElement = new Alert(alertEl)
expect(alertByElement._element).toEqual(alertEl)
})
expect(elInstance._element).not.toBeDefined()
expect(selectorInstance._element).not.toBeDefined()
})
+
+ it('should dispose an existing instance when re-instantiated on the same element', () => {
+ fixtureEl.innerHTML = '<div id="foo"></div>'
+
+ const el = fixtureEl.querySelector('#foo')
+ const firstInstance = new DummyClass(el)
+ const disposeSpy = spyOn(firstInstance, 'dispose').and.callThrough()
+ const secondInstance = new DummyClass(el)
+
+ expect(disposeSpy).toHaveBeenCalled()
+ expect(DummyClass.getInstance(el)).toEqual(secondInstance)
+ expect(DummyClass.getInstance(el)).not.toEqual(firstInstance)
+ })
})
describe('dispose', () => {
expect(spy).toHaveBeenCalledWith(element, DummyClass.EVENT_KEY)
})
+
+ it('should not run a queued transition callback once the instance is disposed', () => {
+ return new Promise(resolve => {
+ createInstance()
+ const callbackSpy = jasmine.createSpy('callback')
+
+ instance._queueCallback(callbackSpy, element, true)
+ instance.dispose()
+
+ setTimeout(() => {
+ expect(callbackSpy).not.toHaveBeenCalled()
+ resolve()
+ }, 50)
+ })
+ })
})
describe('getInstance', () => {
fixtureEl.innerHTML = '<button data-bs-toggle="button">Placeholder</button>'
const buttonEl = fixtureEl.querySelector('[data-bs-toggle="button"]')
const buttonBySelector = new Button('[data-bs-toggle="button"]')
- const buttonByElement = new Button(buttonEl)
-
expect(buttonBySelector._element).toEqual(buttonEl)
+
+ const buttonByElement = new Button(buttonEl)
expect(buttonByElement._element).toEqual(buttonEl)
})
const carouselEl = fixtureEl.querySelector('#myCarousel')
const carouselBySelector = new Carousel('#myCarousel')
- const carouselByElement = new Carousel(carouselEl)
-
expect(carouselBySelector._element).toEqual(carouselEl)
+
+ const carouselByElement = new Carousel(carouselEl)
expect(carouselByElement._element).toEqual(carouselEl)
})
const collapseEl = fixtureEl.querySelector('div.my-collapse')
const collapseBySelector = new Collapse('div.my-collapse')
- const collapseByElement = new Collapse(collapseEl)
-
expect(collapseBySelector._element).toEqual(collapseEl)
+
+ const collapseByElement = new Collapse(collapseEl)
expect(collapseByElement._element).toEqual(collapseEl)
})
const inputEl = fixtureEl.querySelector('#datepickerEl')
const datepickerBySelector = new Datepicker('#datepickerEl')
- const datepickerByElement = new Datepicker(inputEl)
-
expect(datepickerBySelector._element).toEqual(inputEl)
+
+ const datepickerByElement = new Datepicker(inputEl)
expect(datepickerByElement._element).toEqual(inputEl)
})
const dialogEl = fixtureEl.querySelector('.dialog')
const dialogBySelector = new Dialog('#testDialog')
- const dialogByElement = new Dialog(dialogEl)
-
expect(dialogBySelector._element).toEqual(dialogEl)
+
+ const dialogByElement = new Dialog(dialogEl)
expect(dialogByElement._element).toEqual(dialogEl)
})
})
const btnMenu = fixtureEl.querySelector('[data-bs-toggle="menu"]')
const menuBySelector = new Menu('[data-bs-toggle="menu"]')
- const menuByElement = new Menu(btnMenu)
-
expect(menuBySelector._element).toEqual(btnMenu)
+
+ const menuByElement = new Menu(btnMenu)
expect(menuByElement._element).toEqual(btnMenu)
})
const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]')
const navBySelector = new NavOverflow('[data-bs-toggle="nav-overflow"]')
- const navByElement = new NavOverflow(navEl)
-
expect(navBySelector._element).toEqual(navEl)
+
+ const navByElement = new NavOverflow(navEl)
expect(navByElement._element).toEqual(navEl)
- navBySelector.dispose()
navByElement.dispose()
})
const otpEl = fixtureEl.querySelector('.otp')
const otpBySelector = new OtpInput('.otp')
- const otpByElement = new OtpInput(otpEl)
-
expect(otpBySelector._element).toEqual(otpEl)
+
+ const otpByElement = new OtpInput(otpEl)
expect(otpByElement._element).toEqual(otpEl)
})
const sSpyEl = fixtureEl.querySelector('.content')
const sSpyBySelector = new ScrollSpy('.content')
- const sSpyByElement = new ScrollSpy(sSpyEl)
-
expect(sSpyBySelector._element).toEqual(sSpyEl)
+
+ const sSpyByElement = new ScrollSpy(sSpyEl)
expect(sSpyByElement._element).toEqual(sSpyEl)
})
const strengthEl = fixtureEl.querySelector('.strength')
const strengthBySelector = new Strength('.strength')
- const strengthByElement = new Strength(strengthEl)
-
expect(strengthBySelector._element).toEqual(strengthEl)
+
+ const strengthByElement = new Strength(strengthEl)
expect(strengthByElement._element).toEqual(strengthEl)
})
const tabEl = fixtureEl.querySelector('[href="#home"]')
const tabBySelector = new Tab('[href="#home"]')
- const tabByElement = new Tab(tabEl)
-
expect(tabBySelector._element).toEqual(tabEl)
+
+ const tabByElement = new Tab(tabEl)
expect(tabByElement._element).toEqual(tabEl)
})
const toastEl = fixtureEl.querySelector('.toast')
const toastBySelector = new Toast('.toast')
- const toastByElement = new Toast(toastEl)
-
expect(toastBySelector._element).toEqual(toastEl)
+
+ const toastByElement = new Toast(toastEl)
expect(toastByElement._element).toEqual(toastEl)
})
const togglerEl = fixtureEl.querySelector('[data-bs-toggle="toggler"]')
const togglerBySelector = new Toggler('[data-bs-toggle="toggler"]')
- const togglerByElement = new Toggler(togglerEl)
-
expect(togglerBySelector._element).toEqual(togglerEl)
+
+ const togglerByElement = new Toggler(togglerEl)
expect(togglerByElement._element).toEqual(togglerEl)
})
})
const tooltipEl = fixtureEl.querySelector('#tooltipEl')
const tooltipBySelector = new Tooltip('#tooltipEl')
- const tooltipByElement = new Tooltip(tooltipEl)
-
expect(tooltipBySelector._element).toEqual(tooltipEl)
+
+ const tooltipByElement = new Tooltip(tooltipEl)
expect(tooltipByElement._element).toEqual(tooltipEl)
})