From 233f3fb1ce766d73273276e59a75ccec08673573 Mon Sep 17 00:00:00 2001 From: Johann-S Date: Mon, 22 Jul 2019 15:24:17 +0200 Subject: rewrite tab unit tests --- js/src/tab.js | 267 ---------------------- js/src/tab/tab.js | 267 ++++++++++++++++++++++ js/src/tab/tab.spec.js | 593 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 860 insertions(+), 267 deletions(-) delete mode 100644 js/src/tab.js create mode 100644 js/src/tab/tab.js create mode 100644 js/src/tab/tab.spec.js (limited to 'js/src') diff --git a/js/src/tab.js b/js/src/tab.js deleted file mode 100644 index b9db64baa..000000000 --- a/js/src/tab.js +++ /dev/null @@ -1,267 +0,0 @@ -/** - * -------------------------------------------------------------------------- - * Bootstrap (v4.3.1): tab.js - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * -------------------------------------------------------------------------- - */ - -import { - jQuery as $, - TRANSITION_END, - emulateTransitionEnd, - getSelectorFromElement, - getTransitionDurationFromElement, - makeArray, - reflow -} from './util/index' -import Data from './dom/data' -import EventHandler from './dom/event-handler' -import SelectorEngine from './dom/selector-engine' - -/** - * ------------------------------------------------------------------------ - * Constants - * ------------------------------------------------------------------------ - */ - -const NAME = 'tab' -const VERSION = '4.3.1' -const DATA_KEY = 'bs.tab' -const EVENT_KEY = `.${DATA_KEY}` -const DATA_API_KEY = '.data-api' - -const Event = { - HIDE: `hide${EVENT_KEY}`, - HIDDEN: `hidden${EVENT_KEY}`, - SHOW: `show${EVENT_KEY}`, - SHOWN: `shown${EVENT_KEY}`, - CLICK_DATA_API: `click${EVENT_KEY}${DATA_API_KEY}` -} - -const ClassName = { - DROPDOWN_MENU: 'dropdown-menu', - ACTIVE: 'active', - DISABLED: 'disabled', - FADE: 'fade', - SHOW: 'show' -} - -const Selector = { - DROPDOWN: '.dropdown', - NAV_LIST_GROUP: '.nav, .list-group', - ACTIVE: '.active', - ACTIVE_UL: ':scope > li > .active', - DATA_TOGGLE: '[data-toggle="tab"], [data-toggle="pill"], [data-toggle="list"]', - DROPDOWN_TOGGLE: '.dropdown-toggle', - DROPDOWN_ACTIVE_CHILD: ':scope > .dropdown-menu .active' -} - -/** - * ------------------------------------------------------------------------ - * Class Definition - * ------------------------------------------------------------------------ - */ - -class Tab { - constructor(element) { - this._element = element - - Data.setData(this._element, DATA_KEY, this) - } - - // Getters - - static get VERSION() { - return VERSION - } - - // Public - - show() { - if (this._element.parentNode && - this._element.parentNode.nodeType === Node.ELEMENT_NODE && - this._element.classList.contains(ClassName.ACTIVE) || - this._element.classList.contains(ClassName.DISABLED)) { - return - } - - let target - let previous - const listElement = SelectorEngine.closest(this._element, Selector.NAV_LIST_GROUP) - const selector = getSelectorFromElement(this._element) - - if (listElement) { - const itemSelector = listElement.nodeName === 'UL' || listElement.nodeName === 'OL' ? Selector.ACTIVE_UL : Selector.ACTIVE - previous = makeArray(SelectorEngine.find(itemSelector, listElement)) - previous = previous[previous.length - 1] - } - - let hideEvent = null - - if (previous) { - hideEvent = EventHandler.trigger(previous, Event.HIDE, { - relatedTarget: this._element - }) - } - - const showEvent = EventHandler.trigger(this._element, Event.SHOW, { - relatedTarget: previous - }) - - if (showEvent.defaultPrevented || - hideEvent !== null && hideEvent.defaultPrevented) { - return - } - - if (selector) { - target = SelectorEngine.findOne(selector) - } - - this._activate( - this._element, - listElement - ) - - const complete = () => { - EventHandler.trigger(previous, Event.HIDDEN, { - relatedTarget: this._element - }) - EventHandler.trigger(this._element, Event.SHOWN, { - relatedTarget: previous - }) - } - - if (target) { - this._activate(target, target.parentNode, complete) - } else { - complete() - } - } - - dispose() { - Data.removeData(this._element, DATA_KEY) - this._element = null - } - - // Private - - _activate(element, container, callback) { - const activeElements = container && (container.nodeName === 'UL' || container.nodeName === 'OL') ? - SelectorEngine.find(Selector.ACTIVE_UL, container) : - SelectorEngine.children(container, Selector.ACTIVE) - - const active = activeElements[0] - const isTransitioning = callback && - (active && active.classList.contains(ClassName.FADE)) - - const complete = () => this._transitionComplete( - element, - active, - callback - ) - - if (active && isTransitioning) { - const transitionDuration = getTransitionDurationFromElement(active) - active.classList.remove(ClassName.SHOW) - - EventHandler.one(active, TRANSITION_END, complete) - emulateTransitionEnd(active, transitionDuration) - } else { - complete() - } - } - - _transitionComplete(element, active, callback) { - if (active) { - active.classList.remove(ClassName.ACTIVE) - - const dropdownChild = SelectorEngine.findOne(Selector.DROPDOWN_ACTIVE_CHILD, active.parentNode) - - if (dropdownChild) { - dropdownChild.classList.remove(ClassName.ACTIVE) - } - - if (active.getAttribute('role') === 'tab') { - active.setAttribute('aria-selected', false) - } - } - - element.classList.add(ClassName.ACTIVE) - if (element.getAttribute('role') === 'tab') { - element.setAttribute('aria-selected', true) - } - - reflow(element) - - if (element.classList.contains(ClassName.FADE)) { - element.classList.add(ClassName.SHOW) - } - - if (element.parentNode && element.parentNode.classList.contains(ClassName.DROPDOWN_MENU)) { - const dropdownElement = SelectorEngine.closest(element, Selector.DROPDOWN) - - if (dropdownElement) { - makeArray(SelectorEngine.find(Selector.DROPDOWN_TOGGLE)) - .forEach(dropdown => dropdown.classList.add(ClassName.ACTIVE)) - } - - element.setAttribute('aria-expanded', true) - } - - if (callback) { - callback() - } - } - - // Static - - static _jQueryInterface(config) { - return this.each(function () { - const data = Data.getData(this, DATA_KEY) || new Tab(this) - - if (typeof config === 'string') { - if (typeof data[config] === 'undefined') { - throw new TypeError(`No method named "${config}"`) - } - - data[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) { - event.preventDefault() - - const data = Data.getData(this, DATA_KEY) || new Tab(this) - data.show() -}) - -/** - * ------------------------------------------------------------------------ - * jQuery - * ------------------------------------------------------------------------ - * add .tab to jQuery only if jQuery is present - */ - -if (typeof $ !== 'undefined') { - const JQUERY_NO_CONFLICT = $.fn[NAME] - $.fn[NAME] = Tab._jQueryInterface - $.fn[NAME].Constructor = Tab - $.fn[NAME].noConflict = () => { - $.fn[NAME] = JQUERY_NO_CONFLICT - return Tab._jQueryInterface - } -} - -export default Tab diff --git a/js/src/tab/tab.js b/js/src/tab/tab.js new file mode 100644 index 000000000..e882ff1d2 --- /dev/null +++ b/js/src/tab/tab.js @@ -0,0 +1,267 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap (v4.3.1): tab.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + +import { + jQuery as $, + TRANSITION_END, + emulateTransitionEnd, + getSelectorFromElement, + getTransitionDurationFromElement, + makeArray, + reflow +} from '../util/index' +import Data from '../dom/data' +import EventHandler from '../dom/event-handler' +import SelectorEngine from '../dom/selector-engine' + +/** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + +const NAME = 'tab' +const VERSION = '4.3.1' +const DATA_KEY = 'bs.tab' +const EVENT_KEY = `.${DATA_KEY}` +const DATA_API_KEY = '.data-api' + +const Event = { + HIDE: `hide${EVENT_KEY}`, + HIDDEN: `hidden${EVENT_KEY}`, + SHOW: `show${EVENT_KEY}`, + SHOWN: `shown${EVENT_KEY}`, + CLICK_DATA_API: `click${EVENT_KEY}${DATA_API_KEY}` +} + +const ClassName = { + DROPDOWN_MENU: 'dropdown-menu', + ACTIVE: 'active', + DISABLED: 'disabled', + FADE: 'fade', + SHOW: 'show' +} + +const Selector = { + DROPDOWN: '.dropdown', + NAV_LIST_GROUP: '.nav, .list-group', + ACTIVE: '.active', + ACTIVE_UL: ':scope > li > .active', + DATA_TOGGLE: '[data-toggle="tab"], [data-toggle="pill"], [data-toggle="list"]', + DROPDOWN_TOGGLE: '.dropdown-toggle', + DROPDOWN_ACTIVE_CHILD: ':scope > .dropdown-menu .active' +} + +/** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + +class Tab { + constructor(element) { + this._element = element + + Data.setData(this._element, DATA_KEY, this) + } + + // Getters + + static get VERSION() { + return VERSION + } + + // Public + + show() { + if (this._element.parentNode && + this._element.parentNode.nodeType === Node.ELEMENT_NODE && + this._element.classList.contains(ClassName.ACTIVE) || + this._element.classList.contains(ClassName.DISABLED)) { + return + } + + let target + let previous + const listElement = SelectorEngine.closest(this._element, Selector.NAV_LIST_GROUP) + const selector = getSelectorFromElement(this._element) + + if (listElement) { + const itemSelector = listElement.nodeName === 'UL' || listElement.nodeName === 'OL' ? Selector.ACTIVE_UL : Selector.ACTIVE + previous = makeArray(SelectorEngine.find(itemSelector, listElement)) + previous = previous[previous.length - 1] + } + + let hideEvent = null + + if (previous) { + hideEvent = EventHandler.trigger(previous, Event.HIDE, { + relatedTarget: this._element + }) + } + + const showEvent = EventHandler.trigger(this._element, Event.SHOW, { + relatedTarget: previous + }) + + if (showEvent.defaultPrevented || + hideEvent !== null && hideEvent.defaultPrevented) { + return + } + + if (selector) { + target = SelectorEngine.findOne(selector) + } + + this._activate( + this._element, + listElement + ) + + const complete = () => { + EventHandler.trigger(previous, Event.HIDDEN, { + relatedTarget: this._element + }) + EventHandler.trigger(this._element, Event.SHOWN, { + relatedTarget: previous + }) + } + + if (target) { + this._activate(target, target.parentNode, complete) + } else { + complete() + } + } + + dispose() { + Data.removeData(this._element, DATA_KEY) + this._element = null + } + + // Private + + _activate(element, container, callback) { + const activeElements = container && (container.nodeName === 'UL' || container.nodeName === 'OL') ? + SelectorEngine.find(Selector.ACTIVE_UL, container) : + SelectorEngine.children(container, Selector.ACTIVE) + + const active = activeElements[0] + const isTransitioning = callback && + (active && active.classList.contains(ClassName.FADE)) + + const complete = () => this._transitionComplete( + element, + active, + callback + ) + + if (active && isTransitioning) { + const transitionDuration = getTransitionDurationFromElement(active) + active.classList.remove(ClassName.SHOW) + + EventHandler.one(active, TRANSITION_END, complete) + emulateTransitionEnd(active, transitionDuration) + } else { + complete() + } + } + + _transitionComplete(element, active, callback) { + if (active) { + active.classList.remove(ClassName.ACTIVE) + + const dropdownChild = SelectorEngine.findOne(Selector.DROPDOWN_ACTIVE_CHILD, active.parentNode) + + if (dropdownChild) { + dropdownChild.classList.remove(ClassName.ACTIVE) + } + + if (active.getAttribute('role') === 'tab') { + active.setAttribute('aria-selected', false) + } + } + + element.classList.add(ClassName.ACTIVE) + if (element.getAttribute('role') === 'tab') { + element.setAttribute('aria-selected', true) + } + + reflow(element) + + if (element.classList.contains(ClassName.FADE)) { + element.classList.add(ClassName.SHOW) + } + + if (element.parentNode && element.parentNode.classList.contains(ClassName.DROPDOWN_MENU)) { + const dropdownElement = SelectorEngine.closest(element, Selector.DROPDOWN) + + if (dropdownElement) { + makeArray(SelectorEngine.find(Selector.DROPDOWN_TOGGLE)) + .forEach(dropdown => dropdown.classList.add(ClassName.ACTIVE)) + } + + element.setAttribute('aria-expanded', true) + } + + if (callback) { + callback() + } + } + + // Static + + static _jQueryInterface(config) { + return this.each(function () { + const data = Data.getData(this, DATA_KEY) || new Tab(this) + + if (typeof config === 'string') { + if (typeof data[config] === 'undefined') { + throw new TypeError(`No method named "${config}"`) + } + + data[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) { + event.preventDefault() + + const data = Data.getData(this, DATA_KEY) || new Tab(this) + data.show() +}) + +/** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + * add .tab to jQuery only if jQuery is present + */ +/* istanbul ignore if */ +if (typeof $ !== 'undefined') { + const JQUERY_NO_CONFLICT = $.fn[NAME] + $.fn[NAME] = Tab._jQueryInterface + $.fn[NAME].Constructor = Tab + $.fn[NAME].noConflict = () => { + $.fn[NAME] = JQUERY_NO_CONFLICT + return Tab._jQueryInterface + } +} + +export default Tab diff --git a/js/src/tab/tab.spec.js b/js/src/tab/tab.spec.js new file mode 100644 index 000000000..3fae366d1 --- /dev/null +++ b/js/src/tab/tab.spec.js @@ -0,0 +1,593 @@ +import Tab from './tab' + +/** Test helpers */ +import { getFixture, clearFixture, jQueryMock } from '../../tests/helpers/fixture' + +describe('Tab', () => { + let fixtureEl + + beforeAll(() => { + fixtureEl = getFixture() + }) + + afterEach(() => { + clearFixture() + }) + + describe('VERSION', () => { + it('should return plugin version', () => { + expect(Tab.VERSION).toEqual(jasmine.any(String)) + }) + }) + + describe('show', () => { + it('should activate element by tab id', done => { + fixtureEl.innerHTML = [ + '', + '' + ].join('') + + const profileTriggerEl = fixtureEl.querySelector('#triggerProfile') + const tab = new Tab(profileTriggerEl) + + profileTriggerEl.addEventListener('shown.bs.tab', () => { + expect(fixtureEl.querySelector('#profile').classList.contains('active')).toEqual(true) + expect(profileTriggerEl.getAttribute('aria-selected')).toEqual('true') + done() + }) + + tab.show() + }) + + it('should activate element by tab id in ordered list', done => { + fixtureEl.innerHTML = [ + '', + '
' + ].join('') + + const profileTriggerEl = fixtureEl.querySelector('#triggerProfile') + const tab = new Tab(profileTriggerEl) + + profileTriggerEl.addEventListener('shown.bs.tab', () => { + expect(fixtureEl.querySelector('#profile').classList.contains('active')).toEqual(true) + done() + }) + + tab.show() + }) + + it('should activate element by tab id in nav list', done => { + fixtureEl.innerHTML = [ + '', + '' + ].join('') + + const profileTriggerEl = fixtureEl.querySelector('#triggerProfile') + const tab = new Tab(profileTriggerEl) + + profileTriggerEl.addEventListener('shown.bs.tab', () => { + expect(fixtureEl.querySelector('#profile').classList.contains('active')).toEqual(true) + done() + }) + + tab.show() + }) + + it('should activate element by tab id in list group', done => { + fixtureEl.innerHTML = [ + '
', + ' Home', + ' Profile', + '
', + '' + ].join('') + + const profileTriggerEl = fixtureEl.querySelector('#triggerProfile') + const tab = new Tab(profileTriggerEl) + + profileTriggerEl.addEventListener('shown.bs.tab', () => { + expect(fixtureEl.querySelector('#profile').classList.contains('active')).toEqual(true) + done() + }) + + tab.show() + }) + + it('should not fire shown when show is prevented', done => { + fixtureEl.innerHTML = '' + + const navEl = fixtureEl.querySelector('div') + const tab = new Tab(navEl) + const expectDone = () => { + setTimeout(() => { + expect().nothing() + done() + }, 30) + } + + navEl.addEventListener('show.bs.tab', ev => { + ev.preventDefault() + expectDone() + }) + + navEl.addEventListener('shown.bs.tab', () => { + throw new Error('should not trigger shown event') + }) + + tab.show() + }) + + it('should not fire shown when tab is already active', done => { + fixtureEl.innerHTML = [ + '', + '
', + '
', + '
', + '
' + ].join('') + + const triggerActive = fixtureEl.querySelector('a.active') + const tab = new Tab(triggerActive) + + triggerActive.addEventListener('shown.bs.tab', () => { + throw new Error('should not trigger shown event') + }) + + tab.show() + setTimeout(() => { + expect().nothing() + done() + }, 30) + }) + + it('should not fire shown when tab is disabled', done => { + fixtureEl.innerHTML = [ + '', + '
', + '
', + '
', + '
' + ].join('') + + const triggerDisabled = fixtureEl.querySelector('a.disabled') + const tab = new Tab(triggerDisabled) + + triggerDisabled.addEventListener('shown.bs.tab', () => { + throw new Error('should not trigger shown event') + }) + + tab.show() + setTimeout(() => { + expect().nothing() + done() + }, 30) + }) + + it('show and shown events should reference correct relatedTarget', done => { + fixtureEl.innerHTML = [ + '', + '
', + '
', + '
', + '
' + ].join('') + + const secondTabTrigger = fixtureEl.querySelector('#triggerProfile') + const secondTab = new Tab(secondTabTrigger) + + secondTabTrigger.addEventListener('show.bs.tab', ev => { + expect(ev.relatedTarget.hash).toEqual('#home') + }) + + secondTabTrigger.addEventListener('shown.bs.tab', ev => { + expect(ev.relatedTarget.hash).toEqual('#home') + expect(secondTabTrigger.getAttribute('aria-selected')).toEqual('true') + expect(fixtureEl.querySelector('a:not(.active)').getAttribute('aria-selected')).toEqual('false') + done() + }) + + secondTab.show() + }) + + it('should fire hide and hidden events', done => { + fixtureEl.innerHTML = [ + '' + ].join('') + + const triggerList = fixtureEl.querySelectorAll('a') + const firstTab = new Tab(triggerList[0]) + const secondTab = new Tab(triggerList[1]) + + let hideCalled = false + triggerList[0].addEventListener('shown.bs.tab', () => { + secondTab.show() + }) + + triggerList[0].addEventListener('hide.bs.tab', ev => { + hideCalled = true + expect(ev.relatedTarget.hash).toEqual('#profile') + }) + + triggerList[0].addEventListener('hidden.bs.tab', ev => { + expect(hideCalled).toEqual(true) + expect(ev.relatedTarget.hash).toEqual('#profile') + done() + }) + + firstTab.show() + }) + + it('should not fire hidden when hide is prevented', done => { + fixtureEl.innerHTML = [ + '' + ].join('') + + const triggerList = fixtureEl.querySelectorAll('a') + const firstTab = new Tab(triggerList[0]) + const secondTab = new Tab(triggerList[1]) + const expectDone = () => { + setTimeout(() => { + expect().nothing() + done() + }, 30) + } + + triggerList[0].addEventListener('shown.bs.tab', () => { + secondTab.show() + }) + + triggerList[0].addEventListener('hide.bs.tab', ev => { + ev.preventDefault() + expectDone() + }) + + triggerList[0].addEventListener('hidden.bs.tab', () => { + throw new Error('should not trigger hidden') + }) + + firstTab.show() + }) + + it('should handle removed tabs', done => { + fixtureEl.innerHTML = [ + '', + '
', + '
test 1
', + '
test 2
', + '
test 3
', + '
' + ].join('') + + const secondNavEl = fixtureEl.querySelector('#secondNav') + const btnCloseEl = fixtureEl.querySelector('#btnClose') + const secondNavTab = new Tab(secondNavEl) + + secondNavEl.addEventListener('shown.bs.tab', () => { + expect(fixtureEl.querySelectorAll('.nav-tab').length).toEqual(2) + done() + }) + + btnCloseEl.addEventListener('click', () => { + const linkEl = btnCloseEl.parentNode + const liEl = linkEl.parentNode + const tabId = linkEl.getAttribute('href') + const tabIdEl = fixtureEl.querySelector(tabId) + + liEl.parentNode.removeChild(liEl) + tabIdEl.parentNode.removeChild(tabIdEl) + secondNavTab.show() + }) + + btnCloseEl.click() + }) + }) + + describe('dispose', () => { + it('should dispose a tab', () => { + fixtureEl.innerHTML = '
' + + const el = fixtureEl.querySelector('div') + const tab = new Tab(fixtureEl.querySelector('div')) + + expect(Tab._getInstance(el)).not.toBeNull() + + tab.dispose() + + expect(Tab._getInstance(el)).toBeNull() + }) + }) + + describe('_jQueryInterface', () => { + it('should create a tab', () => { + fixtureEl.innerHTML = '
' + + const div = fixtureEl.querySelector('div') + + jQueryMock.fn.tab = Tab._jQueryInterface + jQueryMock.elements = [div] + + jQueryMock.fn.tab.call(jQueryMock) + + expect(Tab._getInstance(div)).toBeDefined() + }) + + it('should not re create a tab', () => { + fixtureEl.innerHTML = '
' + + const div = fixtureEl.querySelector('div') + const tab = new Tab(div) + + jQueryMock.fn.tab = Tab._jQueryInterface + jQueryMock.elements = [div] + + jQueryMock.fn.tab.call(jQueryMock) + + expect(Tab._getInstance(div)).toEqual(tab) + }) + + it('should call a tab method', () => { + fixtureEl.innerHTML = '
' + + const div = fixtureEl.querySelector('div') + const tab = new Tab(div) + + spyOn(tab, 'show') + + jQueryMock.fn.tab = Tab._jQueryInterface + jQueryMock.elements = [div] + + jQueryMock.fn.tab.call(jQueryMock, 'show') + + expect(Tab._getInstance(div)).toEqual(tab) + expect(tab.show).toHaveBeenCalled() + }) + + it('should throw error on undefined method', () => { + fixtureEl.innerHTML = '
' + + const div = fixtureEl.querySelector('div') + const action = 'undefinedMethod' + + jQueryMock.fn.tab = Tab._jQueryInterface + jQueryMock.elements = [div] + + try { + jQueryMock.fn.tab.call(jQueryMock, action) + } catch (error) { + expect(error.message).toEqual(`No method named "${action}"`) + } + }) + }) + + describe('_getInstance', () => { + it('should return null if there is no instance', () => { + expect(Tab._getInstance(fixtureEl)).toEqual(null) + }) + + it('should return this instance', () => { + fixtureEl.innerHTML = '
' + + const divEl = fixtureEl.querySelector('div') + const tab = new Tab(divEl) + + expect(Tab._getInstance(divEl)).toEqual(tab) + }) + }) + + describe('data-api', () => { + it('should create dynamicaly a tab', done => { + fixtureEl.innerHTML = [ + '', + '
', + '
', + '
', + '
' + ].join('') + + const secondTabTrigger = fixtureEl.querySelector('#triggerProfile') + + secondTabTrigger.addEventListener('shown.bs.tab', () => { + expect(secondTabTrigger.classList.contains('active')).toEqual(true) + expect(fixtureEl.querySelector('#profile').classList.contains('active')).toEqual(true) + done() + }) + + secondTabTrigger.click() + }) + + it('selected tab should deactivate previous selected link in dropdown', () => { + fixtureEl.innerHTML = [ + '' + ].join('') + + const firstLiLinkEl = fixtureEl.querySelector('li:first-child a') + + firstLiLinkEl.click() + expect(firstLiLinkEl.classList.contains('active')).toEqual(true) + expect(fixtureEl.querySelector('li:last-child a').classList.contains('active')).toEqual(false) + expect(fixtureEl.querySelector('li:last-child .dropdown-menu a:first-child').classList.contains('active')).toEqual(false) + }) + + it('should handle nested tabs', done => { + fixtureEl.innerHTML = [ + '', + '
', + '
', + ' ', + '
', + '
Nested Tab1 Content
', + '
Nested Tab2 Content
', + '
', + '
', + '
Tab2 Content
', + '
Tab3 Content
', + '
' + ].join('') + + const tab1El = fixtureEl.querySelector('#tab1') + const tabNested2El = fixtureEl.querySelector('#tabNested2') + const xTab1El = fixtureEl.querySelector('#x-tab1') + + tabNested2El.addEventListener('shown.bs.tab', () => { + expect(xTab1El.classList.contains('active')).toEqual(true) + done() + }) + + tab1El.addEventListener('shown.bs.tab', () => { + expect(xTab1El.classList.contains('active')).toEqual(true) + tabNested2El.click() + }) + + tab1El.click() + }) + + it('should not remove fade class if no active pane is present', done => { + fixtureEl.innerHTML = [ + '', + '
', + '
', + '
', + '
' + ].join('') + + const triggerTabProfileEl = fixtureEl.querySelector('#tab-profile') + const triggerTabHomeEl = fixtureEl.querySelector('#tab-home') + const tabProfileEl = fixtureEl.querySelector('#profile') + const tabHomeEl = fixtureEl.querySelector('#home') + + triggerTabProfileEl.addEventListener('shown.bs.tab', () => { + expect(tabProfileEl.classList.contains('fade')).toEqual(true) + expect(tabProfileEl.classList.contains('show')).toEqual(true) + + triggerTabHomeEl.addEventListener('shown.bs.tab', () => { + expect(tabProfileEl.classList.contains('fade')).toEqual(true) + expect(tabProfileEl.classList.contains('show')).toEqual(false) + + expect(tabHomeEl.classList.contains('fade')).toEqual(true) + expect(tabHomeEl.classList.contains('show')).toEqual(true) + + done() + }) + + triggerTabHomeEl.click() + }) + + triggerTabProfileEl.click() + }) + + it('should not add show class to tab panes if there is no `.fade` class', done => { + fixtureEl.innerHTML = [ + '', + '
', + '
test 1
', + '
test 2
', + '
' + ].join('') + + const secondNavEl = fixtureEl.querySelector('#secondNav') + + secondNavEl.addEventListener('shown.bs.tab', () => { + expect(fixtureEl.querySelectorAll('.show').length).toEqual(0) + done() + }) + + secondNavEl.click() + }) + + it('should add show class to tab panes if there is a `.fade` class', done => { + fixtureEl.innerHTML = [ + '', + '
', + '
test 1
', + '
test 2
', + '
' + ].join('') + + const secondNavEl = fixtureEl.querySelector('#secondNav') + + secondNavEl.addEventListener('shown.bs.tab', () => { + expect(fixtureEl.querySelectorAll('.show').length).toEqual(1) + done() + }) + + secondNavEl.click() + }) + }) +}) -- cgit v1.2.3