diff options
| author | Johann-S <[email protected]> | 2019-10-02 11:43:54 +0200 |
|---|---|---|
| committer | Johann-S <[email protected]> | 2019-10-03 09:55:57 +0200 |
| commit | 3d12b541c488ea09efced2fb987fcbf384c656bb (patch) | |
| tree | 1863095dd8162e25a1909cf741e32faa091c32d4 /js/src/modal | |
| parent | 393ddae09b0578c8d381540bdbb4e68cdec1b45b (diff) | |
| download | bootstrap-3d12b541c488ea09efced2fb987fcbf384c656bb.tar.xz bootstrap-3d12b541c488ea09efced2fb987fcbf384c656bb.zip | |
return to the original file structure to avoid breaking modularity
Diffstat (limited to 'js/src/modal')
| -rw-r--r-- | js/src/modal/modal.js | 604 | ||||
| -rw-r--r-- | js/src/modal/modal.spec.js | 987 |
2 files changed, 0 insertions, 1591 deletions
diff --git a/js/src/modal/modal.js b/js/src/modal/modal.js deleted file mode 100644 index 4864cad9d..000000000 --- a/js/src/modal/modal.js +++ /dev/null @@ -1,604 +0,0 @@ -/** - * -------------------------------------------------------------------------- - * Bootstrap (v4.3.1): modal.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * -------------------------------------------------------------------------- - */ - -import { - getjQuery, - TRANSITION_END, - emulateTransitionEnd, - getElementFromSelector, - getTransitionDurationFromElement, - isVisible, - 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' - -/** - * ------------------------------------------------------------------------ - * Constants - * ------------------------------------------------------------------------ - */ - -const NAME = 'modal' -const VERSION = '4.3.1' -const DATA_KEY = 'bs.modal' -const EVENT_KEY = `.${DATA_KEY}` -const DATA_API_KEY = '.data-api' -const ESCAPE_KEYCODE = 27 // KeyboardEvent.which value for Escape (Esc) key - -const Default = { - backdrop: true, - keyboard: true, - focus: true, - show: true -} - -const DefaultType = { - backdrop: '(boolean|string)', - keyboard: 'boolean', - focus: 'boolean', - show: 'boolean' -} - -const Event = { - HIDE: `hide${EVENT_KEY}`, - HIDDEN: `hidden${EVENT_KEY}`, - SHOW: `show${EVENT_KEY}`, - SHOWN: `shown${EVENT_KEY}`, - FOCUSIN: `focusin${EVENT_KEY}`, - RESIZE: `resize${EVENT_KEY}`, - CLICK_DISMISS: `click.dismiss${EVENT_KEY}`, - KEYDOWN_DISMISS: `keydown.dismiss${EVENT_KEY}`, - MOUSEUP_DISMISS: `mouseup.dismiss${EVENT_KEY}`, - MOUSEDOWN_DISMISS: `mousedown.dismiss${EVENT_KEY}`, - CLICK_DATA_API: `click${EVENT_KEY}${DATA_API_KEY}` -} - -const ClassName = { - SCROLLABLE: 'modal-dialog-scrollable', - SCROLLBAR_MEASURER: 'modal-scrollbar-measure', - BACKDROP: 'modal-backdrop', - OPEN: 'modal-open', - FADE: 'fade', - SHOW: 'show' -} - -const Selector = { - DIALOG: '.modal-dialog', - MODAL_BODY: '.modal-body', - DATA_TOGGLE: '[data-toggle="modal"]', - DATA_DISMISS: '[data-dismiss="modal"]', - FIXED_CONTENT: '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top', - STICKY_CONTENT: '.sticky-top' -} - -/** - * ------------------------------------------------------------------------ - * Class Definition - * ------------------------------------------------------------------------ - */ - -class Modal { - constructor(element, config) { - this._config = this._getConfig(config) - this._element = element - this._dialog = SelectorEngine.findOne(Selector.DIALOG, element) - this._backdrop = null - this._isShown = false - this._isBodyOverflowing = false - this._ignoreBackdropClick = false - this._isTransitioning = false - this._scrollbarWidth = 0 - Data.setData(element, DATA_KEY, this) - } - - // Getters - - static get VERSION() { - return VERSION - } - - static get Default() { - return Default - } - - // Public - - toggle(relatedTarget) { - return this._isShown ? this.hide() : this.show(relatedTarget) - } - - show(relatedTarget) { - if (this._isShown || this._isTransitioning) { - return - } - - if (this._element.classList.contains(ClassName.FADE)) { - this._isTransitioning = true - } - - const showEvent = EventHandler.trigger(this._element, Event.SHOW, { - relatedTarget - }) - - if (this._isShown || showEvent.defaultPrevented) { - return - } - - this._isShown = true - - this._checkScrollbar() - this._setScrollbar() - - this._adjustDialog() - - this._setEscapeEvent() - this._setResizeEvent() - - EventHandler.on(this._element, - Event.CLICK_DISMISS, - Selector.DATA_DISMISS, - event => this.hide(event) - ) - - EventHandler.on(this._dialog, Event.MOUSEDOWN_DISMISS, () => { - EventHandler.one(this._element, Event.MOUSEUP_DISMISS, event => { - if (event.target === this._element) { - this._ignoreBackdropClick = true - } - }) - }) - - this._showBackdrop(() => this._showElement(relatedTarget)) - } - - hide(event) { - if (event) { - event.preventDefault() - } - - if (!this._isShown || this._isTransitioning) { - return - } - - const hideEvent = EventHandler.trigger(this._element, Event.HIDE) - - if (hideEvent.defaultPrevented) { - return - } - - this._isShown = false - const transition = this._element.classList.contains(ClassName.FADE) - - if (transition) { - this._isTransitioning = true - } - - this._setEscapeEvent() - this._setResizeEvent() - - EventHandler.off(document, Event.FOCUSIN) - - this._element.classList.remove(ClassName.SHOW) - - EventHandler.off(this._element, Event.CLICK_DISMISS) - EventHandler.off(this._dialog, Event.MOUSEDOWN_DISMISS) - - if (transition) { - const transitionDuration = getTransitionDurationFromElement(this._element) - - EventHandler.one(this._element, TRANSITION_END, event => this._hideModal(event)) - emulateTransitionEnd(this._element, transitionDuration) - } else { - this._hideModal() - } - } - - dispose() { - [window, this._element, this._dialog] - .forEach(htmlElement => EventHandler.off(htmlElement, EVENT_KEY)) - - /** - * `document` has 2 events `Event.FOCUSIN` and `Event.CLICK_DATA_API` - * Do not move `document` in `htmlElements` array - * It will remove `Event.CLICK_DATA_API` event that should remain - */ - EventHandler.off(document, Event.FOCUSIN) - - Data.removeData(this._element, DATA_KEY) - - this._config = null - this._element = null - this._dialog = null - this._backdrop = null - this._isShown = null - this._isBodyOverflowing = null - this._ignoreBackdropClick = null - this._isTransitioning = null - this._scrollbarWidth = null - } - - handleUpdate() { - this._adjustDialog() - } - - // Private - - _getConfig(config) { - config = { - ...Default, - ...config - } - typeCheckConfig(NAME, config, DefaultType) - return config - } - - _showElement(relatedTarget) { - const transition = this._element.classList.contains(ClassName.FADE) - const modalBody = SelectorEngine.findOne(Selector.MODAL_BODY, this._dialog) - - if (!this._element.parentNode || - this._element.parentNode.nodeType !== Node.ELEMENT_NODE) { - // Don't move modal's DOM position - document.body.appendChild(this._element) - } - - this._element.style.display = 'block' - this._element.removeAttribute('aria-hidden') - this._element.setAttribute('aria-modal', true) - - if (this._dialog.classList.contains(ClassName.SCROLLABLE) && modalBody) { - modalBody.scrollTop = 0 - } else { - this._element.scrollTop = 0 - } - - if (transition) { - reflow(this._element) - } - - this._element.classList.add(ClassName.SHOW) - - if (this._config.focus) { - this._enforceFocus() - } - - const transitionComplete = () => { - if (this._config.focus) { - this._element.focus() - } - - this._isTransitioning = false - EventHandler.trigger(this._element, Event.SHOWN, { - relatedTarget - }) - } - - if (transition) { - const transitionDuration = getTransitionDurationFromElement(this._dialog) - - EventHandler.one(this._dialog, TRANSITION_END, transitionComplete) - emulateTransitionEnd(this._dialog, transitionDuration) - } else { - transitionComplete() - } - } - - _enforceFocus() { - EventHandler.off(document, Event.FOCUSIN) // guard against infinite focus loop - EventHandler.on(document, Event.FOCUSIN, event => { - if (document !== event.target && - this._element !== event.target && - !this._element.contains(event.target)) { - this._element.focus() - } - }) - } - - _setEscapeEvent() { - if (this._isShown && this._config.keyboard) { - EventHandler.on(this._element, Event.KEYDOWN_DISMISS, event => { - if (event.which === ESCAPE_KEYCODE) { - event.preventDefault() - this.hide() - } - }) - } else { - EventHandler.off(this._element, Event.KEYDOWN_DISMISS) - } - } - - _setResizeEvent() { - if (this._isShown) { - EventHandler.on(window, Event.RESIZE, () => this._adjustDialog()) - } else { - EventHandler.off(window, Event.RESIZE) - } - } - - _hideModal() { - this._element.style.display = 'none' - this._element.setAttribute('aria-hidden', true) - this._element.removeAttribute('aria-modal') - this._isTransitioning = false - this._showBackdrop(() => { - document.body.classList.remove(ClassName.OPEN) - this._resetAdjustments() - this._resetScrollbar() - EventHandler.trigger(this._element, Event.HIDDEN) - }) - } - - _removeBackdrop() { - this._backdrop.parentNode.removeChild(this._backdrop) - this._backdrop = null - } - - _showBackdrop(callback) { - const animate = this._element.classList.contains(ClassName.FADE) ? - ClassName.FADE : - '' - - if (this._isShown && this._config.backdrop) { - this._backdrop = document.createElement('div') - this._backdrop.className = ClassName.BACKDROP - - if (animate) { - this._backdrop.classList.add(animate) - } - - document.body.appendChild(this._backdrop) - - EventHandler.on(this._element, Event.CLICK_DISMISS, event => { - if (this._ignoreBackdropClick) { - this._ignoreBackdropClick = false - return - } - - if (event.target !== event.currentTarget) { - return - } - - if (this._config.backdrop === 'static') { - this._element.focus() - } else { - this.hide() - } - }) - - if (animate) { - reflow(this._backdrop) - } - - this._backdrop.classList.add(ClassName.SHOW) - - if (!animate) { - callback() - return - } - - const backdropTransitionDuration = getTransitionDurationFromElement(this._backdrop) - - EventHandler.one(this._backdrop, TRANSITION_END, callback) - emulateTransitionEnd(this._backdrop, backdropTransitionDuration) - } else if (!this._isShown && this._backdrop) { - this._backdrop.classList.remove(ClassName.SHOW) - - const callbackRemove = () => { - this._removeBackdrop() - callback() - } - - if (this._element.classList.contains(ClassName.FADE)) { - const backdropTransitionDuration = getTransitionDurationFromElement(this._backdrop) - EventHandler.one(this._backdrop, TRANSITION_END, callbackRemove) - emulateTransitionEnd(this._backdrop, backdropTransitionDuration) - } else { - callbackRemove() - } - } else { - callback() - } - } - - // ---------------------------------------------------------------------- - // the following methods are used to handle overflowing modals - // ---------------------------------------------------------------------- - - _adjustDialog() { - const isModalOverflowing = - this._element.scrollHeight > document.documentElement.clientHeight - - if (!this._isBodyOverflowing && isModalOverflowing) { - this._element.style.paddingLeft = `${this._scrollbarWidth}px` - } - - if (this._isBodyOverflowing && !isModalOverflowing) { - this._element.style.paddingRight = `${this._scrollbarWidth}px` - } - } - - _resetAdjustments() { - this._element.style.paddingLeft = '' - this._element.style.paddingRight = '' - } - - _checkScrollbar() { - const rect = document.body.getBoundingClientRect() - this._isBodyOverflowing = rect.left + rect.right < window.innerWidth - this._scrollbarWidth = this._getScrollbarWidth() - } - - _setScrollbar() { - if (this._isBodyOverflowing) { - // Note: DOMNode.style.paddingRight returns the actual value or '' if not set - // while $(DOMNode).css('padding-right') returns the calculated value or 0 if not set - - // Adjust fixed content padding - makeArray(SelectorEngine.find(Selector.FIXED_CONTENT)) - .forEach(element => { - const actualPadding = element.style.paddingRight - const calculatedPadding = window.getComputedStyle(element)['padding-right'] - Manipulator.setDataAttribute(element, 'padding-right', actualPadding) - element.style.paddingRight = `${parseFloat(calculatedPadding) + this._scrollbarWidth}px` - }) - - // Adjust sticky content margin - makeArray(SelectorEngine.find(Selector.STICKY_CONTENT)) - .forEach(element => { - const actualMargin = element.style.marginRight - const calculatedMargin = window.getComputedStyle(element)['margin-right'] - Manipulator.setDataAttribute(element, 'margin-right', actualMargin) - element.style.marginRight = `${parseFloat(calculatedMargin) - this._scrollbarWidth}px` - }) - - // Adjust body padding - const actualPadding = document.body.style.paddingRight - const calculatedPadding = window.getComputedStyle(document.body)['padding-right'] - - Manipulator.setDataAttribute(document.body, 'padding-right', actualPadding) - document.body.style.paddingRight = `${parseFloat(calculatedPadding) + this._scrollbarWidth}px` - } - - document.body.classList.add(ClassName.OPEN) - } - - _resetScrollbar() { - // Restore fixed content padding - makeArray(SelectorEngine.find(Selector.FIXED_CONTENT)) - .forEach(element => { - const padding = Manipulator.getDataAttribute(element, 'padding-right') - if (typeof padding !== 'undefined') { - Manipulator.removeDataAttribute(element, 'padding-right') - element.style.paddingRight = padding - } - }) - - // Restore sticky content and navbar-toggler margin - makeArray(SelectorEngine.find(`${Selector.STICKY_CONTENT}`)) - .forEach(element => { - const margin = Manipulator.getDataAttribute(element, 'margin-right') - if (typeof margin !== 'undefined') { - Manipulator.removeDataAttribute(element, 'margin-right') - element.style.marginRight = margin - } - }) - - // Restore body padding - const padding = Manipulator.getDataAttribute(document.body, 'padding-right') - if (typeof padding === 'undefined') { - document.body.style.paddingRight = '' - } else { - Manipulator.removeDataAttribute(document.body, 'padding-right') - document.body.style.paddingRight = padding - } - } - - _getScrollbarWidth() { // thx d.walsh - const scrollDiv = document.createElement('div') - scrollDiv.className = ClassName.SCROLLBAR_MEASURER - document.body.appendChild(scrollDiv) - const scrollbarWidth = scrollDiv.getBoundingClientRect().width - scrollDiv.clientWidth - document.body.removeChild(scrollDiv) - return scrollbarWidth - } - - // Static - - static jQueryInterface(config, relatedTarget) { - return this.each(function () { - let data = Data.getData(this, DATA_KEY) - const _config = { - ...Default, - ...Manipulator.getDataAttributes(this), - ...typeof config === 'object' && config ? config : {} - } - - if (!data) { - data = new Modal(this, _config) - } - - if (typeof config === 'string') { - if (typeof data[config] === 'undefined') { - throw new TypeError(`No method named "${config}"`) - } - - data[config](relatedTarget) - } else if (_config.show) { - data.show(relatedTarget) - } - }) - } - - static getInstance(element) { - return Data.getData(element, DATA_KEY) - } -} - -/** - * ------------------------------------------------------------------------ - * Data Api implementation - * ------------------------------------------------------------------------ - */ - -EventHandler.on(document, Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) { - const target = getElementFromSelector(this) - - if (this.tagName === 'A' || this.tagName === 'AREA') { - event.preventDefault() - } - - EventHandler.one(target, Event.SHOW, showEvent => { - if (showEvent.defaultPrevented) { - // only register focus restorer if modal will actually get shown - return - } - - EventHandler.one(target, Event.HIDDEN, () => { - if (isVisible(this)) { - this.focus() - } - }) - }) - - let data = Data.getData(target, DATA_KEY) - if (!data) { - const config = { - ...Manipulator.getDataAttributes(target), - ...Manipulator.getDataAttributes(this) - } - - data = new Modal(target, config) - } - - data.show(this) -}) - -const $ = getjQuery() - -/** - * ------------------------------------------------------------------------ - * jQuery - * ------------------------------------------------------------------------ - * add .modal to jQuery only if jQuery is present - */ -/* istanbul ignore if */ -if ($) { - const JQUERY_NO_CONFLICT = $.fn[NAME] - $.fn[NAME] = Modal.jQueryInterface - $.fn[NAME].Constructor = Modal - $.fn[NAME].noConflict = () => { - $.fn[NAME] = JQUERY_NO_CONFLICT - return Modal.jQueryInterface - } -} - -export default Modal diff --git a/js/src/modal/modal.spec.js b/js/src/modal/modal.spec.js deleted file mode 100644 index 292f61f8b..000000000 --- a/js/src/modal/modal.spec.js +++ /dev/null @@ -1,987 +0,0 @@ -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 occurred 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) - - modalEl.addEventListener('shown.bs.modal', () => { - expect(modalBody.scrollTop).toEqual(0) - done() - }) - - modal.show() - }) - - it('should set .modal\'s scroll top to 0 if .modal-dialog-scrollable and modal body do not exists', done => { - fixtureEl.innerHTML = [ - '<div class="modal fade">', - ' <div class="modal-dialog modal-dialog-scrollable">', - ' </div>', - '</div>' - ].join('') - - const modalEl = fixtureEl.querySelector('.modal') - const modal = new Modal(modalEl) - - modalEl.addEventListener('shown.bs.modal', () => { - expect(modalEl.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('') - - 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() - 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" style="display: none;"></a>', - '<div id="exampleModal" class="modal" style="display: none;"><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) - }) - }) -}) |
