diff options
author | Johann-S <johann.servoire@gmail.com> | 2019-05-01 16:43:40 +0300 |
---|---|---|
committer | Johann-S <johann.servoire@gmail.com> | 2019-07-23 15:23:50 +0300 |
commit | 1ac07a66ce56e6ee3d2b48a649e957417f56c83e (patch) | |
tree | c255b2c904d40986adaf799c0ad87e986936c9f5 | |
parent | e916a9bc03ba916d10f9deef630f810fee918bef (diff) |
rewrite modal unit tests
-rw-r--r-- | build/build-plugins.js | 2 | ||||
-rw-r--r-- | js/index.esm.js | 2 | ||||
-rw-r--r-- | js/index.umd.js | 2 | ||||
-rw-r--r-- | js/src/modal/modal.js (renamed from js/src/modal.js) | 53 | ||||
-rw-r--r-- | js/src/modal/modal.spec.js | 974 | ||||
-rw-r--r-- | js/tests/unit/modal.js | 816 |
6 files changed, 997 insertions, 852 deletions
diff --git a/build/build-plugins.js b/build/build-plugins.js index 6989e4f4db..d0d473f666 100644 --- a/build/build-plugins.js +++ b/build/build-plugins.js @@ -37,7 +37,7 @@ const bsPlugins = { Carousel: path.resolve(__dirname, '../js/src/carousel/carousel.js'), Collapse: path.resolve(__dirname, '../js/src/collapse/collapse.js'), Dropdown: path.resolve(__dirname, '../js/src/dropdown/dropdown.js'), - Modal: path.resolve(__dirname, '../js/src/modal.js'), + Modal: path.resolve(__dirname, '../js/src/modal/modal.js'), Popover: path.resolve(__dirname, '../js/src/popover.js'), ScrollSpy: path.resolve(__dirname, '../js/src/scrollspy.js'), Tab: path.resolve(__dirname, '../js/src/tab.js'), diff --git a/js/index.esm.js b/js/index.esm.js index 3c3def944f..c2912d1910 100644 --- a/js/index.esm.js +++ b/js/index.esm.js @@ -10,7 +10,7 @@ import Button from './src/button/button' import Carousel from './src/carousel/carousel' import Collapse from './src/collapse/collapse' import Dropdown from './src/dropdown/dropdown' -import Modal from './src/modal' +import Modal from './src/modal/modal' import Popover from './src/popover' import ScrollSpy from './src/scrollspy' import Tab from './src/tab' diff --git a/js/index.umd.js b/js/index.umd.js index ed6ba1e1d3..eafc90141d 100644 --- a/js/index.umd.js +++ b/js/index.umd.js @@ -10,7 +10,7 @@ import Button from './src/button/button' import Carousel from './src/carousel/carousel' import Collapse from './src/collapse/collapse' import Dropdown from './src/dropdown/dropdown' -import Modal from './src/modal' +import Modal from './src/modal/modal' import Popover from './src/popover' import ScrollSpy from './src/scrollspy' import Tab from './src/tab' diff --git a/js/src/modal.js b/js/src/modal/modal.js index 9943f93e09..4c430a01f5 100644 --- a/js/src/modal.js +++ b/js/src/modal/modal.js @@ -15,11 +15,11 @@ import { makeArray, reflow, typeCheckConfig -} from './util/index' -import Data from './dom/data' -import EventHandler from './dom/event-handler' -import Manipulator from './dom/manipulator' -import SelectorEngine from './dom/selector-engine' +} from '../util/index' +import Data from '../dom/data' +import EventHandler from '../dom/event-handler' +import Manipulator from '../dom/manipulator' +import SelectorEngine from '../dom/selector-engine' /** * ------------------------------------------------------------------------ @@ -171,7 +171,7 @@ class Modal { const hideEvent = EventHandler.trigger(this._element, Event.HIDE) - if (!this._isShown || hideEvent.defaultPrevented) { + if (hideEvent.defaultPrevented) { return } @@ -310,14 +310,14 @@ class Modal { this.hide() } }) - } else if (!this._isShown) { + } else { EventHandler.off(this._element, Event.KEYDOWN_DISMISS) } } _setResizeEvent() { if (this._isShown) { - EventHandler.on(window, Event.RESIZE, event => this.handleUpdate(event)) + EventHandler.on(window, Event.RESIZE, () => this._adjustDialog()) } else { EventHandler.off(window, Event.RESIZE) } @@ -337,10 +337,8 @@ class Modal { } _removeBackdrop() { - if (this._backdrop) { - this._backdrop.parentNode.removeChild(this._backdrop) - this._backdrop = null - } + this._backdrop.parentNode.removeChild(this._backdrop) + this._backdrop = null } _showBackdrop(callback) { @@ -381,10 +379,6 @@ class Modal { this._backdrop.classList.add(ClassName.SHOW) - if (!callback) { - return - } - if (!animate) { callback() return @@ -399,9 +393,7 @@ class Modal { const callbackRemove = () => { this._removeBackdrop() - if (callback) { - callback() - } + callback() } if (this._element.classList.contains(ClassName.FADE)) { @@ -411,7 +403,7 @@ class Modal { } else { callbackRemove() } - } else if (callback) { + } else { callback() } } @@ -557,19 +549,8 @@ class Modal { */ EventHandler.on(document, Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) { - let target const selector = getSelectorFromElement(this) - - if (selector) { - target = SelectorEngine.findOne(selector) - } - - const config = Data.getData(target, DATA_KEY) ? - 'toggle' : - { - ...Manipulator.getDataAttributes(target), - ...Manipulator.getDataAttributes(this) - } + const target = SelectorEngine.findOne(selector) if (this.tagName === 'A' || this.tagName === 'AREA') { event.preventDefault() @@ -590,6 +571,11 @@ EventHandler.on(document, Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function ( let data = Data.getData(target, DATA_KEY) if (!data) { + const config = { + ...Manipulator.getDataAttributes(target), + ...Manipulator.getDataAttributes(this) + } + data = new Modal(target, config) } @@ -600,8 +586,9 @@ EventHandler.on(document, Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function ( * ------------------------------------------------------------------------ * jQuery * ------------------------------------------------------------------------ + * add .modal to jQuery only if jQuery is present */ - +/* istanbul ignore if */ if (typeof $ !== 'undefined') { const JQUERY_NO_CONFLICT = $.fn[NAME] $.fn[NAME] = Modal._jQueryInterface diff --git a/js/src/modal/modal.spec.js b/js/src/modal/modal.spec.js new file mode 100644 index 0000000000..f1311c5918 --- /dev/null +++ b/js/src/modal/modal.spec.js @@ -0,0 +1,974 @@ +import Modal from './modal' +import EventHandler from '../dom/event-handler' +import { makeArray } from '../util/index' + +/** Test helpers */ +import { getFixture, clearFixture, createEvent, jQueryMock } from '../../tests/helpers/fixture' + +describe('Modal', () => { + let fixtureEl + let style + + beforeAll(() => { + fixtureEl = getFixture() + + // Enable the scrollbar measurer + const css = '.modal-scrollbar-measure { position: absolute; top: -9999px; width: 50px; height: 50px; overflow: scroll; }' + + style = document.createElement('style') + style.type = 'text/css' + style.appendChild(document.createTextNode(css)) + + document.head.appendChild(style) + + // Simulate scrollbars + document.documentElement.style.paddingRight = '16px' + }) + + afterEach(() => { + clearFixture() + + document.body.classList.remove('modal-open') + document.body.removeAttribute('style') + document.body.removeAttribute('data-padding-right') + const backdropList = makeArray(document.querySelectorAll('.modal-backdrop')) + + backdropList.forEach(backdrop => { + document.body.removeChild(backdrop) + }) + + document.body.style.paddingRight = '0px' + }) + + afterAll(() => { + document.head.removeChild(style) + document.documentElement.style.paddingRight = '0px' + }) + + describe('VERSION', () => { + it('should return plugin version', () => { + expect(Modal.VERSION).toEqual(jasmine.any(String)) + }) + }) + + describe('Default', () => { + it('should return plugin default config', () => { + expect(Modal.Default).toEqual(jasmine.any(Object)) + }) + }) + + describe('toggle', () => { + it('should toggle a modal', done => { + fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" /></div>' + + const modalEl = fixtureEl.querySelector('.modal') + const modal = new Modal(modalEl) + const originalPadding = '0px' + + document.body.style.paddingRight = originalPadding + + modalEl.addEventListener('shown.bs.modal', () => { + expect(document.body.getAttribute('data-padding-right')).toEqual(originalPadding, 'original body padding should be stored in data-padding-right') + modal.toggle() + }) + + modalEl.addEventListener('hidden.bs.modal', () => { + expect(document.body.getAttribute('data-padding-right')).toBeNull() + expect().nothing() + done() + }) + + modal.toggle() + }) + + it('should adjust the inline padding of fixed elements when opening and restore when closing', done => { + fixtureEl.innerHTML = [ + '<div class="fixed-top" style="padding-right: 0px"></div>', + '<div class="modal"><div class="modal-dialog" /></div>' + ].join('') + + const fixedEl = fixtureEl.querySelector('.fixed-top') + const originalPadding = parseInt(window.getComputedStyle(fixedEl).paddingRight, 10) + const modalEl = fixtureEl.querySelector('.modal') + const modal = new Modal(modalEl) + + modalEl.addEventListener('shown.bs.modal', () => { + const expectedPadding = originalPadding + modal._getScrollbarWidth() + const currentPadding = parseInt(window.getComputedStyle(modalEl).paddingRight, 10) + + expect(fixedEl.getAttribute('data-padding-right')).toEqual('0px', 'original fixed element padding should be stored in data-padding-right') + expect(currentPadding).toEqual(expectedPadding, 'fixed element padding should be adjusted while opening') + modal.toggle() + }) + + modalEl.addEventListener('hidden.bs.modal', () => { + const currentPadding = parseInt(window.getComputedStyle(modalEl).paddingRight, 10) + + expect(fixedEl.getAttribute('data-padding-right')).toEqual(null, 'data-padding-right should be cleared after closing') + expect(currentPadding).toEqual(originalPadding, 'fixed element padding should be reset after closing') + done() + }) + + modal.toggle() + }) + + it('should adjust the inline margin of sticky elements when opening and restore when closing', done => { + fixtureEl.innerHTML = [ + '<div class="sticky-top" style="margin-right: 0px;"></div>', + '<div class="modal"><div class="modal-dialog" /></div>' + ].join('') + + const stickyTopEl = fixtureEl.querySelector('.sticky-top') + const originalMargin = parseInt(window.getComputedStyle(stickyTopEl).marginRight, 10) + const modalEl = fixtureEl.querySelector('.modal') + const modal = new Modal(modalEl) + + modalEl.addEventListener('shown.bs.modal', () => { + const expectedMargin = originalMargin - modal._getScrollbarWidth() + const currentMargin = parseInt(window.getComputedStyle(stickyTopEl).marginRight, 10) + + expect(stickyTopEl.getAttribute('data-margin-right')).toEqual('0px', 'original sticky element margin should be stored in data-margin-right') + expect(currentMargin).toEqual(expectedMargin, 'sticky element margin should be adjusted while opening') + modal.toggle() + }) + + modalEl.addEventListener('hidden.bs.modal', () => { + const currentMargin = parseInt(window.getComputedStyle(stickyTopEl).marginRight, 10) + + expect(stickyTopEl.getAttribute('data-margin-right')).toEqual(null, 'data-margin-right should be cleared after closing') + expect(currentMargin).toEqual(originalMargin, 'sticky element margin should be reset after closing') + done() + }) + + modal.toggle() + }) + + it('should ignore values set via CSS when trying to restore body padding after closing', done => { + fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" /></div>' + const styleTest = document.createElement('style') + + styleTest.type = 'text/css' + styleTest.appendChild(document.createTextNode('body { padding-right: 7px; }')) + document.head.appendChild(styleTest) + + const modalEl = fixtureEl.querySelector('.modal') + const modal = new Modal(modalEl) + + modalEl.addEventListener('shown.bs.modal', () => { + modal.toggle() + }) + + modalEl.addEventListener('hidden.bs.modal', () => { + expect(window.getComputedStyle(document.body).paddingLeft).toEqual('0px', 'body does not have inline padding set') + document.head.removeChild(styleTest) + done() + }) + + modal.toggle() + }) + + it('should ignore other inline styles when trying to restore body padding after closing', done => { + fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" /></div>' + const styleTest = document.createElement('style') + + styleTest.type = 'text/css' + styleTest.appendChild(document.createTextNode('body { padding-right: 7px; }')) + + document.head.appendChild(styleTest) + document.body.style.color = 'red' + + const modalEl = fixtureEl.querySelector('.modal') + const modal = new Modal(modalEl) + + modalEl.addEventListener('shown.bs.modal', () => { + modal.toggle() + }) + + modalEl.addEventListener('hidden.bs.modal', () => { + const bodyPaddingRight = document.body.style.paddingRight + + expect(bodyPaddingRight === '0px' || bodyPaddingRight === '').toEqual(true, 'body does not have inline padding set') + expect(document.body.style.color).toEqual('red', 'body still has other inline styles set') + document.head.removeChild(styleTest) + document.body.removeAttribute('style') + done() + }) + + modal.toggle() + }) + + it('should properly restore non-pixel inline body padding after closing', done => { + fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" /></div>' + + document.body.style.paddingRight = '5%' + + const modalEl = fixtureEl.querySelector('.modal') + const modal = new Modal(modalEl) + + modalEl.addEventListener('shown.bs.modal', () => { + modal.toggle() + }) + + modalEl.addEventListener('hidden.bs.modal', () => { + expect(document.body.style.paddingRight).toEqual('5%') + document.body.removeAttribute('style') + done() + }) + + modal.toggle() + }) + }) + + describe('show', () => { + it('should show a modal', done => { + fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" /></div>' + + const modalEl = fixtureEl.querySelector('.modal') + const modal = new Modal(modalEl) + + modalEl.addEventListener('show.bs.modal', e => { + expect(e).toBeDefined() + }) + + modalEl.addEventListener('shown.bs.modal', () => { + expect(modalEl.getAttribute('aria-modal')).toEqual('true') + expect(modalEl.getAttribute('aria-hidden')).toEqual(null) + expect(modalEl.style.display).toEqual('block') + expect(document.querySelector('.modal-backdrop')).toBeDefined() + done() + }) + + modal.show() + }) + + it('should show a modal without backdrop', done => { + fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" /></div>' + + const modalEl = fixtureEl.querySelector('.modal') + const modal = new Modal(modalEl, { + backdrop: false + }) + + modalEl.addEventListener('show.bs.modal', e => { + expect(e).toBeDefined() + }) + + modalEl.addEventListener('shown.bs.modal', () => { + expect(modalEl.getAttribute('aria-modal')).toEqual('true') + expect(modalEl.getAttribute('aria-hidden')).toEqual(null) + expect(modalEl.style.display).toEqual('block') + expect(document.querySelector('.modal-backdrop')).toBeNull() + done() + }) + + modal.show() + }) + + it('should show a modal and append the element', done => { + const modalEl = document.createElement('div') + const id = 'dynamicModal' + + modalEl.setAttribute('id', id) + modalEl.classList.add('modal') + modalEl.innerHTML = '<div class="modal-dialog"></div>' + + const modal = new Modal(modalEl) + + modalEl.addEventListener('shown.bs.modal', () => { + const dynamicModal = document.getElementById(id) + expect(dynamicModal).toBeDefined() + dynamicModal.parentNode.removeChild(dynamicModal) + done() + }) + + modal.show() + }) + + it('should do nothing if a modal is shown', () => { + fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" /></div>' + + const modalEl = fixtureEl.querySelector('.modal') + const modal = new Modal(modalEl) + + spyOn(EventHandler, 'trigger') + modal._isShown = true + + modal.show() + + expect(EventHandler.trigger).not.toHaveBeenCalled() + }) + + it('should do nothing if a modal is transitioning', () => { + fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" /></div>' + + const modalEl = fixtureEl.querySelector('.modal') + const modal = new Modal(modalEl) + + spyOn(EventHandler, 'trigger') + modal._isTransitioning = true + + modal.show() + + expect(EventHandler.trigger).not.toHaveBeenCalled() + }) + + it('should not fire shown event when show is prevented', done => { + fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" /></div>' + + const modalEl = fixtureEl.querySelector('.modal') + const modal = new Modal(modalEl) + + modalEl.addEventListener('show.bs.modal', e => { + e.preventDefault() + + const expectedDone = () => { + expect().nothing() + done() + } + + setTimeout(expectedDone, 10) + }) + + modalEl.addEventListener('shown.bs.modal', () => { + throw new Error('shown event triggered') + }) + + modal.show() + }) + + it('should set is transitioning if fade class is present', done => { + fixtureEl.innerHTML = '<div class="modal fade"><div class="modal-dialog" /></div>' + + const modalEl = fixtureEl.querySelector('.modal') + const modal = new Modal(modalEl) + + modalEl.addEventListener('show.bs.modal', () => { + expect(modal._isTransitioning).toEqual(true) + }) + + modalEl.addEventListener('shown.bs.modal', () => { + expect(modal._isTransitioning).toEqual(false) + done() + }) + + modal.show() + }) + + it('should close modal when a click occured on data-dismiss="modal"', done => { + fixtureEl.innerHTML = [ + '<div class="modal fade">', + ' <div class="modal-dialog">', + ' <div class="modal-header">', + ' <button type="button" data-dismiss="modal"></button>', + ' </div>', + ' </div>', + '</div>' + ].join('') + + const modalEl = fixtureEl.querySelector('.modal') + const btnClose = fixtureEl.querySelector('[data-dismiss="modal"]') + const modal = new Modal(modalEl) + + spyOn(modal, 'hide').and.callThrough() + + modalEl.addEventListener('shown.bs.modal', () => { + btnClose.click() + }) + + modalEl.addEventListener('hidden.bs.modal', () => { + expect(modal.hide).toHaveBeenCalled() + done() + }) + + modal.show() + }) + + it('should set modal body scroll top to 0 if .modal-dialog-scrollable', done => { + fixtureEl.innerHTML = [ + '<div class="modal fade">', + ' <div class="modal-dialog modal-dialog-scrollable">', + ' <div class="modal-body"></div>', + ' </div>', + '</div>' + ].join('') + + const modalEl = fixtureEl.querySelector('.modal') + const modalBody = modalEl.querySelector('.modal-body') + const modal = new Modal(modalEl) + + spyOn(modal, 'hide').and.callThrough() + + modalEl.addEventListener('shown.bs.modal', () => { + expect(modalBody.scrollTop).toEqual(0) + done() + }) + + modal.show() + }) + + it('should not enforce focus if focus equal to false', done => { + fixtureEl.innerHTML = '<div class="modal fade"><div class="modal-dialog" /></div>' + + const modalEl = fixtureEl.querySelector('.modal') + const modal = new Modal(modalEl, { + focus: false + }) + + spyOn(modal, '_enforceFocus') + + modalEl.addEventListener('shown.bs.modal', () => { + expect(modal._enforceFocus).not.toHaveBeenCalled() + done() + }) + + modal.show() + }) + + it('should add listener when escape touch is pressed', done => { + fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" /></div>' + + const modalEl = fixtureEl.querySelector('.modal') + const modal = new Modal(modalEl) + + spyOn(modal, 'hide').and.callThrough() + + modalEl.addEventListener('shown.bs.modal', () => { + const keydownEscape = createEvent('keydown') + keydownEscape.which = 27 + + modalEl.dispatchEvent(keydownEscape) + }) + + modalEl.addEventListener('hidden.bs.modal', () => { + expect(modal.hide).toHaveBeenCalled() + done() + }) + + modal.show() + }) + + it('should do nothing when the pressed key is not escape', done => { + fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" /></div>' + + const modalEl = fixtureEl.querySelector('.modal') + const modal = new Modal(modalEl) + + spyOn(modal, 'hide') + + const expectDone = () => { + expect(modal.hide).not.toHaveBeenCalled() + + done() + } + + modalEl.addEventListener('shown.bs.modal', () => { + const keydownTab = createEvent('keydown') + keydownTab.which = 9 + + modalEl.dispatchEvent(keydownTab) + setTimeout(expectDone, 30) + }) + + modal.show() + }) + + it('should adjust dialog on resize', done => { + fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" /></div>' + + const modalEl = fixtureEl.querySelector('.modal') + const modal = new Modal(modalEl) + + spyOn(modal, '_adjustDialog').and.callThrough() + + const expectDone = () => { + expect(modal._adjustDialog).toHaveBeenCalled() + + done() + } + + modalEl.addEventListener('shown.bs.modal', () => { + const resizeEvent = createEvent('resize') + + window.dispatchEvent(resizeEvent) + setTimeout(expectDone, 10) + }) + + modal.show() + }) + + it('should not close modal when clicking outside of modal-content if backdrop = false', done => { + fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" /></div>' + + const modalEl = fixtureEl.querySelector('.modal') + const modal = new Modal(modalEl, { + backdrop: false + }) + + const shownCallback = () => { + setTimeout(() => { + expect(modal._isShown).toEqual(true) + done() + }, 10) + } + + modalEl.addEventListener('shown.bs.modal', () => { + modalEl.click() + shownCallback() + }) + + modalEl.addEventListener('hidden.bs.modal', () => { + throw new Error('Should not hide a modal') + }) + + modal.show() + }) + + it('should not adjust the inline body padding when it does not overflow', done => { + fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" /></div>' + + const modalEl = fixtureEl.querySelector('.modal') + const modal = new Modal(modalEl) + const originalPadding = window.getComputedStyle(document.body).paddingRight + + // Hide scrollbars to prevent the body overflowing + document.body.style.overflow = 'hidden' + document.documentElement.style.paddingRight = '0px' + + modalEl.addEventListener('shown.bs.modal', () => { + const currentPadding = window.getComputedStyle(document.body).paddingRight + + expect(currentPadding).toEqual(originalPadding, 'body padding should not be adjusted') + + // Restore scrollbars + document.body.style.overflow = 'auto' + document.documentElement.style.paddingRight = '16px' + done() + }) + + modal.show() + }) + + it('should enforce focus', done => { + fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" /></div>' + + const isIE11 = Boolean(window.MSInputMethodContext) && Boolean(document.documentMode) + const modalEl = fixtureEl.querySelector('.modal') + const modal = new Modal(modalEl) + + spyOn(modal, '_enforceFocus').and.callThrough() + + const focusInListener = () => { + expect(modal._element.focus).toHaveBeenCalled() + document.removeEventListener('focusin', focusInListener) + done() + } + + modalEl.addEventListener('shown.bs.modal', () => { + expect(modal._enforceFocus).toHaveBeenCalled() + + if (isIE11) { + done() + return + } + + spyOn(modal._element, 'focus') + + document.addEventListener('focusin', focusInListener) + + const focusInEvent = createEvent('focusin', { bubbles: true }) + Object.defineProperty(focusInEvent, 'target', { + value: fixtureEl + }) + + document.dispatchEvent(focusInEvent) + }) + + modal.show() + }) + }) + + describe('hide', () => { + it('should hide a modal', done => { + fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" /></div>' + + const modalEl = fixtureEl.querySelector('.modal') + const modal = new Modal(modalEl) + + modalEl.addEventListener('shown.bs.modal', () => { + modal.hide() + }) + + modalEl.addEventListener('hide.bs.modal', e => { + expect(e).toBeDefined() + }) + + modalEl.addEventListener('hidden.bs.modal', () => { + expect(modalEl.getAttribute('aria-modal')).toEqual(null) + expect(modalEl.getAttribute('aria-hidden')).toEqual('true') + expect(modalEl.style.display).toEqual('none') + expect(document.querySelector('.modal-backdrop')).toBeNull() + done() + }) + + modal.show() + }) + + it('should close modal when clicking outside of modal-content', done => { + fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" /></div>' + + const modalEl = fixtureEl.querySelector('.modal') + const modal = new Modal(modalEl) + + modalEl.addEventListener('shown.bs.modal', () => { + modalEl.click() + }) + + modalEl.addEventListener('hidden.bs.modal', () => { + expect(modalEl.getAttribute('aria-modal')).toEqual(null) + expect(modalEl.getAttribute('aria-hidden')).toEqual('true') + expect(modalEl.style.display).toEqual('none') + expect(document.querySelector('.modal-backdrop')).toBeNull() + done() + }) + + modal.show() + }) + + it('should do nothing is the modal is not shown', () => { + fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" /></div>' + + const modalEl = fixtureEl.querySelector('.modal') + const modal = new Modal(modalEl) + + modal.hide() + + expect().nothing() + }) + + it('should do nothing is the modal is transitioning', () => { + fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" /></div>' + + const modalEl = fixtureEl.querySelector('.modal') + const modal = new Modal(modalEl) + + modal._isTransitioning = true + modal.hide() + + expect().nothing() + }) + + it('should not hide a modal if hide is prevented', done => { + fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" /></div>' + + const modalEl = fixtureEl.querySelector('.modal') + const modal = new Modal(modalEl) + + modalEl.addEventListener('shown.bs.modal', () => { + modal.hide() + }) + + const hideCallback = () => { + setTimeout(() => { + expect(modal._isShown).toEqual(true) + done() + }, 10) + } + + modalEl.addEventListener('hide.bs.modal', e => { + e.preventDefault() + hideCallback() + }) + + modalEl.addEventListener('hidden.bs.modal', () => { + throw new Error('should not trigger hidden') + }) + + modal.show() + }) + }) + + describe('dispose', () => { + it('should dispose a modal', () => { + fixtureEl.innerHTML = '<div id="exampleModal" class="modal"><div class="modal-dialog" /></div>' + + const modalEl = fixtureEl.querySelector('.modal') + const modal = new Modal(modalEl) + + expect(Modal._getInstance(modalEl)).toEqual(modal) + + spyOn(EventHandler, 'off') + + modal.dispose() + + expect(Modal._getInstance(modalEl)).toEqual(null) + expect(EventHandler.off).toHaveBeenCalledTimes(4) + }) + }) + + describe('handleUpdate', () => { + it('should call adjust dialog', () => { + fixtureEl.innerHTML = '<div id="exampleModal" class="modal"><div class="modal-dialog" /></div>' + + const modalEl = fixtureEl.querySelector('.modal') + const modal = new Modal(modalEl) + + spyOn(modal, '_adjustDialog') + + modal.handleUpdate() + + expect(modal._adjustDialog).toHaveBeenCalled() + }) + }) + + describe('data-api', () => { + it('should open modal', done => { + fixtureEl.innerHTML = [ + '<button type="button" data-toggle="modal" data-target="#exampleModal"></button>', + '<div id="exampleModal" class="modal"><div class="modal-dialog" /></div>' + ].join('') + + const modalEl = fixtureEl.querySelector('.modal') + const trigger = fixtureEl.querySelector('[data-toggle="modal"]') + + modalEl.addEventListener('shown.bs.modal', () => { + expect(modalEl.getAttribute('aria-modal')).toEqual('true') + expect(modalEl.getAttribute('aria-hidden')).toEqual(null) + expect(modalEl.style.display).toEqual('block') + expect(document.querySelector('.modal-backdrop')).toBeDefined() + done() + }) + + trigger.click() + }) + + it('should not recreate a new modal', done => { + fixtureEl.innerHTML = [ + '<button type="button" data-toggle="modal" data-target="#exampleModal"></button>', + '<div id="exampleModal" class="modal"><div class="modal-dialog" /></div>' + ].join('') + + const modalEl = fixtureEl.querySelector('.modal') + const modal = new Modal(modalEl) + const trigger = fixtureEl.querySelector('[data-toggle="modal"]') + + spyOn(modal, 'show').and.callThrough() + + modalEl.addEventListener('shown.bs.modal', () => { + expect(modal.show).toHaveBeenCalled() + done() + }) + + trigger.click() + }) + + it('should prevent default when the trigger is <a> or <area>', done => { + fixtureEl.innerHTML = [ + '<a data-toggle="modal" href="#" data-target="#exampleModal"></a>', + '<div id="exampleModal" class="modal"><div class="modal-dialog" /></div>' + ].join('') + + const modalEl = fixtureEl.querySelector('.modal') + const trigger = fixtureEl.querySelector('[data-toggle="modal"]') + + spyOn(Event.prototype, 'preventDefault').and.callThrough() + + modalEl.addEventListener('shown.bs.modal', () => { + expect(modalEl.getAttribute('aria-modal')).toEqual('true') + expect(modalEl.getAttribute('aria-hidden')).toEqual(null) + expect(modalEl.style.display).toEqual('block') + expect(document.querySelector('.modal-backdrop')).toBeDefined() + expect(Event.prototype.preventDefault).toHaveBeenCalled() + done() + }) + + trigger.click() + }) + + it('should focus the trigger on hide', done => { + fixtureEl.innerHTML = [ + '<a data-toggle="modal" href="#" data-target="#exampleModal"></a>', + '<div id="exampleModal" class="modal"><div class="modal-dialog" /></div>' + ].join('') + + // the element must be displayed, without that activeElement won't change + fixtureEl.style.display = 'block' + + const modalEl = fixtureEl.querySelector('.modal') + const trigger = fixtureEl.querySelector('[data-toggle="modal"]') + + spyOn(trigger, 'focus') + + modalEl.addEventListener('shown.bs.modal', () => { + const modal = Modal._getInstance(modalEl) + + modal.hide() + }) + + const hideListener = () => { + setTimeout(() => { + expect(trigger.focus).toHaveBeenCalled() + fixtureEl.style.display = 'none' + done() + }, 20) + } + + modalEl.addEventListener('hidden.bs.modal', () => { + hideListener() + }) + + trigger.click() + }) + + it('should not focus the trigger if the modal is not visible', done => { + fixtureEl.innerHTML = [ + '<a data-toggle="modal" href="#" data-target="#exampleModal"></a>', + '<div id="exampleModal" class="modal"><div class="modal-dialog" /></div>' + ].join('') + + const modalEl = fixtureEl.querySelector('.modal') + const trigger = fixtureEl.querySelector('[data-toggle="modal"]') + + spyOn(trigger, 'focus') + + modalEl.addEventListener('shown.bs.modal', () => { + const modal = Modal._getInstance(modalEl) + + modal.hide() + }) + + const hideListener = () => { + setTimeout(() => { + expect(trigger.focus).not.toHaveBeenCalled() + done() + }, 20) + } + + modalEl.addEventListener('hidden.bs.modal', () => { + hideListener() + }) + + trigger.click() + }) + + it('should not focus the trigger if the modal is not shown', done => { + fixtureEl.innerHTML = [ + '<a data-toggle="modal" href="#" data-target="#exampleModal"></a>', + '<div id="exampleModal" class="modal"><div class="modal-dialog" /></div>' + ].join('') + + const modalEl = fixtureEl.querySelector('.modal') + const trigger = fixtureEl.querySelector('[data-toggle="modal"]') + + spyOn(trigger, 'focus') + + const showListener = () => { + setTimeout(() => { + expect(trigger.focus).not.toHaveBeenCalled() + done() + }, 10) + } + + modalEl.addEventListener('show.bs.modal', e => { + e.preventDefault() + showListener() + }) + + trigger.click() + }) + }) + + describe('_jQueryInterface', () => { + it('should create a modal', () => { + fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" /></div>' + + const div = fixtureEl.querySelector('div') + + jQueryMock.fn.modal = Modal._jQueryInterface + jQueryMock.elements = [div] + + jQueryMock.fn.modal.call(jQueryMock) + + expect(Modal._getInstance(div)).toBeDefined() + }) + + it('should not re create a modal', () => { + fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" /></div>' + + const div = fixtureEl.querySelector('div') + const modal = new Modal(div) + + jQueryMock.fn.modal = Modal._jQueryInterface + jQueryMock.elements = [div] + + jQueryMock.fn.modal.call(jQueryMock) + + expect(Modal._getInstance(div)).toEqual(modal) + }) + + it('should throw error on undefined method', () => { + fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" /></div>' + + const div = fixtureEl.querySelector('div') + const action = 'undefinedMethod' + + jQueryMock.fn.modal = Modal._jQueryInterface + jQueryMock.elements = [div] + + try { + jQueryMock.fn.modal.call(jQueryMock, action) + } catch (error) { + expect(error.message).toEqual(`No method named "${action}"`) + } + }) + + it('should should call show method', () => { + fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" /></div>' + + const div = fixtureEl.querySelector('div') + const modal = new Modal(div) + + jQueryMock.fn.modal = Modal._jQueryInterface + jQueryMock.elements = [div] + + spyOn(modal, 'show') + + jQueryMock.fn.modal.call(jQueryMock, 'show') + + expect(modal.show).toHaveBeenCalled() + }) + + it('should should not call show method', () => { + fixtureEl.innerHTML = '<div class="modal" data-show="false"><div class="modal-dialog" /></div>' + + const div = fixtureEl.querySelector('div') + + jQueryMock.fn.modal = Modal._jQueryInterface + jQueryMock.elements = [div] + + spyOn(Modal.prototype, 'show') + + jQueryMock.fn.modal.call(jQueryMock) + + expect(Modal.prototype.show).not.toHaveBeenCalled() + }) + }) + + describe('_getInstance', () => { + it('should return modal instance', () => { + fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" /></div>' + + const div = fixtureEl.querySelector('div') + const modal = new Modal(div) + + expect(Modal._getInstance(div)).toEqual(modal) + }) + + it('should return null when there is no modal instance', () => { + fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog" /></div>' + + const div = fixtureEl.querySelector('div') + + expect(Modal._getInstance(div)).toEqual(null) + }) + }) +}) diff --git a/js/tests/unit/modal.js b/js/tests/unit/modal.js deleted file mode 100644 index 0137802ed0..0000000000 --- a/js/tests/unit/modal.js +++ /dev/null @@ -1,816 +0,0 @@ -$(function () { - 'use strict' - - window.Util = typeof bootstrap === 'undefined' ? Util : bootstrap.Util - var Modal = typeof window.bootstrap === 'undefined' ? window.Modal : window.bootstrap.Modal - - QUnit.module('modal plugin') - - QUnit.test('should be defined on jquery object', function (assert) { - assert.expect(1) - assert.ok($(document.body).modal, 'modal method is defined') - }) - - QUnit.module('modal', { - before: function () { - // Enable the scrollbar measurer - $('<style type="text/css"> .modal-scrollbar-measure { position: absolute; top: -9999px; width: 50px; height: 50px; overflow: scroll; } </style>').appendTo('head') - // Function to calculate the scrollbar width which is then compared to the padding or margin changes - $.fn.getScrollbarWidth = $.fn.modal.Constructor.prototype._getScrollbarWidth - - // Simulate scrollbars - $('html').css('padding-right', '16px') - }, - beforeEach: function () { - // Run all tests in noConflict mode -- it's the only way to ensure that the plugin works in noConflict mode - $.fn.bootstrapModal = $.fn.modal.noConflict() - }, - afterEach: function () { - $('.modal-backdrop, #modal-test').remove() - $(document.body).removeClass('modal-open') - $.fn.modal = $.fn.bootstrapModal - delete $.fn.bootstrapModal - $('#qunit-fixture').html('') - } - }) - - QUnit.test('should provide no conflict', function (assert) { - assert.expect(1) - assert.strictEqual(typeof $.fn.modal, 'undefined', 'modal was set back to undefined (orig value)') - }) - - QUnit.test('should throw explicit error on undefined method', function (assert) { - assert.expect(1) - var $el = $('<div id="modal-test"><div class="modal-dialog" /></div>') - $el.bootstrapModal() - try { - $el.bootstrapModal('noMethod') - } catch (error) { - assert.strictEqual(error.message, 'No method named "noMethod"') - } - }) - - QUnit.test('should return jquery collection containing the element', function (assert) { - assert.expect(2) - var $el = $('<div id="modal-test"><div class="modal-dialog" /></div>') - var $modal = $el.bootstrapModal() - assert.ok($modal instanceof $, 'returns jquery collection') - assert.strictEqual($modal[0], $el[0], 'collection contains element') - }) - - QUnit.test('should expose defaults var for settings', function (assert) { - assert.expect(1) - assert.ok($.fn.bootstrapModal.Constructor.Default, 'default object exposed') - }) - - QUnit.test('should insert into dom when show method is called', function (assert) { - assert.expect(1) - var done = assert.async() - - $('<div id="modal-test"><div class="modal-dialog" /></div>') - .on('shown.bs.modal', function () { - assert.notEqual($('#modal-test').length, 0, 'modal inserted into dom') - done() - }) - .bootstrapModal('show') - }) - - QUnit.test('should fire show event', function (assert) { - assert.expect(1) - var done = assert.async() - - $('<div id="modal-test"><div class="modal-dialog" /></div>') - .on('show.bs.modal', function () { - assert.ok(true, 'show event fired') - done() - }) - .bootstrapModal('show') - }) - - QUnit.test('should not fire shown when show was prevented', function (assert) { - assert.expect(1) - var done = assert.async() - - $('<div id="modal-test"><div class="modal-dialog" /></div>') - .on('show.bs.modal', function (e) { - e.preventDefault() - assert.ok(true, 'show event fired') - done() - }) - .on('shown.bs.modal', function () { - assert.ok(false, 'shown event fired') - }) - .bootstrapModal('show') - }) - - QUnit.test('should hide modal when hide is called', function (assert) { - assert.expect(3) - var done = assert.async() - - $('<div id="modal-test"><div class="modal-dialog" /></div>') - .on('shown.bs.modal', function () { - assert.ok($('#modal-test').is(':visible'), 'modal visible') - assert.notEqual($('#modal-test').length, 0, 'modal inserted into dom') - $(this).bootstrapModal('hide') - }) - .on('hidden.bs.modal', function () { - assert.ok(!$('#modal-test').is(':visible'), 'modal hidden') - done() - }) - .bootstrapModal('show') - }) - - QUnit.test('should toggle when toggle is called', function (assert) { - assert.expect(3) - var done = assert.async() - - $('<div id="modal-test"><div class="modal-dialog" /></div>') - .on('shown.bs.modal', function () { - assert.ok($('#modal-test').is(':visible'), 'modal visible') - assert.notEqual($('#modal-test').length, 0, 'modal inserted into dom') - $(this).bootstrapModal('toggle') - }) - .on('hidden.bs.modal', function () { - assert.ok(!$('#modal-test').is(':visible'), 'modal hidden') - done() - }) - .bootstrapModal('toggle') - }) - - QUnit.test('should remove from dom when click [data-dismiss="modal"]', function (assert) { - assert.expect(3) - var done = assert.async() - $('<div id="modal-test"><div class="modal-dialog" /><span class="close" data-dismiss="modal"/></div>') - .on('shown.bs.modal', function () { - assert.ok($('#modal-test').is(':visible'), 'modal visible') - assert.notEqual($('#modal-test').length, 0, 'modal inserted into dom') - $(this).find('.close').trigger('click') - }) - .on('hidden.bs.modal', function () { - assert.ok(!$('#modal-test').is(':visible'), 'modal hidden') - done() - }) - .bootstrapModal('toggle') - }) - - QUnit.test('should allow modal close with "backdrop:false"', function (assert) { - assert.expect(2) - var done = assert.async() - - $('<div id="modal-test" data-backdrop="false"><div class="modal-dialog" /></div>') - .on('shown.bs.modal', function () { - assert.ok($('#modal-test').is(':visible'), 'modal visible') - $(this).bootstrapModal('hide') - }) - .on('hidden.bs.modal', function () { - assert.ok(!$('#modal-test').is(':visible'), 'modal hidden') - done() - }) - .bootstrapModal('show') - }) - - QUnit.test('should close modal when clicking outside of modal-content', function (assert) { - assert.expect(3) - var done = assert.async() - - $('<div id="modal-test"><div class="modal-dialog" /><div class="contents"/></div>') - .on('shown.bs.modal', function () { - assert.notEqual($('#modal-test').length, 0, 'modal inserted into dom') - $('.contents').trigger('click') - assert.ok($('#modal-test').is(':visible'), 'modal visible') - $('#modal-test').trigger('click') - }) - .on('hidden.bs.modal', function () { - assert.ok(!$('#modal-test').is(':visible'), 'modal hidden') - done() - }) - .bootstrapModal('show') - }) - - QUnit.test('should not close modal when clicking outside of modal-content if data-backdrop="true"', function (assert) { - assert.expect(1) - var done = assert.async() - - $('<div id="modal-test" data-backdrop="false"><div class="modal-dialog" /><div class="contents"/></div>') - .on('shown.bs.modal', function () { - $('#modal-test').trigger('click') - assert.ok($('#modal-test').is(':visible'), 'modal not hidden') - done() - }) - .bootstrapModal('show') - }) - - QUnit.test('should close modal when escape key is pressed via keydown', function (assert) { - assert.expect(3) - var done = assert.async() - - var $div = $('<div id="modal-test"><div class="modal-dialog" /></div>') - $div - .on('shown.bs.modal', function () { - assert.ok($('#modal-test').length, 'modal inserted into dom') - assert.ok($('#modal-test').is(':visible'), 'modal visible') - - var evt = document.createEvent('HTMLEvents') - evt.initEvent('keydown', true, true) - evt.which = 27 - - $div[0].dispatchEvent(evt) - - setTimeout(function () { - assert.ok(!$('#modal-test').is(':visible'), 'modal hidden') - $div.remove() - done() - }, 0) - }) - .bootstrapModal('show') - }) - - QUnit.test('should not close modal when escape key is pressed via keyup', function (assert) { - assert.expect(3) - var done = assert.async() - - var $div = $('<div id="modal-test"><div class="modal-dialog" /></div>') - $div - .on('shown.bs.modal', function () { - assert.ok($('#modal-test').length, 'modal inserted into dom') - assert.ok($('#modal-test').is(':visible'), 'modal visible') - $div.trigger($.Event('keyup', { - which: 27 - })) - - setTimeout(function () { - assert.ok($div.is(':visible'), 'modal still visible') - $div.remove() - done() - }, 0) - }) - .bootstrapModal('show') - }) - - QUnit.test('should trigger hide event once when clicking outside of modal-content', function (assert) { - assert.expect(1) - var done = assert.async() - - var triggered - - $('<div id="modal-test"><div class="modal-dialog" /><div class="contents"/></div>') - .on('shown.bs.modal', function () { - triggered = 0 - $('#modal-test').trigger('click') - }) - .on('hide.bs.modal', function () { - triggered += 1 - assert.strictEqual(triggered, 1, 'modal hide triggered once') - done() - }) - .bootstrapModal('show') - }) - - QUnit.test('should remove aria-hidden attribute when shown, add it back when hidden', function (assert) { - assert.expect(3) - var done = assert.async() - - $('<div id="modal-test" aria-hidden="true"><div class="modal-dialog" /></div>') - .on('shown.bs.modal', function () { - assert.notOk($('#modal-test').is('[aria-hidden]'), 'aria-hidden attribute removed') - $(this).bootstrapModal('hide') - }) - .on('hidden.bs.modal', function () { - assert.ok($('#modal-test').is('[aria-hidden]'), 'aria-hidden attribute added') - assert.strictEqual($('#modal-test').attr('aria-hidden'), 'true', 'correct aria-hidden="true" added') - done() - }) - .bootstrapModal('show') - }) - - QUnit.test('should add aria-modal attribute when shown, remove it again when hidden', function (assert) { - assert.expect(3) - var done = assert.async() - - $('<div id="modal-test"><div class="modal-dialog" /></div>') - .on('shown.bs.modal', function () { - assert.ok($('#modal-test').is('[aria-modal]'), 'aria-modal attribute added') - assert.strictEqual($('#modal-test').attr('aria-modal'), 'true', 'correct aria-modal="true" added') - $(this).bootstrapModal('hide') - }) - .on('hidden.bs.modal', function () { - assert.notOk($('#modal-test').is('[aria-modal]'), 'aria-modal attribute removed') - done() - }) - .bootstrapModal('show') - }) - - QUnit.test('should close reopened modal with [data-dismiss="modal"] click', function (assert) { - assert.expect(2) - var done = assert.async() - var html = [ - '<div id="modal-test">', - ' <div class="modal-dialog">', - ' <div class="contents"><div id="close" data-dismiss="modal"/></div>', - ' </div>', - '</div>' - ].join('') - - $(html) - .one('shown.bs.modal', function () { - $('#close').trigger('click') - }) - .one('hidden.bs.modal', function () { - // After one open-close cycle - assert.ok(!$('#modal-test').is(':visible'), 'modal hidden') - - var $this = $(this) - setTimeout(function () { - $this - .one('shown.bs.modal', function () { - $('#close').trigger('click') - }) - .one('hidden.bs.modal', function () { - assert.ok(!$('#modal-test').is(':visible'), 'modal hidden') - done() - }) - .bootstrapModal('show') - }, 0) - }) - .bootstrapModal('show') - }) - - QUnit.test('should restore focus to toggling element when modal is hidden after having been opened via data-api', function (assert) { - assert.expect(1) - var done = assert.async() - var $toggleBtn = $('<button data-toggle="modal" data-target="#modal-test"/>').appendTo('#qunit-fixture') - var html = [ - '<div id="modal-test">', - ' <div class="modal-dialog">', - ' <div class="contents"><div id="close" data-dismiss="modal"/></div>', - ' </div>', - '</div>' - ].join('') - - $(html) - .on('hidden.bs.modal', function () { - setTimeout(function () { - assert.ok($(document.activeElement).is($toggleBtn), 'toggling element is once again focused') - done() - }, 0) - }) - .on('shown.bs.modal', function () { - $('#close').trigger('click') - }) - .appendTo('#qunit-fixture') - - $toggleBtn.trigger('click') - }) - - QUnit.test('should not restore focus to toggling element if the associated show event gets prevented', function (assert) { - assert.expect(1) - var done = assert.async() - var $toggleBtn = $('<button data-toggle="modal" data-target="#modal-test"/>').appendTo('#qunit-fixture') - var $otherBtn = $('<button id="other-btn"/>').appendTo('#qunit-fixture') - var html = [ - '<div id="modal-test">', - ' <div class="modal-dialog">', - ' <div class="contents"><div id="close" data-dismiss="modal"/></div>', - ' </div>', - '</div>' - ].join('') - - $(html) - .one('show.bs.modal', function (e) { - e.preventDefault() - $otherBtn.trigger('focus') - setTimeout($.proxy(function () { - $(this).bootstrapModal('show') - }, this), 0) - }) - .on('hidden.bs.modal', function () { - setTimeout(function () { - assert.ok($(document.activeElement).is($otherBtn), 'focus returned to toggling element') - done() - }, 0) - }) - .on('shown.bs.modal', function () { - $('#close').trigger('click') - }) - .appendTo('#qunit-fixture') - - $toggleBtn.trigger('click') - }) - - QUnit.test('should adjust the inline padding of the modal when opening', function (assert) { - assert.expect(1) - var done = assert.async() - - $('<div id="modal-test"><div class="modal-dialog" /></div>') - .on('shown.bs.modal', function () { - var expectedPadding = parseInt($(this).getScrollbarWidth(), 10) - var currentPadding = parseInt($(this).css('padding-right'), 10) - assert.strictEqual(currentPadding, expectedPadding, 'modal padding should be adjusted while opening') - done() - }) - .bootstrapModal('show') - }) - - QUnit.test('should adjust the inline body padding when opening and restore when closing', function (assert) { - assert.expect(2) - var done = assert.async() - var $body = $(document.body) - var originalPadding = parseInt($body.css('padding-right'), 10) - - $('<div id="modal-test"><div class="modal-dialog" /></div>') - .on('hidden.bs.modal', function () { - var currentPadding = parseInt($body.css('padding-right'), 10) - assert.strictEqual(currentPadding, originalPadding, 'body padding should be reset after closing') - $body.removeAttr('style') - done() - }) - .on('shown.bs.modal', function () { - var expectedPadding = parseInt(originalPadding, 10) + parseInt($(this).getScrollbarWidth(), 10) - var currentPadding = parseInt($body.css('padding-right'), 10) - assert.strictEqual(currentPadding, expectedPadding, 'body padding should be adjusted while opening') - $(this).bootstrapModal('hide') - }) - .bootstrapModal('show') - }) - - QUnit.test('should store the original body padding in data-padding-right before showing', function (assert) { - assert.expect(2) - var done = assert.async() - var $body = $(document.body) - var originalPadding = '0px' - $body.css('padding-right', originalPadding) - - $('<div id="modal-test"><div class="modal-dialog" /></div>') - .on('hidden.bs.modal', function () { - assert.strictEqual(document.body.getAttribute('data-padding-right'), null, 'data-padding-right should be cleared after closing') - $body.removeAttr('style') - done() - }) - .on('shown.bs.modal', function () { - assert.strictEqual($body.data('padding-right'), originalPadding, 'original body padding should be stored in data-padding-right') - $(this).bootstrapModal('hide') - }) - .bootstrapModal('show') - }) - - QUnit.test('should not adjust the inline body padding when it does not overflow', function (assert) { - assert.expect(1) - var done = assert.async() - var $body = $(document.body) - var originalPadding = $body.css('padding-right') - - // Hide scrollbars to prevent the body overflowing - $body.css('overflow', 'hidden') // Real scrollbar (for in-browser testing) - $('html').css('padding-right', '0px') // Simulated scrollbar (for PhantomJS) - - $('<div id="modal-test"><div class="modal-dialog" /></div>') - .on('shown.bs.modal', function () { - var currentPadding = $body.css('padding-right') - assert.strictEqual(currentPadding, originalPadding, 'body padding should not be adjusted') - $(this).bootstrapModal('hide') - - // Restore scrollbars - $body.css('overflow', 'auto') - $('html').css('padding-right', '16px') - done() - }) - .bootstrapModal('show') - }) - - QUnit.test('should adjust the inline padding of fixed elements when opening and restore when closing', function (assert) { - assert.expect(2) - var done = assert.async() - var $element = $('<div class="fixed-top"></div>').appendTo('#qunit-fixture') - var originalPadding = parseInt($element.css('padding-right'), 10) - - $('<div id="modal-test"><div class="modal-dialog" /></div>') - .on('hidden.bs.modal', function () { - var currentPadding = parseInt($element.css('padding-right'), 10) - assert.strictEqual(currentPadding, originalPadding, 'fixed element padding should be reset after closing') - $element.remove() - done() - }) - .on('shown.bs.modal', function () { - var expectedPadding = parseFloat(originalPadding) + parseInt($(this).getScrollbarWidth(), 10) - var currentPadding = parseInt($element.css('padding-right'), 10) - assert.strictEqual(currentPadding, expectedPadding, 'fixed element padding should be adjusted while opening') - $(this).bootstrapModal('hide') - }) - .bootstrapModal('show') - }) - - QUnit.test('should store the original padding of fixed elements in data-padding-right before showing', function (assert) { - assert.expect(2) - var done = assert.async() - var $element = $('<div class="fixed-top"></div>').appendTo('#qunit-fixture') - var originalPadding = '0px' - $element.css('padding-right', originalPadding) - - $('<div id="modal-test"><div class="modal-dialog" /></div>') - .on('hidden.bs.modal', function () { - assert.strictEqual($element[0].getAttribute('data-padding-right'), null, 'data-padding-right should be cleared after closing') - $element.remove() - done() - }) - .on('shown.bs.modal', function () { - assert.strictEqual($element.data('padding-right'), originalPadding, 'original fixed element padding should be stored in data-padding-right') - $(this).bootstrapModal('hide') - }) - .bootstrapModal('show') - }) - - QUnit.test('should adjust the inline margin of sticky elements when opening and restore when closing', function (assert) { - assert.expect(2) - var done = assert.async() - var $element = $('<div class="sticky-top"></div>').appendTo('#qunit-fixture') - var originalPadding = parseInt($element.css('margin-right'), 10) - - $('<div id="modal-test"><div class="modal-dialog" /></div>') - .on('hidden.bs.modal', function () { - var currentPadding = parseInt($element.css('margin-right'), 10) - assert.strictEqual(currentPadding, originalPadding, 'sticky element margin should be reset after closing') - $element.remove() - done() - }) - .on('shown.bs.modal', function () { - var expectedPadding = parseFloat(originalPadding) - $(this).getScrollbarWidth() - var currentPadding = parseInt($element.css('margin-right'), 10) - assert.strictEqual(currentPadding, expectedPadding, 'sticky element margin should be adjusted while opening') - $(this).bootstrapModal('hide') - }) - .bootstrapModal('show') - }) - - QUnit.test('should store the original margin of sticky elements in data-margin-right before showing', function (assert) { - assert.expect(2) - var done = assert.async() - var $element = $('<div class="sticky-top"></div>').appendTo('#qunit-fixture') - var originalPadding = '0px' - $element.css('margin-right', originalPadding) - - $('<div id="modal-test"><div class="modal-dialog" /></div>') - .on('hidden.bs.modal', function () { - assert.strictEqual($element[0].getAttribute('data-margin-right'), null, 'data-margin-right should be cleared after closing') - $element.remove() - done() - }) - .on('shown.bs.modal', function () { - assert.strictEqual($element.data('margin-right'), originalPadding, 'original sticky element margin should be stored in data-margin-right') - $(this).bootstrapModal('hide') - }) - .bootstrapModal('show') - }) - - QUnit.test('should ignore values set via CSS when trying to restore body padding after closing', function (assert) { - assert.expect(1) - var done = assert.async() - var $body = $(document.body) - var $style = $('<style>body { padding-right: 42px; }</style>').appendTo('head') - - $('<div id="modal-test"><div class="modal-dialog" /></div>') - .on('hidden.bs.modal', function () { - assert.strictEqual($body.css('padding-left'), '0px', 'body does not have inline padding set') - $style.remove() - done() - }) - .on('shown.bs.modal', function () { - $(this).bootstrapModal('hide') - }) - .bootstrapModal('show') - }) - - QUnit.test('should ignore other inline styles when trying to restore body padding after closing', function (assert) { - assert.expect(2) - var done = assert.async() - var $body = $(document.body) - var $style = $('<style>body { padding-right: 42px; }</style>').appendTo('head') - - $body.css('color', 'red') - - $('<div id="modal-test"><div class="modal-dialog" /></div>') - .on('hidden.bs.modal', function () { - assert.strictEqual($body[0].style.paddingRight, '', 'body does not have inline padding set') - assert.strictEqual($body[0].style.color, 'red', 'body still has other inline styles set') - $body.removeAttr('style') - $style.remove() - done() - }) - .on('shown.bs.modal', function () { - $(this).bootstrapModal('hide') - }) - .bootstrapModal('show') - }) - - QUnit.test('should properly restore non-pixel inline body padding after closing', function (assert) { - assert.expect(1) - var done = assert.async() - var $body = $(document.body) - - $body.css('padding-right', '5%') - - $('<div id="modal-test"><div class="modal-dialog" /></div>') - .on('hidden.bs.modal', function () { - assert.strictEqual($body[0].style.paddingRight, '5%', 'body does not have inline padding set') - $body.removeAttr('style') - done() - }) - .on('shown.bs.modal', function () { - $(this).bootstrapModal('hide') - }) - .bootstrapModal('show') - }) - - QUnit.test('should not follow link in area tag', function (assert) { - assert.expect(2) - - $('<map><area id="test" shape="default" data-toggle="modal" data-target="#modal-test" href="demo.html"/></map>') - .appendTo('#qunit-fixture') - - var modalHtml = [ - '<div id="modal-test">', - ' <div class="modal-dialog">', - ' <div class="contents"><div id="close" data-dismiss="modal"/></div>', - ' </div>', - '</div>' - ].join('') - - $(modalHtml) - .appendTo('#qunit-fixture') - - // We need to use CustomEvent here to have a working preventDefault in IE tests. - var evt = new CustomEvent('click', { - bubbles: true, - cancelable: true - }) - - $('#test') - .on('click.bs.modal.data-api', function (event) { - assert.notOk(event.defaultPrevented, 'navigating to href will happen') - }) - - $('#test')[0].dispatchEvent(evt) - assert.ok(evt.defaultPrevented, 'model shown instead of navigating to href') - }) - - QUnit.test('should not try to open a modal which is already visible', function (assert) { - assert.expect(1) - var done = assert.async() - var count = 0 - - $('<div id="modal-test"><div class="modal-dialog" /></div>').on('shown.bs.modal', function () { - count++ - }).on('hidden.bs.modal', function () { - assert.strictEqual(count, 1, 'show() runs only once') - done() - }) - .bootstrapModal('show') - .bootstrapModal('show') - .bootstrapModal('hide') - }) - - QUnit.test('transition duration should be the modal-dialog duration before triggering shown event', function (assert) { - assert.expect(1) - var done = assert.async() - var style = [ - '<style>', - ' .modal.fade .modal-dialog {', - ' transition: -webkit-transform .3s ease-out;', - ' transition: transform .3s ease-out;', - ' transition: transform .3s ease-out,-webkit-transform .3s ease-out;', - ' -webkit-transform: translate(0,-50px);', - ' transform: translate(0,-50px);', - ' }', - '</style>' - ].join('') - - var $style = $(style).appendTo('head') - var modalHTML = [ - '<div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">', - ' <div class="modal-dialog" role="document">', - ' <div class="modal-content">', - ' <div class="modal-body">...</div>', - ' </div>', - ' </div>', - '</div>' - ].join('') - - var $modal = $(modalHTML).appendTo('#qunit-fixture') - - $modal.on('shown.bs.modal', function () { - $style.remove() - assert.ok(true) - done() - }) - .bootstrapModal('show') - }) - - QUnit.test('should dispose modal', function (assert) { - assert.expect(2) - var done = assert.async() - - var $modal = $([ - '<div id="modal-test">', - ' <div class="modal-dialog">', - ' <div class="modal-content">', - ' <div class="modal-body" />', - ' </div>', - ' </div>', - '</div>' - ].join('')).appendTo('#qunit-fixture') - - $modal.on('shown.bs.modal', function () { - var modal = Modal._getInstance($modal[0]) - var spy = sinon.spy($modal[0], 'removeEventListener') - - modal.dispose() - - assert.ok(!Modal._getInstance($modal[0]), 'modal data object was disposed') - assert.ok(spy.called) - done() - }).bootstrapModal('show') - }) - - QUnit.test('should enforce focus', function (assert) { - var isIE11 = Boolean(window.MSInputMethodContext) && Boolean(document.documentMode) - - if (isIE11) { - assert.expect(1) - } else { - assert.expect(2) - } - - var done = assert.async() - - var $modal = $([ - '<div id="modal-test" data-show="false">', - ' <div class="modal-dialog">', - ' <div class="modal-content">', - ' <div class="modal-body" />', - ' </div>', - ' </div>', - '</div>' - ].join('')) - .bootstrapModal() - .appendTo('#qunit-fixture') - - var modal = Modal._getInstance($modal[0]) - var spy = sinon.spy(modal, '_enforceFocus') - - $modal.one('shown.bs.modal', function () { - assert.ok(spy.called, '_enforceFocus called') - var spyFocus = sinon.spy(modal._element, 'focus') - - function focusInListener() { - assert.ok(spyFocus.called) - document.removeEventListener('focusin', focusInListener) - done() - } - - if (isIE11) { - done() - } else { - document.addEventListener('focusin', focusInListener) - - var focusInEvent = new Event('focusin') - Object.defineProperty(focusInEvent, 'target', { - value: $('#qunit-fixture')[0] - }) - - document.dispatchEvent(focusInEvent) - } - }) - .bootstrapModal('show') - }) - - QUnit.test('should scroll to top of the modal body if the modal has .modal-dialog-scrollable class', function (assert) { - assert.expect(2) - var done = assert.async() - - var $modal = $([ - '<div id="modal-test">', - ' <div class="modal-dialog modal-dialog-scrollable">', - ' <div class="modal-content">', - ' <div class="modal-body" style="height: 100px; overflow-y: auto;">', - ' <div style="height: 200px" />', - ' </div>', - ' </div>', - ' </div>', - '</div>' - ].join('')).appendTo('#qunit-fixture') - - var $modalBody = $('.modal-body') - $modalBody.scrollTop(100) - assert.ok($modalBody.scrollTop() > 95 && $modalBody.scrollTop() <= 100) - - $modal.on('shown.bs.modal', function () { - assert.strictEqual($modalBody.scrollTop(), 0, 'modal body scrollTop should be 0 when opened') - done() - }) - .bootstrapModal('show') - }) - - QUnit.test('should return the version', function (assert) { - assert.expect(1) - assert.strictEqual(typeof Modal.VERSION, 'string') - }) -}) |