diff options
Diffstat (limited to 'js/src/collapse')
| -rw-r--r-- | js/src/collapse/collapse.js | 441 | ||||
| -rw-r--r-- | js/src/collapse/collapse.spec.js | 826 |
2 files changed, 0 insertions, 1267 deletions
diff --git a/js/src/collapse/collapse.js b/js/src/collapse/collapse.js deleted file mode 100644 index 4de7b5282..000000000 --- a/js/src/collapse/collapse.js +++ /dev/null @@ -1,441 +0,0 @@ -/** - * -------------------------------------------------------------------------- - * Bootstrap (v4.3.1): collapse.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * -------------------------------------------------------------------------- - */ - -import { - getjQuery, - TRANSITION_END, - emulateTransitionEnd, - getSelectorFromElement, - getElementFromSelector, - getTransitionDurationFromElement, - isElement, - 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 = 'collapse' -const VERSION = '4.3.1' -const DATA_KEY = 'bs.collapse' -const EVENT_KEY = `.${DATA_KEY}` -const DATA_API_KEY = '.data-api' - -const Default = { - toggle: true, - parent: '' -} - -const DefaultType = { - toggle: 'boolean', - parent: '(string|element)' -} - -const Event = { - SHOW: `show${EVENT_KEY}`, - SHOWN: `shown${EVENT_KEY}`, - HIDE: `hide${EVENT_KEY}`, - HIDDEN: `hidden${EVENT_KEY}`, - CLICK_DATA_API: `click${EVENT_KEY}${DATA_API_KEY}` -} - -const ClassName = { - SHOW: 'show', - COLLAPSE: 'collapse', - COLLAPSING: 'collapsing', - COLLAPSED: 'collapsed' -} - -const Dimension = { - WIDTH: 'width', - HEIGHT: 'height' -} - -const Selector = { - ACTIVES: '.show, .collapsing', - DATA_TOGGLE: '[data-toggle="collapse"]' -} - -/** - * ------------------------------------------------------------------------ - * Class Definition - * ------------------------------------------------------------------------ - */ - -class Collapse { - constructor(element, config) { - this._isTransitioning = false - this._element = element - this._config = this._getConfig(config) - this._triggerArray = makeArray(SelectorEngine.find( - `[data-toggle="collapse"][href="#${element.id}"],` + - `[data-toggle="collapse"][data-target="#${element.id}"]` - )) - - const toggleList = makeArray(SelectorEngine.find(Selector.DATA_TOGGLE)) - for (let i = 0, len = toggleList.length; i < len; i++) { - const elem = toggleList[i] - const selector = getSelectorFromElement(elem) - const filterElement = makeArray(SelectorEngine.find(selector)) - .filter(foundElem => foundElem === element) - - if (selector !== null && filterElement.length) { - this._selector = selector - this._triggerArray.push(elem) - } - } - - this._parent = this._config.parent ? this._getParent() : null - - if (!this._config.parent) { - this._addAriaAndCollapsedClass(this._element, this._triggerArray) - } - - if (this._config.toggle) { - this.toggle() - } - - Data.setData(element, DATA_KEY, this) - } - - // Getters - - static get VERSION() { - return VERSION - } - - static get Default() { - return Default - } - - // Public - - toggle() { - if (this._element.classList.contains(ClassName.SHOW)) { - this.hide() - } else { - this.show() - } - } - - show() { - if (this._isTransitioning || - this._element.classList.contains(ClassName.SHOW)) { - return - } - - let actives - let activesData - - if (this._parent) { - actives = makeArray(SelectorEngine.find(Selector.ACTIVES, this._parent)) - .filter(elem => { - if (typeof this._config.parent === 'string') { - return elem.getAttribute('data-parent') === this._config.parent - } - - return elem.classList.contains(ClassName.COLLAPSE) - }) - - if (actives.length === 0) { - actives = null - } - } - - const container = SelectorEngine.findOne(this._selector) - if (actives) { - const tempActiveData = actives.filter(elem => container !== elem) - activesData = tempActiveData[0] ? Data.getData(tempActiveData[0], DATA_KEY) : null - - if (activesData && activesData._isTransitioning) { - return - } - } - - const startEvent = EventHandler.trigger(this._element, Event.SHOW) - if (startEvent.defaultPrevented) { - return - } - - if (actives) { - actives.forEach(elemActive => { - if (container !== elemActive) { - Collapse.collapseInterface(elemActive, 'hide') - } - - if (!activesData) { - Data.setData(elemActive, DATA_KEY, null) - } - }) - } - - const dimension = this._getDimension() - - this._element.classList.remove(ClassName.COLLAPSE) - this._element.classList.add(ClassName.COLLAPSING) - - this._element.style[dimension] = 0 - - if (this._triggerArray.length) { - this._triggerArray.forEach(element => { - element.classList.remove(ClassName.COLLAPSED) - element.setAttribute('aria-expanded', true) - }) - } - - this.setTransitioning(true) - - const complete = () => { - this._element.classList.remove(ClassName.COLLAPSING) - this._element.classList.add(ClassName.COLLAPSE) - this._element.classList.add(ClassName.SHOW) - - this._element.style[dimension] = '' - - this.setTransitioning(false) - - EventHandler.trigger(this._element, Event.SHOWN) - } - - const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1) - const scrollSize = `scroll${capitalizedDimension}` - const transitionDuration = getTransitionDurationFromElement(this._element) - - EventHandler.one(this._element, TRANSITION_END, complete) - - emulateTransitionEnd(this._element, transitionDuration) - this._element.style[dimension] = `${this._element[scrollSize]}px` - } - - hide() { - if (this._isTransitioning || - !this._element.classList.contains(ClassName.SHOW)) { - return - } - - const startEvent = EventHandler.trigger(this._element, Event.HIDE) - if (startEvent.defaultPrevented) { - return - } - - const dimension = this._getDimension() - - this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px` - - reflow(this._element) - - this._element.classList.add(ClassName.COLLAPSING) - this._element.classList.remove(ClassName.COLLAPSE) - this._element.classList.remove(ClassName.SHOW) - - const triggerArrayLength = this._triggerArray.length - if (triggerArrayLength > 0) { - for (let i = 0; i < triggerArrayLength; i++) { - const trigger = this._triggerArray[i] - const elem = getElementFromSelector(trigger) - - if (elem && !elem.classList.contains(ClassName.SHOW)) { - trigger.classList.add(ClassName.COLLAPSED) - trigger.setAttribute('aria-expanded', false) - } - } - } - - this.setTransitioning(true) - - const complete = () => { - this.setTransitioning(false) - this._element.classList.remove(ClassName.COLLAPSING) - this._element.classList.add(ClassName.COLLAPSE) - EventHandler.trigger(this._element, Event.HIDDEN) - } - - this._element.style[dimension] = '' - const transitionDuration = getTransitionDurationFromElement(this._element) - - EventHandler.one(this._element, TRANSITION_END, complete) - emulateTransitionEnd(this._element, transitionDuration) - } - - setTransitioning(isTransitioning) { - this._isTransitioning = isTransitioning - } - - dispose() { - Data.removeData(this._element, DATA_KEY) - - this._config = null - this._parent = null - this._element = null - this._triggerArray = null - this._isTransitioning = null - } - - // Private - - _getConfig(config) { - config = { - ...Default, - ...config - } - config.toggle = Boolean(config.toggle) // Coerce string values - typeCheckConfig(NAME, config, DefaultType) - return config - } - - _getDimension() { - const hasWidth = this._element.classList.contains(Dimension.WIDTH) - return hasWidth ? Dimension.WIDTH : Dimension.HEIGHT - } - - _getParent() { - let { parent } = this._config - - if (isElement(parent)) { - // it's a jQuery object - if (typeof parent.jquery !== 'undefined' || typeof parent[0] !== 'undefined') { - parent = parent[0] - } - } else { - parent = SelectorEngine.findOne(parent) - } - - const selector = `[data-toggle="collapse"][data-parent="${parent}"]` - - makeArray(SelectorEngine.find(selector, parent)) - .forEach(element => { - const selected = getElementFromSelector(element) - - this._addAriaAndCollapsedClass( - selected, - [element] - ) - }) - - return parent - } - - _addAriaAndCollapsedClass(element, triggerArray) { - if (element) { - const isOpen = element.classList.contains(ClassName.SHOW) - - if (triggerArray.length) { - triggerArray.forEach(elem => { - if (isOpen) { - elem.classList.remove(ClassName.COLLAPSED) - } else { - elem.classList.add(ClassName.COLLAPSED) - } - - elem.setAttribute('aria-expanded', isOpen) - }) - } - } - } - - // Static - - static collapseInterface(element, config) { - let data = Data.getData(element, DATA_KEY) - const _config = { - ...Default, - ...Manipulator.getDataAttributes(element), - ...typeof config === 'object' && config ? config : {} - } - - if (!data && _config.toggle && /show|hide/.test(config)) { - _config.toggle = false - } - - if (!data) { - data = new Collapse(element, _config) - } - - if (typeof config === 'string') { - if (typeof data[config] === 'undefined') { - throw new TypeError(`No method named "${config}"`) - } - - data[config]() - } - } - - static jQueryInterface(config) { - return this.each(function () { - Collapse.collapseInterface(this, config) - }) - } - - static getInstance(element) { - return Data.getData(element, DATA_KEY) - } -} - -/** - * ------------------------------------------------------------------------ - * Data Api implementation - * ------------------------------------------------------------------------ - */ - -EventHandler.on(document, Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) { - // preventDefault only for <a> elements (which change the URL) not inside the collapsible element - if (event.target.tagName === 'A') { - event.preventDefault() - } - - const triggerData = Manipulator.getDataAttributes(this) - const selector = getSelectorFromElement(this) - const selectorElements = makeArray(SelectorEngine.find(selector)) - - selectorElements.forEach(element => { - const data = Data.getData(element, DATA_KEY) - let config - if (data) { - // update parent attribute - if (data._parent === null && typeof triggerData.parent === 'string') { - data._config.parent = triggerData.parent - data._parent = data._getParent() - } - - config = 'toggle' - } else { - config = triggerData - } - - Collapse.collapseInterface(element, config) - }) -}) - -const $ = getjQuery() - -/** - * ------------------------------------------------------------------------ - * jQuery - * ------------------------------------------------------------------------ - * add .collapse to jQuery only if jQuery is present - */ -/* istanbul ignore if */ -if ($) { - const JQUERY_NO_CONFLICT = $.fn[NAME] - $.fn[NAME] = Collapse.jQueryInterface - $.fn[NAME].Constructor = Collapse - $.fn[NAME].noConflict = () => { - $.fn[NAME] = JQUERY_NO_CONFLICT - return Collapse.jQueryInterface - } -} - -export default Collapse diff --git a/js/src/collapse/collapse.spec.js b/js/src/collapse/collapse.spec.js deleted file mode 100644 index 154bc2c6b..000000000 --- a/js/src/collapse/collapse.spec.js +++ /dev/null @@ -1,826 +0,0 @@ -import Collapse from './collapse' -import EventHandler from '../dom/event-handler' -import { makeArray } from '../util/index' - -/** Test helpers */ -import { getFixture, clearFixture, jQueryMock } from '../../tests/helpers/fixture' - -describe('Collapse', () => { - let fixtureEl - - beforeAll(() => { - fixtureEl = getFixture() - }) - - afterEach(() => { - clearFixture() - }) - - describe('VERSION', () => { - it('should return plugin version', () => { - expect(Collapse.VERSION).toEqual(jasmine.any(String)) - }) - }) - - describe('Default', () => { - it('should return plugin default config', () => { - expect(Collapse.Default).toEqual(jasmine.any(Object)) - }) - }) - - describe('constructor', () => { - it('should allow jquery object in parent config', () => { - fixtureEl.innerHTML = [ - '<div class="my-collapse">', - ' <div class="item">', - ' <a data-toggle="collapse" href="#">Toggle item</a>', - ' <div class="collapse">Lorem ipsum</div>', - ' </div>', - '</div>' - ].join('') - - const collapseEl = fixtureEl.querySelector('div.collapse') - const myCollapseEl = fixtureEl.querySelector('.my-collapse') - const fakejQueryObject = { - 0: myCollapseEl - } - const collapse = new Collapse(collapseEl, { - parent: fakejQueryObject - }) - - expect(collapse._config.parent).toEqual(fakejQueryObject) - expect(collapse._getParent()).toEqual(myCollapseEl) - }) - - it('should allow non jquery object in parent config', () => { - fixtureEl.innerHTML = [ - '<div class="my-collapse">', - ' <div class="item">', - ' <a data-toggle="collapse" href="#">Toggle item</a>', - ' <div class="collapse">Lorem ipsum</div>', - ' </div>', - '</div>' - ].join('') - - const collapseEl = fixtureEl.querySelector('div.collapse') - const myCollapseEl = fixtureEl.querySelector('.my-collapse') - const collapse = new Collapse(collapseEl, { - parent: myCollapseEl - }) - - expect(collapse._config.parent).toEqual(myCollapseEl) - }) - - it('should allow string selector in parent config', () => { - fixtureEl.innerHTML = [ - '<div class="my-collapse">', - ' <div class="item">', - ' <a data-toggle="collapse" href="#">Toggle item</a>', - ' <div class="collapse">Lorem ipsum</div>', - ' </div>', - '</div>' - ].join('') - - const collapseEl = fixtureEl.querySelector('div.collapse') - const myCollapseEl = fixtureEl.querySelector('.my-collapse') - const collapse = new Collapse(collapseEl, { - parent: 'div.my-collapse' - }) - - expect(collapse._config.parent).toEqual('div.my-collapse') - expect(collapse._getParent()).toEqual(myCollapseEl) - }) - }) - - describe('toggle', () => { - it('should call show method if show class is not present', () => { - fixtureEl.innerHTML = '<div></div>' - - const collapseEl = fixtureEl.querySelector('div') - const collapse = new Collapse(collapseEl) - - spyOn(collapse, 'show') - - collapse.toggle() - - expect(collapse.show).toHaveBeenCalled() - }) - - it('should call hide method if show class is present', () => { - fixtureEl.innerHTML = '<div class="show"></div>' - - const collapseEl = fixtureEl.querySelector('.show') - const collapse = new Collapse(collapseEl, { - toggle: false - }) - - spyOn(collapse, 'hide') - - collapse.toggle() - - expect(collapse.hide).toHaveBeenCalled() - }) - - it('should find collapse children if they have collapse class too not only data-parent', done => { - fixtureEl.innerHTML = [ - '<div class="my-collapse">', - ' <div class="item">', - ' <a data-toggle="collapse" href="#">Toggle item 1</a>', - ' <div id="collapse1" class="collapse show">Lorem ipsum 1</div>', - ' </div>', - ' <div class="item">', - ' <a id="triggerCollapse2" data-toggle="collapse" href="#">Toggle item 2</a>', - ' <div id="collapse2" class="collapse">Lorem ipsum 2</div>', - ' </div>', - '</div>' - ].join('') - - const parent = fixtureEl.querySelector('.my-collapse') - const collapseEl1 = fixtureEl.querySelector('#collapse1') - const collapseEl2 = fixtureEl.querySelector('#collapse2') - - const collapseList = makeArray(fixtureEl.querySelectorAll('.collapse')) - .map(el => new Collapse(el, { - parent, - toggle: false - })) - - collapseEl2.addEventListener('shown.bs.collapse', () => { - expect(collapseEl2.classList.contains('show')).toEqual(true) - expect(collapseEl1.classList.contains('show')).toEqual(false) - done() - }) - - collapseList[1].toggle() - }) - }) - - describe('show', () => { - it('should do nothing if is transitioning', () => { - fixtureEl.innerHTML = '<div></div>' - - spyOn(EventHandler, 'trigger') - - const collapseEl = fixtureEl.querySelector('div') - const collapse = new Collapse(collapseEl, { - toggle: false - }) - - collapse._isTransitioning = true - collapse.show() - - expect(EventHandler.trigger).not.toHaveBeenCalled() - }) - - it('should do nothing if already shown', () => { - fixtureEl.innerHTML = '<div class="show"></div>' - - spyOn(EventHandler, 'trigger') - - const collapseEl = fixtureEl.querySelector('div') - const collapse = new Collapse(collapseEl, { - toggle: false - }) - - collapse.show() - - expect(EventHandler.trigger).not.toHaveBeenCalled() - }) - - it('should show a collapsed element', done => { - fixtureEl.innerHTML = '<div class="collapse" style="height: 0px;"></div>' - - const collapseEl = fixtureEl.querySelector('div') - const collapse = new Collapse(collapseEl, { - toggle: false - }) - - collapseEl.addEventListener('show.bs.collapse', () => { - expect(collapseEl.style.height).toEqual('0px') - }) - collapseEl.addEventListener('shown.bs.collapse', () => { - expect(collapseEl.classList.contains('show')).toEqual(true) - expect(collapseEl.style.height).toEqual('') - done() - }) - - collapse.show() - }) - - it('should show a collapsed element on width', done => { - fixtureEl.innerHTML = '<div class="collapse width" style="width: 0px;"></div>' - - const collapseEl = fixtureEl.querySelector('div') - const collapse = new Collapse(collapseEl, { - toggle: false - }) - - collapseEl.addEventListener('show.bs.collapse', () => { - expect(collapseEl.style.width).toEqual('0px') - }) - collapseEl.addEventListener('shown.bs.collapse', () => { - expect(collapseEl.classList.contains('show')).toEqual(true) - expect(collapseEl.style.width).toEqual('') - done() - }) - - collapse.show() - }) - - it('should collapse only the first collapse', done => { - fixtureEl.innerHTML = [ - '<div class="card" id="accordion1">', - ' <div id="collapse1" class="collapse"/>', - '</div>', - '<div class="card" id="accordion2">', - ' <div id="collapse2" class="collapse show"/>', - '</div>' - ].join('') - - const el1 = fixtureEl.querySelector('#collapse1') - const el2 = fixtureEl.querySelector('#collapse2') - const collapse = new Collapse(el1, { - toggle: false - }) - - el1.addEventListener('shown.bs.collapse', () => { - expect(el1.classList.contains('show')).toEqual(true) - expect(el2.classList.contains('show')).toEqual(true) - done() - }) - - collapse.show() - }) - - it('should not fire shown when show is prevented', done => { - fixtureEl.innerHTML = '<div class="collapse"></div>' - - const collapseEl = fixtureEl.querySelector('div') - const collapse = new Collapse(collapseEl, { - toggle: false - }) - - const expectEnd = () => { - setTimeout(() => { - expect().nothing() - done() - }, 10) - } - - collapseEl.addEventListener('show.bs.collapse', e => { - e.preventDefault() - expectEnd() - }) - - collapseEl.addEventListener('shown.bs.collapse', () => { - throw new Error('should not fire shown event') - }) - - collapse.show() - }) - }) - - describe('hide', () => { - it('should do nothing if is transitioning', () => { - fixtureEl.innerHTML = '<div></div>' - - spyOn(EventHandler, 'trigger') - - const collapseEl = fixtureEl.querySelector('div') - const collapse = new Collapse(collapseEl, { - toggle: false - }) - - collapse._isTransitioning = true - collapse.hide() - - expect(EventHandler.trigger).not.toHaveBeenCalled() - }) - - it('should do nothing if already shown', () => { - fixtureEl.innerHTML = '<div></div>' - - spyOn(EventHandler, 'trigger') - - const collapseEl = fixtureEl.querySelector('div') - const collapse = new Collapse(collapseEl, { - toggle: false - }) - - collapse.hide() - - expect(EventHandler.trigger).not.toHaveBeenCalled() - }) - - it('should hide a collapsed element', done => { - fixtureEl.innerHTML = '<div class="collapse show"></div>' - - const collapseEl = fixtureEl.querySelector('div') - const collapse = new Collapse(collapseEl, { - toggle: false - }) - - collapseEl.addEventListener('hidden.bs.collapse', () => { - expect(collapseEl.classList.contains('show')).toEqual(false) - expect(collapseEl.style.height).toEqual('') - done() - }) - - collapse.hide() - }) - - it('should not fire hidden when hide is prevented', done => { - fixtureEl.innerHTML = '<div class="collapse show"></div>' - - const collapseEl = fixtureEl.querySelector('div') - const collapse = new Collapse(collapseEl, { - toggle: false - }) - - const expectEnd = () => { - setTimeout(() => { - expect().nothing() - done() - }, 10) - } - - collapseEl.addEventListener('hide.bs.collapse', e => { - e.preventDefault() - expectEnd() - }) - - collapseEl.addEventListener('hidden.bs.collapse', () => { - throw new Error('should not fire hidden event') - }) - - collapse.hide() - }) - }) - - describe('dispose', () => { - it('should destroy a collapse', () => { - fixtureEl.innerHTML = '<div class="collapse show"></div>' - - const collapseEl = fixtureEl.querySelector('div') - const collapse = new Collapse(collapseEl, { - toggle: false - }) - - expect(Collapse.getInstance(collapseEl)).toEqual(collapse) - - collapse.dispose() - - expect(Collapse.getInstance(collapseEl)).toEqual(null) - }) - }) - - describe('data-api', () => { - it('should show multiple collapsed elements', done => { - fixtureEl.innerHTML = [ - '<a role="button" data-toggle="collapse" class="collapsed" href=".multi"></a>', - '<div id="collapse1" class="collapse multi"/>', - '<div id="collapse2" class="collapse multi"/>' - ].join('') - - const trigger = fixtureEl.querySelector('a') - const collapse1 = fixtureEl.querySelector('#collapse1') - const collapse2 = fixtureEl.querySelector('#collapse2') - - collapse2.addEventListener('shown.bs.collapse', () => { - expect(trigger.getAttribute('aria-expanded')).toEqual('true') - expect(trigger.classList.contains('collapsed')).toEqual(false) - expect(collapse1.classList.contains('show')).toEqual(true) - expect(collapse1.classList.contains('show')).toEqual(true) - done() - }) - - trigger.click() - }) - - it('should hide multiple collapsed elements', done => { - fixtureEl.innerHTML = [ - '<a role="button" data-toggle="collapse" href=".multi"></a>', - '<div id="collapse1" class="collapse multi show"/>', - '<div id="collapse2" class="collapse multi show"/>' - ].join('') - - const trigger = fixtureEl.querySelector('a') - const collapse1 = fixtureEl.querySelector('#collapse1') - const collapse2 = fixtureEl.querySelector('#collapse2') - - collapse2.addEventListener('hidden.bs.collapse', () => { - expect(trigger.getAttribute('aria-expanded')).toEqual('false') - expect(trigger.classList.contains('collapsed')).toEqual(true) - expect(collapse1.classList.contains('show')).toEqual(false) - expect(collapse1.classList.contains('show')).toEqual(false) - done() - }) - - trigger.click() - }) - - it('should remove "collapsed" class from target when collapse is shown', done => { - fixtureEl.innerHTML = [ - '<a id="link1" role="button" data-toggle="collapse" class="collapsed" href="#" data-target="#test1" />', - '<a id="link2" role="button" data-toggle="collapse" class="collapsed" href="#" data-target="#test1" />', - '<div id="test1"></div>' - ].join('') - - const link1 = fixtureEl.querySelector('#link1') - const link2 = fixtureEl.querySelector('#link2') - const collapseTest1 = fixtureEl.querySelector('#test1') - - collapseTest1.addEventListener('shown.bs.collapse', () => { - expect(link1.getAttribute('aria-expanded')).toEqual('true') - expect(link2.getAttribute('aria-expanded')).toEqual('true') - expect(link1.classList.contains('collapsed')).toEqual(false) - expect(link2.classList.contains('collapsed')).toEqual(false) - done() - }) - - link1.click() - }) - - it('should add "collapsed" class to target when collapse is hidden', done => { - fixtureEl.innerHTML = [ - '<a id="link1" role="button" data-toggle="collapse" href="#" data-target="#test1" />', - '<a id="link2" role="button" data-toggle="collapse" href="#" data-target="#test1" />', - '<div id="test1" class="show"></div>' - ].join('') - - const link1 = fixtureEl.querySelector('#link1') - const link2 = fixtureEl.querySelector('#link2') - const collapseTest1 = fixtureEl.querySelector('#test1') - - collapseTest1.addEventListener('hidden.bs.collapse', () => { - expect(link1.getAttribute('aria-expanded')).toEqual('false') - expect(link2.getAttribute('aria-expanded')).toEqual('false') - expect(link1.classList.contains('collapsed')).toEqual(true) - expect(link2.classList.contains('collapsed')).toEqual(true) - done() - }) - - link1.click() - }) - - it('should allow accordion to use children other than card', done => { - fixtureEl.innerHTML = [ - '<div id="accordion">', - ' <div class="item">', - ' <a id="linkTrigger" data-toggle="collapse" href="#collapseOne" aria-expanded="false" aria-controls="collapseOne"></a>', - ' <div id="collapseOne" class="collapse" role="tabpanel" aria-labelledby="headingThree" data-parent="#accordion"></div>', - ' </div>', - ' <div class="item">', - ' <a id="linkTriggerTwo" data-toggle="collapse" href="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo"></a>', - ' <div id="collapseTwo" class="collapse show" role="tabpanel" aria-labelledby="headingTwo" data-parent="#accordion"></div>', - ' </div>', - '</div>' - ].join('') - - const trigger = fixtureEl.querySelector('#linkTrigger') - const triggerTwo = fixtureEl.querySelector('#linkTriggerTwo') - const collapseOne = fixtureEl.querySelector('#collapseOne') - const collapseTwo = fixtureEl.querySelector('#collapseTwo') - - collapseOne.addEventListener('shown.bs.collapse', () => { - expect(collapseOne.classList.contains('show')).toEqual(true) - expect(collapseTwo.classList.contains('show')).toEqual(false) - - collapseTwo.addEventListener('shown.bs.collapse', () => { - expect(collapseOne.classList.contains('show')).toEqual(false) - expect(collapseTwo.classList.contains('show')).toEqual(true) - done() - }) - - triggerTwo.click() - }) - - trigger.click() - }) - - it('should not prevent event for input', done => { - fixtureEl.innerHTML = [ - '<input type="checkbox" data-toggle="collapse" data-target="#collapsediv1" />', - '<div id="collapsediv1"></div>' - ].join('') - - const target = fixtureEl.querySelector('input') - const collapseEl = fixtureEl.querySelector('#collapsediv1') - - collapseEl.addEventListener('shown.bs.collapse', () => { - expect(collapseEl.classList.contains('show')).toEqual(true) - expect(target.checked).toEqual(true) - done() - }) - - target.click() - }) - - it('should allow accordion to contain nested elements', done => { - fixtureEl.innerHTML = [ - '<div id="accordion">', - ' <div class="row">', - ' <div class="col-lg-6">', - ' <div class="item">', - ' <a id="linkTrigger" data-toggle="collapse" href="#collapseOne" aria-expanded="false" aria-controls="collapseOne"></a>', - ' <div id="collapseOne" class="collapse" role="tabpanel" aria-labelledby="headingThree" data-parent="#accordion"></div>', - ' </div>', - ' </div>', - ' <div class="col-lg-6">', - ' <div class="item">', - ' <a id="linkTriggerTwo" data-toggle="collapse" href="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo"></a>', - ' <div id="collapseTwo" class="collapse show" role="tabpanel" aria-labelledby="headingTwo" data-parent="#accordion"></div>', - ' </div>', - ' </div>', - ' </div>', - '</div>' - ].join('') - - const triggerEl = fixtureEl.querySelector('#linkTrigger') - const triggerTwoEl = fixtureEl.querySelector('#linkTriggerTwo') - const collapseOneEl = fixtureEl.querySelector('#collapseOne') - const collapseTwoEl = fixtureEl.querySelector('#collapseTwo') - - collapseOneEl.addEventListener('shown.bs.collapse', () => { - expect(collapseOneEl.classList.contains('show')).toEqual(true) - expect(triggerEl.classList.contains('collapsed')).toEqual(false) - expect(triggerEl.getAttribute('aria-expanded')).toEqual('true') - - expect(collapseTwoEl.classList.contains('show')).toEqual(false) - expect(triggerTwoEl.classList.contains('collapsed')).toEqual(true) - expect(triggerTwoEl.getAttribute('aria-expanded')).toEqual('false') - - collapseTwoEl.addEventListener('shown.bs.collapse', () => { - expect(collapseOneEl.classList.contains('show')).toEqual(false) - expect(triggerEl.classList.contains('collapsed')).toEqual(true) - expect(triggerEl.getAttribute('aria-expanded')).toEqual('false') - - expect(collapseTwoEl.classList.contains('show')).toEqual(true) - expect(triggerTwoEl.classList.contains('collapsed')).toEqual(false) - expect(triggerTwoEl.getAttribute('aria-expanded')).toEqual('true') - done() - }) - - triggerTwoEl.click() - }) - - triggerEl.click() - }) - - it('should allow accordion to target multiple elements', done => { - fixtureEl.innerHTML = [ - '<div id="accordion">', - ' <a id="linkTriggerOne" data-toggle="collapse" data-target=".collapseOne" href="#" aria-expanded="false" aria-controls="collapseOne"></a>', - ' <a id="linkTriggerTwo" data-toggle="collapse" data-target=".collapseTwo" href="#" aria-expanded="false" aria-controls="collapseTwo"></a>', - ' <div id="collapseOneOne" class="collapse collapseOne" role="tabpanel" data-parent="#accordion"></div>', - ' <div id="collapseOneTwo" class="collapse collapseOne" role="tabpanel" data-parent="#accordion"></div>', - ' <div id="collapseTwoOne" class="collapse collapseTwo" role="tabpanel" data-parent="#accordion"></div>', - ' <div id="collapseTwoTwo" class="collapse collapseTwo" role="tabpanel" data-parent="#accordion"></div>', - '</div>' - ].join('') - - const trigger = fixtureEl.querySelector('#linkTriggerOne') - const triggerTwo = fixtureEl.querySelector('#linkTriggerTwo') - const collapseOneOne = fixtureEl.querySelector('#collapseOneOne') - const collapseOneTwo = fixtureEl.querySelector('#collapseOneTwo') - const collapseTwoOne = fixtureEl.querySelector('#collapseTwoOne') - const collapseTwoTwo = fixtureEl.querySelector('#collapseTwoTwo') - const collapsedElements = { - one: false, - two: false - } - - function firstTest() { - expect(collapseOneOne.classList.contains('show')).toEqual(true) - expect(collapseOneTwo.classList.contains('show')).toEqual(true) - - expect(collapseTwoOne.classList.contains('show')).toEqual(false) - expect(collapseTwoTwo.classList.contains('show')).toEqual(false) - - triggerTwo.click() - } - - function secondTest() { - expect(collapseOneOne.classList.contains('show')).toEqual(false) - expect(collapseOneTwo.classList.contains('show')).toEqual(false) - - expect(collapseTwoOne.classList.contains('show')).toEqual(true) - expect(collapseTwoTwo.classList.contains('show')).toEqual(true) - done() - } - - collapseOneOne.addEventListener('shown.bs.collapse', () => { - if (collapsedElements.one) { - firstTest() - } else { - collapsedElements.one = true - } - }) - - collapseOneTwo.addEventListener('shown.bs.collapse', () => { - if (collapsedElements.one) { - firstTest() - } else { - collapsedElements.one = true - } - }) - - collapseTwoOne.addEventListener('shown.bs.collapse', () => { - if (collapsedElements.two) { - secondTest() - } else { - collapsedElements.two = true - } - }) - - collapseTwoTwo.addEventListener('shown.bs.collapse', () => { - if (collapsedElements.two) { - secondTest() - } else { - collapsedElements.two = true - } - }) - - trigger.click() - }) - - it('should collapse accordion children but not nested accordion children', done => { - fixtureEl.innerHTML = [ - '<div id="accordion">', - ' <div class="item">', - ' <a id="linkTrigger" data-toggle="collapse" href="#collapseOne" aria-expanded="false" aria-controls="collapseOne"></a>', - ' <div id="collapseOne" data-parent="#accordion" class="collapse" role="tabpanel" aria-labelledby="headingThree">', - ' <div id="nestedAccordion">', - ' <div class="item">', - ' <a id="nestedLinkTrigger" data-toggle="collapse" href="#nestedCollapseOne" aria-expanded="false" aria-controls="nestedCollapseOne"></a>', - ' <div id="nestedCollapseOne" data-parent="#nestedAccordion" class="collapse" role="tabpanel" aria-labelledby="headingThree"></div>', - ' </div>', - ' </div>', - ' </div>', - ' </div>', - ' <div class="item">', - ' <a id="linkTriggerTwo" data-toggle="collapse" href="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo"></a>', - ' <div id="collapseTwo" data-parent="#accordion" class="collapse show" role="tabpanel" aria-labelledby="headingTwo"></div>', - ' </div>', - '</div>' - ].join('') - - const trigger = fixtureEl.querySelector('#linkTrigger') - const triggerTwo = fixtureEl.querySelector('#linkTriggerTwo') - const nestedTrigger = fixtureEl.querySelector('#nestedLinkTrigger') - const collapseOne = fixtureEl.querySelector('#collapseOne') - const collapseTwo = fixtureEl.querySelector('#collapseTwo') - const nestedCollapseOne = fixtureEl.querySelector('#nestedCollapseOne') - - function handlerCollapseOne() { - expect(collapseOne.classList.contains('show')).toEqual(true) - expect(collapseTwo.classList.contains('show')).toEqual(false) - expect(nestedCollapseOne.classList.contains('show')).toEqual(false) - - nestedCollapseOne.addEventListener('shown.bs.collapse', handlerNestedCollapseOne) - nestedTrigger.click() - collapseOne.removeEventListener('shown.bs.collapse', handlerCollapseOne) - } - - function handlerNestedCollapseOne() { - expect(collapseOne.classList.contains('show')).toEqual(true) - expect(collapseTwo.classList.contains('show')).toEqual(false) - expect(nestedCollapseOne.classList.contains('show')).toEqual(true) - - collapseTwo.addEventListener('shown.bs.collapse', () => { - expect(collapseOne.classList.contains('show')).toEqual(false) - expect(collapseTwo.classList.contains('show')).toEqual(true) - expect(nestedCollapseOne.classList.contains('show')).toEqual(true) - done() - }) - - triggerTwo.click() - nestedCollapseOne.removeEventListener('shown.bs.collapse', handlerNestedCollapseOne) - } - - collapseOne.addEventListener('shown.bs.collapse', handlerCollapseOne) - trigger.click() - }) - - it('should add "collapsed" class and set aria-expanded to triggers only when all the targeted collapse are hidden', done => { - fixtureEl.innerHTML = [ - '<a id="trigger1" role="button" data-toggle="collapse" href="#test1"/>', - '<a id="trigger2" role="button" data-toggle="collapse" href="#test2"/>', - '<a id="trigger3" role="button" data-toggle="collapse" href=".multi"/>', - '<div id="test1" class="multi"/>', - '<div id="test2" class="multi"/>' - ].join('') - - const trigger1 = fixtureEl.querySelector('#trigger1') - const trigger2 = fixtureEl.querySelector('#trigger2') - const trigger3 = fixtureEl.querySelector('#trigger3') - const target1 = fixtureEl.querySelector('#test1') - const target2 = fixtureEl.querySelector('#test2') - - const target2Shown = () => { - expect(trigger1.classList.contains('collapsed')).toEqual(false) - expect(trigger1.getAttribute('aria-expanded')).toEqual('true') - - expect(trigger2.classList.contains('collapsed')).toEqual(false) - expect(trigger2.getAttribute('aria-expanded')).toEqual('true') - - expect(trigger3.classList.contains('collapsed')).toEqual(false) - expect(trigger3.getAttribute('aria-expanded')).toEqual('true') - - target2.addEventListener('hidden.bs.collapse', () => { - expect(trigger1.classList.contains('collapsed')).toEqual(false) - expect(trigger1.getAttribute('aria-expanded')).toEqual('true') - - expect(trigger2.classList.contains('collapsed')).toEqual(true) - expect(trigger2.getAttribute('aria-expanded')).toEqual('false') - - expect(trigger3.classList.contains('collapsed')).toEqual(false) - expect(trigger3.getAttribute('aria-expanded')).toEqual('true') - - target1.addEventListener('hidden.bs.collapse', () => { - expect(trigger1.classList.contains('collapsed')).toEqual(true) - expect(trigger1.getAttribute('aria-expanded')).toEqual('false') - - expect(trigger2.classList.contains('collapsed')).toEqual(true) - expect(trigger2.getAttribute('aria-expanded')).toEqual('false') - - expect(trigger3.classList.contains('collapsed')).toEqual(true) - expect(trigger3.getAttribute('aria-expanded')).toEqual('false') - done() - }) - - trigger1.click() - }) - - trigger2.click() - } - - target2.addEventListener('shown.bs.collapse', target2Shown) - trigger3.click() - }) - }) - - describe('jQueryInterface', () => { - it('should create a collapse', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - - jQueryMock.fn.collapse = Collapse.jQueryInterface - jQueryMock.elements = [div] - - jQueryMock.fn.collapse.call(jQueryMock) - - expect(Collapse.getInstance(div)).toBeDefined() - }) - - it('should not re create a collapse', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - const collapse = new Collapse(div) - - jQueryMock.fn.collapse = Collapse.jQueryInterface - jQueryMock.elements = [div] - - jQueryMock.fn.collapse.call(jQueryMock) - - expect(Collapse.getInstance(div)).toEqual(collapse) - }) - - it('should throw error on undefined method', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - const action = 'undefinedMethod' - - jQueryMock.fn.collapse = Collapse.jQueryInterface - jQueryMock.elements = [div] - - try { - jQueryMock.fn.collapse.call(jQueryMock, action) - } catch (error) { - expect(error.message).toEqual(`No method named "${action}"`) - } - }) - }) - - describe('getInstance', () => { - it('should return collapse instance', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - const collapse = new Collapse(div) - - expect(Collapse.getInstance(div)).toEqual(collapse) - }) - - it('should return null when there is no collapse instance', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - - expect(Collapse.getInstance(div)).toEqual(null) - }) - }) -}) |
