Skip to content

Commit 28d3e31

Browse files
Akira SudohChristopher A. Dhanaraj
Akira Sudoh
authored and
Christopher A. Dhanaraj
committed
fix(modal): fix wrong launchingElement when user clicks on close button (#2423)
1 parent 359f06a commit 28d3e31

File tree

2 files changed

+58
-8
lines changed

2 files changed

+58
-8
lines changed

src/components/modal/modal.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,19 @@ class Modal extends mixin(createComponent, initComponentByLauncher, eventedState
3131
this._hookCloseActions();
3232
}
3333

34-
// when .init() is called from initComponentByLauncher, run this method
34+
/**
35+
* A method that runs when `.init()` is called from `initComponentByLauncher`.
36+
* @param {Event} evt The event fired on the launcher button.
37+
*/
3538
createdByLauncher(evt) {
3639
this.show(evt);
3740
}
3841

39-
// when .changeState() is called from eventedState, determine whether or
40-
// not to emit events and callback function
42+
/**
43+
* Determines whether or not to emit events and callback function when `.changeState()` is called from `eventedState`.
44+
* @param {string} state The new state.
45+
* @returns {boolean} `true` if the given `state` is different from current state.
46+
*/
4147
shouldStateBeChanged(state) {
4248
if (state === 'shown') {
4349
return !this.element.classList.contains(this.options.classVisible);
@@ -97,6 +103,9 @@ class Modal extends mixin(createComponent, initComponentByLauncher, eventedState
97103
_hookCloseActions() {
98104
this.element.addEventListener('click', (evt) => {
99105
const closeButton = eventMatches(evt, this.options.selectorModalClose);
106+
if (closeButton) {
107+
evt.delegateTarget = closeButton; // eslint-disable-line no-param-reassign
108+
}
100109
if (closeButton || evt.target === this.element) {
101110
this.hide(evt);
102111
}

tests/spec/modal_spec.js

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,6 @@ describe('Test modal', function () {
109109
}).to.throw(Error);
110110
});
111111

112-
113112
it('Should have hide() not hide if not visible', function () {
114113
const spy = sinon.spy();
115114
events.on(element, 'modal-beinghidden', spy);
@@ -179,25 +178,67 @@ describe('Test modal', function () {
179178

180179
it('Should handle the ESC key to close the modal', function () {
181180
element.classList.add('is-visible');
182-
const spy = sinon.spy();
183-
events.on(element, 'modal-beinghidden', spy);
181+
const spyBeforeHidden = sinon.spy();
182+
const spyAfterHidden = sinon.spy();
183+
events.on(element, 'modal-beinghidden', spyBeforeHidden);
184+
events.on(element, 'modal-hidden', spyAfterHidden);
184185
element.ownerDocument.body.dispatchEvent(Object.assign(new CustomEvent('keydown', { bubbles: true }), { which: 27 }));
186+
modal.element.dispatchEvent(new CustomEvent('transitionend', { bubbles: true }));
185187
expect(element.classList.contains('is-visible')).to.be.false;
186-
expect(spy).to.be.called;
188+
expect(spyBeforeHidden).to.be.called;
189+
expect(spyAfterHidden).to.be.called;
190+
const eventDataBeforeHidden = spyBeforeHidden.firstCall.args[0].detail;
191+
const eventDataAfterHidden = spyAfterHidden.firstCall.args[0].detail;
192+
expect(eventDataBeforeHidden.launchingElement, 'Launching element for modal-beinghidden')
193+
.to.equal(element.ownerDocument.body);
194+
expect(eventDataBeforeHidden.launchingEvent.target, 'Launching event for modal-beinghidden')
195+
.to.equal(element.ownerDocument.body);
196+
expect(eventDataAfterHidden.launchingElement, 'Launching element for modal-hidden')
197+
.to.equal(element.ownerDocument.body);
198+
expect(eventDataAfterHidden.launchingEvent.target, 'Launching event for modal-hidden')
199+
.to.equal(element.ownerDocument.body);
187200
});
188201

189202
it('Should handle any elements with data-modal-close attribute to close the modal', function () {
190203
modal.show();
204+
modal.element.dispatchEvent(new CustomEvent('transitionend', { bubbles: true }));
205+
const spyBeforeHidden = sinon.spy();
206+
const spyAfterHidden = sinon.spy();
207+
events.on(modal.element, 'modal-beinghidden', spyBeforeHidden);
208+
events.on(modal.element, 'modal-hidden', spyAfterHidden);
191209
const closeButton = element.querySelector('[data-modal-close]');
192210
closeButton.dispatchEvent(new CustomEvent('click', { bubbles: true }));
193-
expect(element.classList.contains('is-visible')).to.be.false;
211+
modal.element.dispatchEvent(new CustomEvent('transitionend', { bubbles: true }));
212+
expect(element.classList.contains('is-visible'), 'Visible state').to.be.false;
213+
expect(spyBeforeHidden).to.have.been.called;
214+
expect(spyAfterHidden).to.have.been.called;
215+
const eventDataBeforeHidden = spyBeforeHidden.firstCall.args[0].detail;
216+
const eventDataAfterHidden = spyAfterHidden.firstCall.args[0].detail;
217+
expect(eventDataBeforeHidden.launchingElement, 'Launching element for modal-beinghidden').to.equal(closeButton);
218+
expect(eventDataBeforeHidden.launchingEvent.target, 'Launching event for modal-beinghidden').to.equal(closeButton);
219+
expect(eventDataAfterHidden.launchingElement, 'Launching element for modal-hidden').to.equal(closeButton);
220+
expect(eventDataAfterHidden.launchingEvent.target, 'Launching event for modal-hidden').to.equal(closeButton);
194221
});
195222

196223
it('Should handle any click outside the modal element to close the modal', function () {
197224
modal.show();
225+
modal.element.dispatchEvent(new CustomEvent('transitionend', { bubbles: true }));
226+
const spyBeforeHidden = sinon.spy();
227+
const spyAfterHidden = sinon.spy();
228+
events.on(modal.element, 'modal-beinghidden', spyBeforeHidden);
229+
events.on(modal.element, 'modal-hidden', spyAfterHidden);
198230
const containerArea = document.querySelector('.bx--modal');
199231
containerArea.dispatchEvent(new CustomEvent('click', { bubbles: true }));
232+
modal.element.dispatchEvent(new CustomEvent('transitionend', { bubbles: true }));
200233
expect(element.classList.contains('is-visible')).to.be.false;
234+
expect(spyBeforeHidden).to.have.been.called;
235+
expect(spyAfterHidden).to.have.been.called;
236+
const eventDataBeforeHidden = spyBeforeHidden.firstCall.args[0].detail;
237+
const eventDataAfterHidden = spyAfterHidden.firstCall.args[0].detail;
238+
expect(eventDataBeforeHidden.launchingElement, 'Launching element for modal-beinghidden').to.equal(element);
239+
expect(eventDataBeforeHidden.launchingEvent.target, 'Launching event for modal-beinghidden').to.equal(element);
240+
expect(eventDataAfterHidden.launchingElement, 'Launching element for modal-hidden').to.equal(element);
241+
expect(eventDataAfterHidden.launchingEvent.target, 'Launching event for modal-hidden').to.equal(element);
201242
});
202243

203244
afterEach(function () {

0 commit comments

Comments
 (0)