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 | |
| 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')
| -rw-r--r-- | js/src/.eslintrc.json | 14 | ||||
| -rw-r--r-- | js/src/alert.js (renamed from js/src/alert/alert.js) | 8 | ||||
| -rw-r--r-- | js/src/alert/alert.spec.js | 173 | ||||
| -rw-r--r-- | js/src/button.js (renamed from js/src/button/button.js) | 8 | ||||
| -rw-r--r-- | js/src/button/button.spec.js | 292 | ||||
| -rw-r--r-- | js/src/carousel.js (renamed from js/src/carousel/carousel.js) | 10 | ||||
| -rw-r--r-- | js/src/carousel/carousel.spec.js | 1201 | ||||
| -rw-r--r-- | js/src/collapse.js (renamed from js/src/collapse/collapse.js) | 10 | ||||
| -rw-r--r-- | js/src/collapse/collapse.spec.js | 826 | ||||
| -rw-r--r-- | js/src/dom/data.spec.js | 131 | ||||
| -rw-r--r-- | js/src/dom/event-handler.spec.js | 327 | ||||
| -rw-r--r-- | js/src/dom/manipulator.spec.js | 158 | ||||
| -rw-r--r-- | js/src/dom/selector-engine.spec.js | 115 | ||||
| -rw-r--r-- | js/src/dropdown.js (renamed from js/src/dropdown/dropdown.js) | 10 | ||||
| -rw-r--r-- | js/src/dropdown/dropdown.spec.js | 1564 | ||||
| -rw-r--r-- | js/src/modal.js (renamed from js/src/modal/modal.js) | 10 | ||||
| -rw-r--r-- | js/src/modal/modal.spec.js | 987 | ||||
| -rw-r--r-- | js/src/popover.js (renamed from js/src/popover/popover.js) | 8 | ||||
| -rw-r--r-- | js/src/popover/popover.spec.js | 251 | ||||
| -rw-r--r-- | js/src/scrollspy.js (renamed from js/src/scrollspy/scrollspy.js) | 10 | ||||
| -rw-r--r-- | js/src/scrollspy/scrollspy.spec.js | 653 | ||||
| -rw-r--r-- | js/src/tab.js (renamed from js/src/tab/tab.js) | 8 | ||||
| -rw-r--r-- | js/src/tab/tab.spec.js | 593 | ||||
| -rw-r--r-- | js/src/toast.js (renamed from js/src/toast/toast.js) | 8 | ||||
| -rw-r--r-- | js/src/toast/toast.spec.js | 374 | ||||
| -rw-r--r-- | js/src/tooltip.js (renamed from js/src/tooltip/tooltip.js) | 12 | ||||
| -rw-r--r-- | js/src/tooltip/tooltip.spec.js | 1020 | ||||
| -rw-r--r-- | js/src/util/index.spec.js | 382 | ||||
| -rw-r--r-- | js/src/util/sanitizer.spec.js | 70 |
29 files changed, 51 insertions, 9182 deletions
diff --git a/js/src/.eslintrc.json b/js/src/.eslintrc.json deleted file mode 100644 index 18ffdc003..000000000 --- a/js/src/.eslintrc.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": [ - "../../.eslintrc.json" - ], - "overrides": [ - { - "files": ["**/*.spec.js"], - "env": { - "jasmine": true - } - } - ] -} diff --git a/js/src/alert/alert.js b/js/src/alert.js index 024528b81..dbd931b52 100644 --- a/js/src/alert/alert.js +++ b/js/src/alert.js @@ -11,10 +11,10 @@ import { emulateTransitionEnd, getElementFromSelector, getTransitionDurationFromElement -} from '../util/index' -import Data from '../dom/data' -import EventHandler from '../dom/event-handler' -import SelectorEngine from '../dom/selector-engine' +} from './util/index' +import Data from './dom/data' +import EventHandler from './dom/event-handler' +import SelectorEngine from './dom/selector-engine' /** * ------------------------------------------------------------------------ diff --git a/js/src/alert/alert.spec.js b/js/src/alert/alert.spec.js deleted file mode 100644 index 61d656bd0..000000000 --- a/js/src/alert/alert.spec.js +++ /dev/null @@ -1,173 +0,0 @@ -import Alert from './alert' -import { makeArray, getTransitionDurationFromElement } from '../util/index' - -/** Test helpers */ -import { getFixture, clearFixture, jQueryMock } from '../../tests/helpers/fixture' - -describe('Alert', () => { - let fixtureEl - - beforeAll(() => { - fixtureEl = getFixture() - }) - - afterEach(() => { - clearFixture() - }) - - it('should return version', () => { - expect(typeof Alert.VERSION).toEqual('string') - }) - - describe('data-api', () => { - it('should close an alert without instantiate it manually', () => { - fixtureEl.innerHTML = [ - '<div class="alert">', - ' <button type="button" data-dismiss="alert">x</button>', - '</div>' - ].join('') - - const button = document.querySelector('button') - - button.click() - expect(makeArray(document.querySelectorAll('.alert')).length).toEqual(0) - }) - - it('should close an alert without instantiate it manually with the parent selector', () => { - fixtureEl.innerHTML = [ - '<div class="alert">', - ' <button type="button" data-target=".alert" data-dismiss="alert">x</button>', - '</div>' - ].join('') - - const button = document.querySelector('button') - - button.click() - expect(makeArray(document.querySelectorAll('.alert')).length).toEqual(0) - }) - }) - - describe('close', () => { - it('should close an alert', done => { - const spy = jasmine.createSpy('spy', getTransitionDurationFromElement) - fixtureEl.innerHTML = '<div class="alert"></div>' - - const alertEl = document.querySelector('.alert') - const alert = new Alert(alertEl) - - alertEl.addEventListener('closed.bs.alert', () => { - expect(makeArray(document.querySelectorAll('.alert')).length).toEqual(0) - expect(spy).not.toHaveBeenCalled() - done() - }) - - alert.close() - }) - - it('should close alert with fade class', done => { - fixtureEl.innerHTML = '<div class="alert fade"></div>' - - const alertEl = document.querySelector('.alert') - const alert = new Alert(alertEl) - - alertEl.addEventListener('transitionend', () => { - expect().nothing() - }) - - alertEl.addEventListener('closed.bs.alert', () => { - expect(makeArray(document.querySelectorAll('.alert')).length).toEqual(0) - done() - }) - - alert.close() - }) - - it('should not remove alert if close event is prevented', done => { - fixtureEl.innerHTML = '<div class="alert"></div>' - - const alertEl = document.querySelector('.alert') - const alert = new Alert(alertEl) - - const endTest = () => { - setTimeout(() => { - expect(alert._removeElement).not.toHaveBeenCalled() - done() - }, 10) - } - - spyOn(alert, '_removeElement') - - alertEl.addEventListener('close.bs.alert', event => { - event.preventDefault() - endTest() - }) - - alertEl.addEventListener('closed.bs.alert', () => { - endTest() - }) - - alert.close() - }) - }) - - describe('dispose', () => { - it('should dispose an alert', () => { - fixtureEl.innerHTML = '<div class="alert"></div>' - - const alertEl = document.querySelector('.alert') - const alert = new Alert(alertEl) - - expect(Alert.getInstance(alertEl)).toBeDefined() - - alert.dispose() - - expect(Alert.getInstance(alertEl)).toBeNull() - }) - }) - - describe('jQueryInterface', () => { - it('should handle config passed and toggle existing alert', () => { - fixtureEl.innerHTML = '<div class="alert"></div>' - - const alertEl = fixtureEl.querySelector('.alert') - const alert = new Alert(alertEl) - - spyOn(alert, 'close') - - jQueryMock.fn.alert = Alert.jQueryInterface - jQueryMock.elements = [alertEl] - - jQueryMock.fn.alert.call(jQueryMock, 'close') - - expect(alert.close).toHaveBeenCalled() - }) - - it('should create new alert instance and call close', () => { - fixtureEl.innerHTML = '<div class="alert"></div>' - - const alertEl = fixtureEl.querySelector('.alert') - - jQueryMock.fn.alert = Alert.jQueryInterface - jQueryMock.elements = [alertEl] - - jQueryMock.fn.alert.call(jQueryMock, 'close') - - expect(Alert.getInstance(alertEl)).toBeDefined() - expect(fixtureEl.querySelector('.alert')).toBeNull() - }) - - it('should just create an alert instance without calling close', () => { - fixtureEl.innerHTML = '<div class="alert"></div>' - - const alertEl = fixtureEl.querySelector('.alert') - - jQueryMock.fn.alert = Alert.jQueryInterface - jQueryMock.elements = [alertEl] - - jQueryMock.fn.alert.call(jQueryMock) - - expect(Alert.getInstance(alertEl)).toBeDefined() - expect(fixtureEl.querySelector('.alert')).not.toBeNull() - }) - }) -}) diff --git a/js/src/button/button.js b/js/src/button.js index 4418ba6b8..6edd5cb64 100644 --- a/js/src/button/button.js +++ b/js/src/button.js @@ -5,10 +5,10 @@ * -------------------------------------------------------------------------- */ -import { getjQuery } from '../util/index' -import Data from '../dom/data' -import EventHandler from '../dom/event-handler' -import SelectorEngine from '../dom/selector-engine' +import { getjQuery } from './util/index' +import Data from './dom/data' +import EventHandler from './dom/event-handler' +import SelectorEngine from './dom/selector-engine' /** * ------------------------------------------------------------------------ diff --git a/js/src/button/button.spec.js b/js/src/button/button.spec.js deleted file mode 100644 index 622881185..000000000 --- a/js/src/button/button.spec.js +++ /dev/null @@ -1,292 +0,0 @@ -import Button from './button' -import EventHandler from '../dom/event-handler' - -/** Test helpers */ -import { - getFixture, - clearFixture, - createEvent, - jQueryMock -} from '../../tests/helpers/fixture' - -describe('Button', () => { - let fixtureEl - - beforeAll(() => { - fixtureEl = getFixture() - }) - - afterEach(() => { - clearFixture() - }) - - describe('VERSION', () => { - it('should return plugin version', () => { - expect(Button.VERSION).toEqual(jasmine.any(String)) - }) - }) - - describe('data-api', () => { - it('should toggle active class on click', () => { - fixtureEl.innerHTML = [ - '<button class="btn" data-toggle="button">btn</button>', - '<button class="btn testParent" data-toggle="button"><div class="test"></div></button>' - ].join('') - - const btn = fixtureEl.querySelector('.btn') - const divTest = fixtureEl.querySelector('.test') - const btnTestParent = fixtureEl.querySelector('.testParent') - - expect(btn.classList.contains('active')).toEqual(false) - - btn.click() - - expect(btn.classList.contains('active')).toEqual(true) - - btn.click() - - expect(btn.classList.contains('active')).toEqual(false) - - divTest.click() - - expect(btnTestParent.classList.contains('active')).toEqual(true) - }) - - it('should trigger input change event when toggled button has input field', done => { - fixtureEl.innerHTML = [ - '<div class="btn-group" data-toggle="buttons">', - ' <label class="btn btn-primary">', - ' <input type="radio" id="radio" autocomplete="off"> Radio', - ' </label>', - '</div>' - ].join('') - - const input = fixtureEl.querySelector('input') - const label = fixtureEl.querySelector('label') - - input.addEventListener('change', () => { - expect().nothing() - done() - }) - - label.click() - }) - - it('should not trigger input change event when input already checked and button is active', () => { - fixtureEl.innerHTML = [ - '<button type="button" class="btn btn-primary active" data-toggle="buttons">', - ' <input type="radio" id="radio" autocomplete="off" checked> Radio', - '</button>' - ].join('') - - const button = fixtureEl.querySelector('button') - - spyOn(EventHandler, 'trigger') - - button.click() - - expect(EventHandler.trigger).not.toHaveBeenCalled() - }) - - it('should remove active when an other radio button is clicked', () => { - fixtureEl.innerHTML = [ - '<div class="btn-group btn-group-toggle" data-toggle="buttons">', - ' <label class="btn btn-secondary active">', - ' <input type="radio" name="options" id="option1" autocomplete="off" checked> Active', - ' </label>', - ' <label class="btn btn-secondary">', - ' <input type="radio" name="options" id="option2" autocomplete="off"> Radio', - ' </label>', - ' <label class="btn btn-secondary">', - ' <input type="radio" name="options" id="option3" autocomplete="off"> Radio', - ' </label>', - '</div>' - ].join('') - - const option1 = fixtureEl.querySelector('#option1') - const option2 = fixtureEl.querySelector('#option2') - - expect(option1.checked).toEqual(true) - expect(option1.parentElement.classList.contains('active')).toEqual(true) - - const clickEvent = createEvent('click') - - option2.dispatchEvent(clickEvent) - - expect(option1.checked).toEqual(false) - expect(option1.parentElement.classList.contains('active')).toEqual(false) - expect(option2.checked).toEqual(true) - expect(option2.parentElement.classList.contains('active')).toEqual(true) - }) - - it('should do nothing if the child is not an input', () => { - fixtureEl.innerHTML = [ - '<div class="btn-group btn-group-toggle" data-toggle="buttons">', - ' <label class="btn btn-secondary active">', - ' <span id="option1">el 1</span>', - ' </label>', - ' <label class="btn btn-secondary">', - ' <span id="option2">el 2</span>', - ' </label>', - ' <label class="btn btn-secondary">', - ' <span>el 3</span>', - ' </label>', - '</div>' - ].join('') - - const option2 = fixtureEl.querySelector('#option2') - const clickEvent = createEvent('click') - - option2.dispatchEvent(clickEvent) - - expect().nothing() - }) - - it('should add focus class on focus event', () => { - fixtureEl.innerHTML = '<button class="btn" data-toggle="button"><input type="text" /></button>' - - const btn = fixtureEl.querySelector('.btn') - const input = fixtureEl.querySelector('input') - - const focusEvent = createEvent('focus') - input.dispatchEvent(focusEvent) - - expect(btn.classList.contains('focus')).toEqual(true) - }) - - it('should not add focus class', () => { - fixtureEl.innerHTML = '<button data-toggle="button"><input type="text" /></button>' - - const btn = fixtureEl.querySelector('button') - const input = fixtureEl.querySelector('input') - - const focusEvent = createEvent('focus') - input.dispatchEvent(focusEvent) - - expect(btn.classList.contains('focus')).toEqual(false) - }) - - it('should remove focus class on blur event', () => { - fixtureEl.innerHTML = '<button class="btn focus" data-toggle="button"><input type="text" /></button>' - - const btn = fixtureEl.querySelector('.btn') - const input = fixtureEl.querySelector('input') - - const focusEvent = createEvent('blur') - input.dispatchEvent(focusEvent) - - expect(btn.classList.contains('focus')).toEqual(false) - }) - - it('should not remove focus class on blur event', () => { - fixtureEl.innerHTML = '<button class="focus" data-toggle="button"><input type="text" /></button>' - - const btn = fixtureEl.querySelector('button') - const input = fixtureEl.querySelector('input') - - const focusEvent = createEvent('blur') - input.dispatchEvent(focusEvent) - - expect(btn.classList.contains('focus')).toEqual(true) - }) - }) - - describe('toggle', () => { - it('should toggle aria-pressed', () => { - fixtureEl.innerHTML = '<button class="btn" data-toggle="button" aria-pressed="false"></button>' - - const btnEl = fixtureEl.querySelector('.btn') - const button = new Button(btnEl) - - expect(btnEl.getAttribute('aria-pressed')).toEqual('false') - expect(btnEl.classList.contains('active')).toEqual(false) - - button.toggle() - - expect(btnEl.getAttribute('aria-pressed')).toEqual('true') - expect(btnEl.classList.contains('active')).toEqual(true) - }) - - it('should handle disabled attribute on non-button elements', () => { - fixtureEl.innerHTML = [ - '<div class="btn-group disabled" data-toggle="buttons" aria-disabled="true" disabled>', - ' <label class="btn btn-danger disabled" aria-disabled="true" disabled>', - ' <input type="checkbox" aria-disabled="true" autocomplete="off" disabled class="disabled"/>', - ' </label>', - '</div>' - ].join('') - - const btnGroupEl = fixtureEl.querySelector('.btn-group') - const btnDanger = fixtureEl.querySelector('.btn-danger') - const input = fixtureEl.querySelector('input') - - const button = new Button(btnGroupEl) - - button.toggle() - - expect(btnDanger.hasAttribute('disabled')).toEqual(true) - expect(input.checked).toEqual(false) - }) - }) - - describe('dispose', () => { - it('should dispose a button', () => { - fixtureEl.innerHTML = '<button class="btn" data-toggle="button"></button>' - - const btnEl = fixtureEl.querySelector('.btn') - const button = new Button(btnEl) - - expect(Button.getInstance(btnEl)).toBeDefined() - - button.dispose() - - expect(Button.getInstance(btnEl)).toBeNull() - }) - }) - - describe('jQueryInterface', () => { - it('should handle config passed and toggle existing button', () => { - fixtureEl.innerHTML = '<button class="btn" data-toggle="button"></button>' - - const btnEl = fixtureEl.querySelector('.btn') - const button = new Button(btnEl) - - spyOn(button, 'toggle') - - jQueryMock.fn.button = Button.jQueryInterface - jQueryMock.elements = [btnEl] - - jQueryMock.fn.button.call(jQueryMock, 'toggle') - - expect(button.toggle).toHaveBeenCalled() - }) - - it('should create new button instance and call toggle', () => { - fixtureEl.innerHTML = '<button class="btn" data-toggle="button"></button>' - - const btnEl = fixtureEl.querySelector('.btn') - - jQueryMock.fn.button = Button.jQueryInterface - jQueryMock.elements = [btnEl] - - jQueryMock.fn.button.call(jQueryMock, 'toggle') - - expect(Button.getInstance(btnEl)).toBeDefined() - expect(btnEl.classList.contains('active')).toEqual(true) - }) - - it('should just create a button instance without calling toggle', () => { - fixtureEl.innerHTML = '<button class="btn" data-toggle="button"></button>' - - const btnEl = fixtureEl.querySelector('.btn') - - jQueryMock.fn.button = Button.jQueryInterface - jQueryMock.elements = [btnEl] - - jQueryMock.fn.button.call(jQueryMock) - - expect(Button.getInstance(btnEl)).toBeDefined() - expect(btnEl.classList.contains('active')).toEqual(false) - }) - }) -}) diff --git a/js/src/carousel/carousel.js b/js/src/carousel.js index 723bf57c6..5034f1798 100644 --- a/js/src/carousel/carousel.js +++ b/js/src/carousel.js @@ -16,11 +16,11 @@ import { reflow, triggerTransitionEnd, typeCheckConfig -} from '../util/index' -import Data from '../dom/data' -import EventHandler from '../dom/event-handler' -import Manipulator from '../dom/manipulator' -import SelectorEngine from '../dom/selector-engine' +} from './util/index' +import Data from './dom/data' +import EventHandler from './dom/event-handler' +import Manipulator from './dom/manipulator' +import SelectorEngine from './dom/selector-engine' /** * ------------------------------------------------------------------------ diff --git a/js/src/carousel/carousel.spec.js b/js/src/carousel/carousel.spec.js deleted file mode 100644 index 4c13b6d22..000000000 --- a/js/src/carousel/carousel.spec.js +++ /dev/null @@ -1,1201 +0,0 @@ -import Carousel from './carousel' -import EventHandler from '../dom/event-handler' - -/** Test helpers */ -import { getFixture, clearFixture, createEvent, jQueryMock } from '../../tests/helpers/fixture' - -describe('Carousel', () => { - const { Simulator, PointerEvent, MSPointerEvent } = window - const originWinPointerEvent = PointerEvent || MSPointerEvent - const supportPointerEvent = Boolean(PointerEvent || MSPointerEvent) - - window.MSPointerEvent = null - const cssStyleCarousel = '.carousel.pointer-event { -ms-touch-action: none; touch-action: none; }' - - const stylesCarousel = document.createElement('style') - stylesCarousel.type = 'text/css' - stylesCarousel.appendChild(document.createTextNode(cssStyleCarousel)) - - const clearPointerEvents = () => { - window.PointerEvent = null - } - - const restorePointerEvents = () => { - window.PointerEvent = originWinPointerEvent - } - - let fixtureEl - - beforeAll(() => { - fixtureEl = getFixture() - }) - - afterEach(() => { - clearFixture() - }) - - describe('VERSION', () => { - it('should return plugin version', () => { - expect(Carousel.VERSION).toEqual(jasmine.any(String)) - }) - }) - - describe('Default', () => { - it('should return plugin default config', () => { - expect(Carousel.Default).toEqual(jasmine.any(Object)) - }) - }) - - describe('constructor', () => { - it('should go to next item if right arrow key is pressed', done => { - fixtureEl.innerHTML = [ - '<div id="myCarousel" class="carousel slide">', - ' <div class="carousel-inner">', - ' <div class="carousel-item active">item 1</div>', - ' <div id="item2" class="carousel-item">item 2</div>', - ' <div class="carousel-item">item 3</div>', - ' </div>', - '</div>' - ].join('') - - const carouselEl = fixtureEl.querySelector('#myCarousel') - const carousel = new Carousel(carouselEl, { - keyboard: true - }) - - spyOn(carousel, '_keydown').and.callThrough() - - carouselEl.addEventListener('slid.bs.carousel', () => { - expect(fixtureEl.querySelector('.active')).toEqual(fixtureEl.querySelector('#item2')) - expect(carousel._keydown).toHaveBeenCalled() - done() - }) - - const keyDown = createEvent('keydown') - keyDown.which = 39 - - carouselEl.dispatchEvent(keyDown) - }) - - it('should go to previous item if left arrow key is pressed', done => { - fixtureEl.innerHTML = [ - '<div id="myCarousel" class="carousel slide">', - ' <div class="carousel-inner">', - ' <div id="item1" class="carousel-item">item 1</div>', - ' <div class="carousel-item active">item 2</div>', - ' <div class="carousel-item">item 3</div>', - ' </div>', - '</div>' - ].join('') - - const carouselEl = fixtureEl.querySelector('#myCarousel') - const carousel = new Carousel(carouselEl, { - keyboard: true - }) - - spyOn(carousel, '_keydown').and.callThrough() - - carouselEl.addEventListener('slid.bs.carousel', () => { - expect(fixtureEl.querySelector('.active')).toEqual(fixtureEl.querySelector('#item1')) - expect(carousel._keydown).toHaveBeenCalled() - done() - }) - - const keyDown = createEvent('keydown') - keyDown.which = 37 - - carouselEl.dispatchEvent(keyDown) - }) - - it('should not prevent keydown if key is not ARROW_LEFT or ARROW_RIGHT', done => { - fixtureEl.innerHTML = [ - '<div id="myCarousel" class="carousel slide">', - ' <div class="carousel-inner">', - ' <div class="carousel-item active">item 1</div>', - ' <div class="carousel-item">item 2</div>', - ' <div class="carousel-item">item 3</div>', - ' </div>', - '</div>' - ].join('') - - const carouselEl = fixtureEl.querySelector('#myCarousel') - const carousel = new Carousel(carouselEl, { - keyboard: true - }) - - spyOn(carousel, '_keydown').and.callThrough() - - carouselEl.addEventListener('keydown', event => { - expect(carousel._keydown).toHaveBeenCalled() - expect(event.defaultPrevented).toEqual(false) - done() - }) - - const keyDown = createEvent('keydown') - keyDown.which = 40 - - carouselEl.dispatchEvent(keyDown) - }) - - it('should ignore keyboard events within <input>s and <textarea>s', () => { - fixtureEl.innerHTML = [ - '<div id="myCarousel" class="carousel slide">', - ' <div class="carousel-inner">', - ' <div class="carousel-item active">', - ' <input type="text" />', - ' <textarea></textarea>', - ' </div>', - ' <div class="carousel-item"></div>', - ' <div class="carousel-item">item 3</div>', - ' </div>', - '</div>' - ].join('') - - const carouselEl = fixtureEl.querySelector('#myCarousel') - const input = fixtureEl.querySelector('input') - const textarea = fixtureEl.querySelector('textarea') - const carousel = new Carousel(carouselEl, { - keyboard: true - }) - - const spyKeyDown = spyOn(carousel, '_keydown').and.callThrough() - const spyPrev = spyOn(carousel, 'prev') - const spyNext = spyOn(carousel, 'next') - - const keyDown = createEvent('keydown', { bubbles: true, cancelable: true }) - keyDown.which = 39 - Object.defineProperty(keyDown, 'target', { - value: input, - writable: true, - configurable: true - }) - - input.dispatchEvent(keyDown) - - expect(spyKeyDown).toHaveBeenCalled() - expect(spyPrev).not.toHaveBeenCalled() - expect(spyNext).not.toHaveBeenCalled() - - spyKeyDown.calls.reset() - spyPrev.calls.reset() - spyNext.calls.reset() - - Object.defineProperty(keyDown, 'target', { - value: textarea - }) - textarea.dispatchEvent(keyDown) - - expect(spyKeyDown).toHaveBeenCalled() - expect(spyPrev).not.toHaveBeenCalled() - expect(spyNext).not.toHaveBeenCalled() - }) - - it('should wrap around from end to start when wrap option is true', done => { - fixtureEl.innerHTML = [ - '<div id="myCarousel" class="carousel slide">', - ' <div class="carousel-inner">', - ' <div id="one" class="carousel-item active"></div>', - ' <div id="two" class="carousel-item"></div>', - ' <div id="three" class="carousel-item">item 3</div>', - ' </div>', - '</div>' - ].join('') - - const carouselEl = fixtureEl.querySelector('#myCarousel') - const carousel = new Carousel(carouselEl, { wrap: true }) - const getActiveId = () => { - return carouselEl.querySelector('.carousel-item.active').getAttribute('id') - } - - carouselEl.addEventListener('slid.bs.carousel', e => { - const activeId = getActiveId() - - if (activeId === 'two') { - carousel.next() - return - } - - if (activeId === 'three') { - carousel.next() - return - } - - if (activeId === 'one') { - // carousel wrapped around and slid from 3rd to 1st slide - expect(activeId).toEqual('one') - expect(e.from + 1).toEqual(3) - done() - } - }) - - carousel.next() - }) - - it('should stay at the start when the prev method is called and wrap is false', done => { - fixtureEl.innerHTML = [ - '<div id="myCarousel" class="carousel slide">', - ' <div class="carousel-inner">', - ' <div id="one" class="carousel-item active"></div>', - ' <div id="two" class="carousel-item"></div>', - ' <div id="three" class="carousel-item">item 3</div>', - ' </div>', - '</div>' - ].join('') - - const carouselEl = fixtureEl.querySelector('#myCarousel') - const firstElement = fixtureEl.querySelector('#one') - const carousel = new Carousel(carouselEl, { wrap: false }) - - carouselEl.addEventListener('slid.bs.carousel', () => { - throw new Error('carousel slid when it should not have slid') - }) - - carousel.prev() - - setTimeout(() => { - expect(firstElement.classList.contains('active')).toEqual(true) - done() - }, 10) - }) - - it('should not add touch event listeners if touch = false', () => { - fixtureEl.innerHTML = '<div></div>' - - const carouselEl = fixtureEl.querySelector('div') - - spyOn(Carousel.prototype, '_addTouchEventListeners') - - const carousel = new Carousel(carouselEl, { - touch: false - }) - - expect(carousel._addTouchEventListeners).not.toHaveBeenCalled() - }) - - it('should not add touch event listeners if touch supported = false', () => { - fixtureEl.innerHTML = '<div></div>' - - const carouselEl = fixtureEl.querySelector('div') - - const carousel = new Carousel(carouselEl) - - EventHandler.off(carouselEl, '.bs-carousel') - carousel._touchSupported = false - - spyOn(carousel, '_addTouchEventListeners') - - carousel._addEventListeners() - - expect(carousel._addTouchEventListeners).not.toHaveBeenCalled() - }) - - it('should add touch event listeners by default', () => { - fixtureEl.innerHTML = '<div></div>' - - const carouselEl = fixtureEl.querySelector('div') - - spyOn(Carousel.prototype, '_addTouchEventListeners') - - document.documentElement.ontouchstart = () => {} - const carousel = new Carousel(carouselEl) - - expect(carousel._addTouchEventListeners).toHaveBeenCalled() - }) - - it('should allow swiperight and call prev with pointer events', done => { - if (!supportPointerEvent) { - expect().nothing() - done() - return - } - - document.documentElement.ontouchstart = () => {} - document.head.appendChild(stylesCarousel) - Simulator.setType('pointer') - - fixtureEl.innerHTML = [ - '<div class="carousel" data-interval="false">', - ' <div class="carousel-inner">', - ' <div id="item" class="carousel-item">', - ' <img alt="">', - ' </div>', - ' <div class="carousel-item active">', - ' <img alt="">', - ' </div>', - ' </div>', - '</div>' - ].join('') - - const carouselEl = fixtureEl.querySelector('.carousel') - const item = fixtureEl.querySelector('#item') - const carousel = new Carousel(carouselEl) - - spyOn(carousel, 'prev').and.callThrough() - - carouselEl.addEventListener('slid.bs.carousel', () => { - expect(item.classList.contains('active')).toEqual(true) - expect(carousel.prev).toHaveBeenCalled() - document.head.removeChild(stylesCarousel) - delete document.documentElement.ontouchstart - done() - }) - - Simulator.gestures.swipe(carouselEl, { - deltaX: 300, - deltaY: 0 - }) - }) - - it('should allow swipeleft and call next with pointer events', done => { - if (!supportPointerEvent) { - expect().nothing() - done() - return - } - - document.documentElement.ontouchstart = () => {} - document.head.appendChild(stylesCarousel) - Simulator.setType('pointer') - - fixtureEl.innerHTML = [ - '<div class="carousel" data-interval="false">', - ' <div class="carousel-inner">', - ' <div id="item" class="carousel-item active">', - ' <img alt="">', - ' </div>', - ' <div class="carousel-item">', - ' <img alt="">', - ' </div>', - ' </div>', - '</div>' - ].join('') - - const carouselEl = fixtureEl.querySelector('.carousel') - const item = fixtureEl.querySelector('#item') - const carousel = new Carousel(carouselEl) - - spyOn(carousel, 'next').and.callThrough() - - carouselEl.addEventListener('slid.bs.carousel', () => { - expect(item.classList.contains('active')).toEqual(false) - expect(carousel.next).toHaveBeenCalled() - document.head.removeChild(stylesCarousel) - delete document.documentElement.ontouchstart - done() - }) - - Simulator.gestures.swipe(carouselEl, { - pos: [300, 10], - deltaX: -300, - deltaY: 0 - }) - }) - - it('should allow swiperight and call prev with touch events', done => { - Simulator.setType('touch') - clearPointerEvents() - document.documentElement.ontouchstart = () => {} - - fixtureEl.innerHTML = [ - '<div class="carousel" data-interval="false">', - ' <div class="carousel-inner">', - ' <div id="item" class="carousel-item">', - ' <img alt="">', - ' </div>', - ' <div class="carousel-item active">', - ' <img alt="">', - ' </div>', - ' </div>', - '</div>' - ].join('') - - const carouselEl = fixtureEl.querySelector('.carousel') - const item = fixtureEl.querySelector('#item') - const carousel = new Carousel(carouselEl) - - spyOn(carousel, 'prev').and.callThrough() - - carouselEl.addEventListener('slid.bs.carousel', () => { - expect(item.classList.contains('active')).toEqual(true) - expect(carousel.prev).toHaveBeenCalled() - delete document.documentElement.ontouchstart - restorePointerEvents() - done() - }) - - Simulator.gestures.swipe(carouselEl, { - deltaX: 300, - deltaY: 0 - }) - }) - - it('should allow swipeleft and call next with touch events', done => { - Simulator.setType('touch') - clearPointerEvents() - document.documentElement.ontouchstart = () => {} - - fixtureEl.innerHTML = [ - '<div class="carousel" data-interval="false">', - ' <div class="carousel-inner">', - ' <div id="item" class="carousel-item active">', - ' <img alt="">', - ' </div>', - ' <div class="carousel-item">', - ' <img alt="">', - ' </div>', - ' </div>', - '</div>' - ].join('') - - const carouselEl = fixtureEl.querySelector('.carousel') - const item = fixtureEl.querySelector('#item') - const carousel = new Carousel(carouselEl) - - spyOn(carousel, 'next').and.callThrough() - - carouselEl.addEventListener('slid.bs.carousel', () => { - expect(item.classList.contains('active')).toEqual(false) - expect(carousel.next).toHaveBeenCalled() - delete document.documentElement.ontouchstart - restorePointerEvents() - done() - }) - - Simulator.gestures.swipe(carouselEl, { - pos: [300, 10], - deltaX: -300, - deltaY: 0 - }) - }) - - it('should not allow pinch with touch events', done => { - Simulator.setType('touch') - clearPointerEvents() - document.documentElement.ontouchstart = () => {} - - fixtureEl.innerHTML = '<div class="carousel" data-interval="false"></div>' - - const carouselEl = fixtureEl.querySelector('.carousel') - const carousel = new Carousel(carouselEl) - - Simulator.gestures.swipe(carouselEl, { - pos: [300, 10], - deltaX: -300, - deltaY: 0, - touches: 2 - }, () => { - restorePointerEvents() - delete document.documentElement.ontouchstart - expect(carousel.touchDeltaX).toEqual(0) - done() - }) - }) - - it('should call pause method on mouse over with pause equal to hover', done => { - fixtureEl.innerHTML = '<div class="carousel"></div>' - - const carouselEl = fixtureEl.querySelector('.carousel') - const carousel = new Carousel(carouselEl) - - spyOn(carousel, 'pause') - - const mouseOverEvent = createEvent('mouseover') - carouselEl.dispatchEvent(mouseOverEvent) - - setTimeout(() => { - expect(carousel.pause).toHaveBeenCalled() - done() - }, 10) - }) - - it('should call cycle on mouse out with pause equal to hover', done => { - fixtureEl.innerHTML = '<div class="carousel"></div>' - - const carouselEl = fixtureEl.querySelector('.carousel') - const carousel = new Carousel(carouselEl) - - spyOn(carousel, 'cycle') - - const mouseOutEvent = createEvent('mouseout') - carouselEl.dispatchEvent(mouseOutEvent) - - setTimeout(() => { - expect(carousel.cycle).toHaveBeenCalled() - done() - }, 10) - }) - }) - - describe('next', () => { - it('should not slide if the carousel is sliding', () => { - fixtureEl.innerHTML = '<div></div>' - - const carouselEl = fixtureEl.querySelector('div') - const carousel = new Carousel(carouselEl, {}) - - spyOn(carousel, '_slide') - - carousel._isSliding = true - carousel.next() - - expect(carousel._slide).not.toHaveBeenCalled() - }) - - it('should not fire slid when slide is prevented', done => { - fixtureEl.innerHTML = '<div></div>' - - const carouselEl = fixtureEl.querySelector('div') - const carousel = new Carousel(carouselEl, {}) - let slidEvent = false - - const doneTest = () => { - setTimeout(() => { - expect(slidEvent).toEqual(false) - done() - }, 20) - } - - carouselEl.addEventListener('slide.bs.carousel', e => { - e.preventDefault() - doneTest() - }) - - carouselEl.addEventListener('slid.bs.carousel', () => { - slidEvent = true - }) - - carousel.next() - }) - - it('should fire slide event with: direction, relatedTarget, from and to', done => { - fixtureEl.innerHTML = [ - '<div id="myCarousel" class="carousel slide">', - ' <div class="carousel-inner">', - ' <div class="carousel-item active">item 1</div>', - ' <div class="carousel-item">item 2</div>', - ' <div class="carousel-item">item 3</div>', - ' </div>', - '</div>' - ].join('') - - const carouselEl = fixtureEl.querySelector('#myCarousel') - const carousel = new Carousel(carouselEl, {}) - - const onSlide = e => { - expect(e.direction).toEqual('left') - expect(e.relatedTarget.classList.contains('carousel-item')).toEqual(true) - expect(e.from).toEqual(0) - expect(e.to).toEqual(1) - - carouselEl.removeEventListener('slide.bs.carousel', onSlide) - carouselEl.addEventListener('slide.bs.carousel', onSlide2) - - carousel.prev() - } - - const onSlide2 = e => { - expect(e.direction).toEqual('right') - done() - } - - carouselEl.addEventListener('slide.bs.carousel', onSlide) - carousel.next() - }) - - it('should fire slid event with: direction, relatedTarget, from and to', done => { - fixtureEl.innerHTML = [ - '<div id="myCarousel" class="carousel slide">', - ' <div class="carousel-inner">', - ' <div class="carousel-item active">item 1</div>', - ' <div class="carousel-item">item 2</div>', - ' <div class="carousel-item">item 3</div>', - ' </div>', - '</div>' - ].join('') - - const carouselEl = fixtureEl.querySelector('#myCarousel') - const carousel = new Carousel(carouselEl, {}) - - const onSlid = e => { - expect(e.direction).toEqual('left') - expect(e.relatedTarget.classList.contains('carousel-item')).toEqual(true) - expect(e.from).toEqual(0) - expect(e.to).toEqual(1) - - carouselEl.removeEventListener('slid.bs.carousel', onSlid) - carouselEl.addEventListener('slid.bs.carousel', onSlid2) - - carousel.prev() - } - - const onSlid2 = e => { - expect(e.direction).toEqual('right') - done() - } - - carouselEl.addEventListener('slid.bs.carousel', onSlid) - carousel.next() - }) - - it('should get interval from data attribute in individual item', () => { - fixtureEl.innerHTML = [ - '<div id="myCarousel" class="carousel slide">', - ' <div class="carousel-inner">', - ' <div class="carousel-item active">item 1</div>', - ' <div class="carousel-item" data-interval="7">item 2</div>', - ' <div class="carousel-item">item 3</div>', - ' </div>', - '</div>' - ].join('') - - const carouselEl = fixtureEl.querySelector('#myCarousel') - const carousel = new Carousel(carouselEl, { - interval: 1814 - }) - - expect(carousel._config.interval).toEqual(1814) - - carousel.next() - - expect(carousel._config.interval).toEqual(7) - }) - - it('should update indicators if present', done => { - fixtureEl.innerHTML = [ - '<div id="myCarousel" class="carousel slide">', - ' <ol class="carousel-indicators">', - ' <li data-target="#myCarousel" data-slide-to="0" class="active"></li>', - ' <li id="secondIndicator" data-target="#myCarousel" data-slide-to="1"></li>', - ' <li data-target="#myCarousel" data-slide-to="2"></li>', - ' </ol>', - ' <div class="carousel-inner">', - ' <div class="carousel-item active">item 1</div>', - ' <div class="carousel-item" data-interval="7">item 2</div>', - ' <div class="carousel-item">item 3</div>', - ' </div>', - '</div>' - ].join('') - - const carouselEl = fixtureEl.querySelector('#myCarousel') - const secondIndicator = fixtureEl.querySelector('#secondIndicator') - const carousel = new Carousel(carouselEl) - - carouselEl.addEventListener('slid.bs.carousel', () => { - expect(secondIndicator.classList.contains('active')).toEqual(true) - done() - }) - - carousel.next() - }) - }) - - describe('nextWhenVisible', () => { - it('should not call next when the page is not visible', () => { - fixtureEl.innerHTML = [ - '<div style="display: none;">', - ' <div class="carousel" data-interval="false"></div>', - '</div>' - ].join('') - - const carouselEl = fixtureEl.querySelector('.carousel') - const carousel = new Carousel(carouselEl) - - spyOn(carousel, 'next') - - carousel.nextWhenVisible() - - expect(carousel.next).not.toHaveBeenCalled() - }) - }) - - describe('prev', () => { - it('should not slide if the carousel is sliding', () => { - fixtureEl.innerHTML = '<div></div>' - - const carouselEl = fixtureEl.querySelector('div') - const carousel = new Carousel(carouselEl, {}) - - spyOn(carousel, '_slide') - - carousel._isSliding = true - carousel.prev() - - expect(carousel._slide).not.toHaveBeenCalled() - }) - }) - - describe('pause', () => { - it('should call cycle if the carousel have carousel-item-next and carousel-item-prev class', () => { - fixtureEl.innerHTML = [ - '<div id="myCarousel" class="carousel slide">', - ' <div class="carousel-inner">', - ' <div class="carousel-item active">item 1</div>', - ' <div class="carousel-item carousel-item-next">item 2</div>', - ' <div class="carousel-item">item 3</div>', - ' </div>', - ' <div class="carousel-control-prev"></div>', - ' <div class="carousel-control-next"></div>', - '</div>' - ].join('') - - const carouselEl = fixtureEl.querySelector('#myCarousel') - const carousel = new Carousel(carouselEl) - - spyOn(carousel, 'cycle') - spyOn(window, 'clearInterval') - - carousel.pause() - - expect(carousel.cycle).toHaveBeenCalledWith(true) - expect(window.clearInterval).toHaveBeenCalled() - expect(carousel._isPaused).toEqual(true) - }) - - it('should not call cycle if nothing is in transition', () => { - fixtureEl.innerHTML = [ - '<div id="myCarousel" class="carousel slide">', - ' <div class="carousel-inner">', - ' <div class="carousel-item active">item 1</div>', - ' <div class="carousel-item">item 2</div>', - ' <div class="carousel-item">item 3</div>', - ' </div>', - ' <div class="carousel-control-prev"></div>', - ' <div class="carousel-control-next"></div>', - '</div>' - ].join('') - - const carouselEl = fixtureEl.querySelector('#myCarousel') - const carousel = new Carousel(carouselEl) - - spyOn(carousel, 'cycle') - spyOn(window, 'clearInterval') - - carousel.pause() - - expect(carousel.cycle).not.toHaveBeenCalled() - expect(window.clearInterval).toHaveBeenCalled() - expect(carousel._isPaused).toEqual(true) - }) - - it('should not set is paused at true if an event is passed', () => { - fixtureEl.innerHTML = [ - '<div id="myCarousel" class="carousel slide">', - ' <div class="carousel-inner">', - ' <div class="carousel-item active">item 1</div>', - ' <div class="carousel-item">item 2</div>', - ' <div class="carousel-item">item 3</div>', - ' </div>', - ' <div class="carousel-control-prev"></div>', - ' <div class="carousel-control-next"></div>', - '</div>' - ].join('') - - const carouselEl = fixtureEl.querySelector('#myCarousel') - const carousel = new Carousel(carouselEl) - const event = createEvent('mouseenter') - - spyOn(window, 'clearInterval') - - carousel.pause(event) - - expect(window.clearInterval).toHaveBeenCalled() - expect(carousel._isPaused).toEqual(false) - }) - }) - - describe('cycle', () => { - it('should set an interval', () => { - fixtureEl.innerHTML = [ - '<div id="myCarousel" class="carousel slide">', - ' <div class="carousel-inner">', - ' <div class="carousel-item active">item 1</div>', - ' <div class="carousel-item">item 2</div>', - ' <div class="carousel-item">item 3</div>', - ' </div>', - ' <div class="carousel-control-prev"></div>', - ' <div class="carousel-control-next"></div>', - '</div>' - ].join('') - - const carouselEl = fixtureEl.querySelector('#myCarousel') - const carousel = new Carousel(carouselEl) - - spyOn(window, 'setInterval').and.callThrough() - - carousel.cycle() - - expect(window.setInterval).toHaveBeenCalled() - }) - - it('should not set interval if the carousel is paused', () => { - fixtureEl.innerHTML = [ - '<div id="myCarousel" class="carousel slide">', - ' <div class="carousel-inner">', - ' <div class="carousel-item active">item 1</div>', - ' <div class="carousel-item">item 2</div>', - ' <div class="carousel-item">item 3</div>', - ' </div>', - ' <div class="carousel-control-prev"></div>', - ' <div class="carousel-control-next"></div>', - '</div>' - ].join('') - - const carouselEl = fixtureEl.querySelector('#myCarousel') - const carousel = new Carousel(carouselEl) - - spyOn(window, 'setInterval').and.callThrough() - - carousel._isPaused = true - carousel.cycle(true) - - expect(window.setInterval).not.toHaveBeenCalled() - }) - - it('should clear interval if there is one', () => { - fixtureEl.innerHTML = [ - '<div id="myCarousel" class="carousel slide">', - ' <div class="carousel-inner">', - ' <div class="carousel-item active">item 1</div>', - ' <div class="carousel-item">item 2</div>', - ' <div class="carousel-item">item 3</div>', - ' </div>', - ' <div class="carousel-control-prev"></div>', - ' <div class="carousel-control-next"></div>', - '</div>' - ].join('') - - const carouselEl = fixtureEl.querySelector('#myCarousel') - const carousel = new Carousel(carouselEl) - - carousel._interval = setInterval(() => {}, 10) - - spyOn(window, 'setInterval').and.callThrough() - spyOn(window, 'clearInterval').and.callThrough() - - carousel.cycle() - - expect(window.setInterval).toHaveBeenCalled() - expect(window.clearInterval).toHaveBeenCalled() - }) - }) - - describe('to', () => { - it('should go directement to the provided index', done => { - fixtureEl.innerHTML = [ - '<div id="myCarousel" class="carousel slide">', - ' <div class="carousel-inner">', - ' <div id="item1" class="carousel-item active">item 1</div>', - ' <div class="carousel-item">item 2</div>', - ' <div id="item3" class="carousel-item">item 3</div>', - ' </div>', - '</div>' - ].join('') - - const carouselEl = fixtureEl.querySelector('#myCarousel') - const carousel = new Carousel(carouselEl, {}) - - expect(fixtureEl.querySelector('.active')).toEqual(fixtureEl.querySelector('#item1')) - - carousel.to(2) - - carouselEl.addEventListener('slid.bs.carousel', () => { - expect(fixtureEl.querySelector('.active')).toEqual(fixtureEl.querySelector('#item3')) - done() - }) - }) - - it('should return to a previous slide if the provided index is lower than the current', done => { - fixtureEl.innerHTML = [ - '<div id="myCarousel" class="carousel slide">', - ' <div class="carousel-inner">', - ' <div class="carousel-item">item 1</div>', - ' <div id="item2" class="carousel-item">item 2</div>', - ' <div id="item3" class="carousel-item active">item 3</div>', - ' </div>', - '</div>' - ].join('') - - const carouselEl = fixtureEl.querySelector('#myCarousel') - const carousel = new Carousel(carouselEl, {}) - - expect(fixtureEl.querySelector('.active')).toEqual(fixtureEl.querySelector('#item3')) - - carousel.to(1) - - carouselEl.addEventListener('slid.bs.carousel', () => { - expect(fixtureEl.querySelector('.active')).toEqual(fixtureEl.querySelector('#item2')) - done() - }) - }) - - it('should do nothing if a wrong index is provided', () => { - fixtureEl.innerHTML = [ - '<div id="myCarousel" class="carousel slide">', - ' <div class="carousel-inner">', - ' <div class="carousel-item active">item 1</div>', - ' <div class="carousel-item" data-interval="7">item 2</div>', - ' <div class="carousel-item">item 3</div>', - ' </div>', - '</div>' - ].join('') - - const carouselEl = fixtureEl.querySelector('#myCarousel') - const carousel = new Carousel(carouselEl, {}) - - const spy = spyOn(carousel, '_slide') - - carousel.to(25) - - expect(spy).not.toHaveBeenCalled() - - spy.calls.reset() - - carousel.to(-5) - - expect(spy).not.toHaveBeenCalled() - }) - - it('should call pause and cycle is the provided is the same compare to the current one', () => { - fixtureEl.innerHTML = [ - '<div id="myCarousel" class="carousel slide">', - ' <div class="carousel-inner">', - ' <div class="carousel-item active">item 1</div>', - ' <div class="carousel-item" data-interval="7">item 2</div>', - ' <div class="carousel-item">item 3</div>', - ' </div>', - '</div>' - ].join('') - - const carouselEl = fixtureEl.querySelector('#myCarousel') - const carousel = new Carousel(carouselEl, {}) - - spyOn(carousel, '_slide') - spyOn(carousel, 'pause') - spyOn(carousel, 'cycle') - - carousel.to(0) - - expect(carousel._slide).not.toHaveBeenCalled() - expect(carousel.pause).toHaveBeenCalled() - expect(carousel.cycle).toHaveBeenCalled() - }) - - it('should wait before performing to if a slide is sliding', done => { - fixtureEl.innerHTML = [ - '<div id="myCarousel" class="carousel slide">', - ' <div class="carousel-inner">', - ' <div class="carousel-item active">item 1</div>', - ' <div class="carousel-item" data-interval="7">item 2</div>', - ' <div class="carousel-item">item 3</div>', - ' </div>', - '</div>' - ].join('') - - const carouselEl = fixtureEl.querySelector('#myCarousel') - const carousel = new Carousel(carouselEl, {}) - - spyOn(EventHandler, 'one').and.callThrough() - spyOn(carousel, '_slide') - - carousel._isSliding = true - carousel.to(1) - - expect(carousel._slide).not.toHaveBeenCalled() - expect(EventHandler.one).toHaveBeenCalled() - - spyOn(carousel, 'to') - - EventHandler.trigger(carouselEl, 'slid.bs.carousel') - - setTimeout(() => { - expect(carousel.to).toHaveBeenCalledWith(1) - done() - }) - }) - }) - - describe('dispose', () => { - it('should destroy a carousel', () => { - fixtureEl.innerHTML = [ - '<div id="myCarousel" class="carousel slide">', - ' <div class="carousel-inner">', - ' <div class="carousel-item active">item 1</div>', - ' <div class="carousel-item" data-interval="7">item 2</div>', - ' <div class="carousel-item">item 3</div>', - ' </div>', - '</div>' - ].join('') - - const carouselEl = fixtureEl.querySelector('#myCarousel') - const carousel = new Carousel(carouselEl) - - spyOn(EventHandler, 'off').and.callThrough() - - carousel.dispose() - - expect(EventHandler.off).toHaveBeenCalled() - }) - }) - - describe('jQueryInterface', () => { - it('should create a carousel', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - - jQueryMock.fn.carousel = Carousel.jQueryInterface - jQueryMock.elements = [div] - - jQueryMock.fn.carousel.call(jQueryMock) - - expect(Carousel.getInstance(div)).toBeDefined() - }) - - it('should not re create a carousel', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - const carousel = new Carousel(div) - - jQueryMock.fn.carousel = Carousel.jQueryInterface - jQueryMock.elements = [div] - - jQueryMock.fn.carousel.call(jQueryMock) - - expect(Carousel.getInstance(div)).toEqual(carousel) - }) - - it('should call to if the config is a number', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - const carousel = new Carousel(div) - const slideTo = 2 - - spyOn(carousel, 'to') - - jQueryMock.fn.carousel = Carousel.jQueryInterface - jQueryMock.elements = [div] - - jQueryMock.fn.carousel.call(jQueryMock, slideTo) - - expect(carousel.to).toHaveBeenCalledWith(slideTo) - }) - - it('should throw error on undefined method', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - const action = 'undefinedMethod' - - jQueryMock.fn.carousel = Carousel.jQueryInterface - jQueryMock.elements = [div] - - try { - jQueryMock.fn.carousel.call(jQueryMock, action) - } catch (error) { - expect(error.message).toEqual(`No method named "${action}"`) - } - }) - }) - - describe('data-api', () => { - it('should init carousels with data-ride="carousel" on load', () => { - fixtureEl.innerHTML = '<div data-ride="carousel"></div>' - - const carouselEl = fixtureEl.querySelector('div') - const loadEvent = createEvent('load') - - window.dispatchEvent(loadEvent) - - expect(Carousel.getInstance(carouselEl)).toBeDefined() - }) - - it('should create carousel and go to the next slide on click', done => { - fixtureEl.innerHTML = [ - '<div id="myCarousel" class="carousel slide">', - ' <div class="carousel-inner">', - ' <div class="carousel-item active">item 1</div>', - ' <div id="item2" class="carousel-item">item 2</div>', - ' <div class="carousel-item">item 3</div>', - ' </div>', - ' <div class="carousel-control-prev" data-target="#myCarousel" role="button" data-slide="prev"></div>', - ' <div id="next" class="carousel-control-next" data-target="#myCarousel" role="button" data-slide="next"></div>', - '</div>' - ].join('') - - const next = fixtureEl.querySelector('#next') - const item2 = fixtureEl.querySelector('#item2') - - next.click() - - setTimeout(() => { - expect(item2.classList.contains('active')).toEqual(true) - done() - }, 10) - }) - - it('should create carousel and go to the next slide on click with data-slide-to', done => { - fixtureEl.innerHTML = [ - '<div id="myCarousel" class="carousel slide">', - ' <div class="carousel-inner">', - ' <div class="carousel-item active">item 1</div>', - ' <div id="item2" class="carousel-item">item 2</div>', - ' <div class="carousel-item">item 3</div>', - ' </div>', - ' <div id="next" data-target="#myCarousel" data-slide-to="1"></div>', - '</div>' - ].join('') - - const next = fixtureEl.querySelector('#next') - const item2 = fixtureEl.querySelector('#item2') - - next.click() - - setTimeout(() => { - expect(item2.classList.contains('active')).toEqual(true) - done() - }, 10) - }) - - it('should do nothing if no selector on click on arrows', () => { - fixtureEl.innerHTML = [ - '<div id="myCarousel" class="carousel slide">', - ' <div class="carousel-inner">', - ' <div class="carousel-item active">item 1</div>', - ' <div class="carousel-item">item 2</div>', - ' <div class="carousel-item">item 3</div>', - ' </div>', - ' <div class="carousel-control-prev" data-target="#myCarousel" role="button" data-slide="prev"></div>', - ' <div id="next" class="carousel-control-next" role="button" data-slide="next"></div>', - '</div>' - ].join('') - - const next = fixtureEl.querySelector('#next') - - next.click() - - expect().nothing() - }) - - it('should do nothing if no carousel class on click on arrows', () => { - fixtureEl.innerHTML = [ - '<div id="myCarousel" class="slide">', - ' <div class="carousel-inner">', - ' <div class="carousel-item active">item 1</div>', - ' <div id="item2" class="carousel-item">item 2</div>', - ' <div class="carousel-item">item 3</div>', - ' </div>', - ' <div class="carousel-control-prev" data-target="#myCarousel" role="button" data-slide="prev"></div>', - ' <div id="next" class="carousel-control-next" data-target="#myCarousel" role="button" data-slide="next"></div>', - '</div>' - ].join('') - - const next = fixtureEl.querySelector('#next') - - next.click() - - expect().nothing() - }) - }) -}) diff --git a/js/src/collapse/collapse.js b/js/src/collapse.js index 4de7b5282..f533885ec 100644 --- a/js/src/collapse/collapse.js +++ b/js/src/collapse.js @@ -16,11 +16,11 @@ import { makeArray, reflow, typeCheckConfig -} from '../util/index' -import Data from '../dom/data' -import EventHandler from '../dom/event-handler' -import Manipulator from '../dom/manipulator' -import SelectorEngine from '../dom/selector-engine' +} from './util/index' +import Data from './dom/data' +import EventHandler from './dom/event-handler' +import Manipulator from './dom/manipulator' +import SelectorEngine from './dom/selector-engine' /** * ------------------------------------------------------------------------ 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) - }) - }) -}) diff --git a/js/src/dom/data.spec.js b/js/src/dom/data.spec.js deleted file mode 100644 index 46018dd5c..000000000 --- a/js/src/dom/data.spec.js +++ /dev/null @@ -1,131 +0,0 @@ -import Data from './data' - -/** Test helpers */ -import { getFixture, clearFixture } from '../../tests/helpers/fixture' - -describe('Data', () => { - let fixtureEl - - beforeAll(() => { - fixtureEl = getFixture() - }) - - afterEach(() => { - clearFixture() - }) - - describe('setData', () => { - it('should set data in an element by adding a key attribute', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - const data = { - test: 'bsData' - } - - Data.setData(div, 'test', data) - expect(div.key).toBeDefined() - }) - - it('should change data if something is already stored', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - const data = { - test: 'bsData' - } - - Data.setData(div, 'test', data) - - data.test = 'bsData2' - Data.setData(div, 'test', data) - - expect(div.key).toBeDefined() - }) - }) - - describe('getData', () => { - it('should return stored data', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - const data = { - test: 'bsData' - } - - Data.setData(div, 'test', data) - expect(Data.getData(div, 'test')).toEqual(data) - }) - - it('should return null on undefined element', () => { - expect(Data.getData(null)).toEqual(null) - expect(Data.getData(undefined)).toEqual(null) - }) - - it('should return null when an element have nothing stored', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - - expect(Data.getData(div, 'test')).toEqual(null) - }) - - it('should return null when an element have nothing stored with the provided key', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - const data = { - test: 'bsData' - } - - Data.setData(div, 'test', data) - - expect(Data.getData(div, 'test2')).toEqual(null) - }) - }) - - describe('removeData', () => { - it('should do nothing when an element have nothing stored', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - - Data.removeData(div, 'test') - expect().nothing() - }) - - it('should should do nothing if it\'s not a valid key provided', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - const data = { - test: 'bsData' - } - - Data.setData(div, 'test', data) - - expect(div.key).toBeDefined() - - Data.removeData(div, 'test2') - - expect(div.key).toBeDefined() - }) - - it('should remove data if something is stored', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - const data = { - test: 'bsData' - } - - Data.setData(div, 'test', data) - - expect(div.key).toBeDefined() - - Data.removeData(div, 'test') - - expect(div.key).toBeUndefined() - }) - }) -}) diff --git a/js/src/dom/event-handler.spec.js b/js/src/dom/event-handler.spec.js deleted file mode 100644 index dc5c5c30c..000000000 --- a/js/src/dom/event-handler.spec.js +++ /dev/null @@ -1,327 +0,0 @@ -import EventHandler from './event-handler' - -/** Test helpers */ -import { getFixture, clearFixture } from '../../tests/helpers/fixture' - -describe('EventHandler', () => { - let fixtureEl - - beforeAll(() => { - fixtureEl = getFixture() - }) - - afterEach(() => { - clearFixture() - }) - - describe('on', () => { - it('should not add event listener if the event is not a string', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - - EventHandler.on(div, null, () => {}) - EventHandler.on(null, 'click', () => {}) - - expect().nothing() - }) - - it('should add event listener', done => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - - EventHandler.on(div, 'click', () => { - expect().nothing() - done() - }) - - div.click() - }) - - it('should add namespaced event listener', done => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - - EventHandler.on(div, 'bs.namespace', () => { - expect().nothing() - done() - }) - - EventHandler.trigger(div, 'bs.namespace') - }) - - it('should add native namespaced event listener', done => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - - EventHandler.on(div, 'click.namespace', () => { - expect().nothing() - done() - }) - - EventHandler.trigger(div, 'click') - }) - - it('should handle event delegation', done => { - EventHandler.on(document, 'click', '.test', () => { - expect().nothing() - done() - }) - - fixtureEl.innerHTML = '<div class="test"></div>' - - const div = fixtureEl.querySelector('div') - - div.click() - }) - }) - - describe('one', () => { - it('should call listener just one', done => { - fixtureEl.innerHTML = '<div></div>' - - let called = 0 - const div = fixtureEl.querySelector('div') - const obj = { - oneListener() { - called++ - } - } - - EventHandler.one(div, 'bootstrap', obj.oneListener) - - EventHandler.trigger(div, 'bootstrap') - EventHandler.trigger(div, 'bootstrap') - - setTimeout(() => { - expect(called).toEqual(1) - done() - }, 20) - }) - }) - - describe('off', () => { - it('should not remove a listener', () => { - fixtureEl.innerHTML = '<div></div>' - const div = fixtureEl.querySelector('div') - - EventHandler.off(div, null, () => {}) - EventHandler.off(null, 'click', () => {}) - expect().nothing() - }) - - it('should remove a listener', done => { - fixtureEl.innerHTML = '<div></div>' - const div = fixtureEl.querySelector('div') - - let called = 0 - const handler = () => { - called++ - } - - EventHandler.on(div, 'foobar', handler) - EventHandler.trigger(div, 'foobar') - - EventHandler.off(div, 'foobar', handler) - EventHandler.trigger(div, 'foobar') - - setTimeout(() => { - expect(called).toEqual(1) - done() - }, 20) - }) - - it('should remove all the events', done => { - fixtureEl.innerHTML = '<div></div>' - const div = fixtureEl.querySelector('div') - - let called = 0 - - EventHandler.on(div, 'foobar', () => { - called++ - }) - EventHandler.on(div, 'foobar', () => { - called++ - }) - EventHandler.trigger(div, 'foobar') - - EventHandler.off(div, 'foobar') - EventHandler.trigger(div, 'foobar') - - setTimeout(() => { - expect(called).toEqual(2) - done() - }, 20) - }) - - it('should remove all the namespaced listeners if namespace is passed', done => { - fixtureEl.innerHTML = '<div></div>' - const div = fixtureEl.querySelector('div') - - let called = 0 - - EventHandler.on(div, 'foobar.namespace', () => { - called++ - }) - EventHandler.on(div, 'foofoo.namespace', () => { - called++ - }) - EventHandler.trigger(div, 'foobar.namespace') - EventHandler.trigger(div, 'foofoo.namespace') - - EventHandler.off(div, '.namespace') - EventHandler.trigger(div, 'foobar.namespace') - EventHandler.trigger(div, 'foofoo.namespace') - - setTimeout(() => { - expect(called).toEqual(2) - done() - }, 20) - }) - - it('should remove the namespaced listeners', done => { - fixtureEl.innerHTML = '<div></div>' - const div = fixtureEl.querySelector('div') - - let calledCallback1 = 0 - let calledCallback2 = 0 - - EventHandler.on(div, 'foobar.namespace', () => { - calledCallback1++ - }) - EventHandler.on(div, 'foofoo.namespace', () => { - calledCallback2++ - }) - - EventHandler.trigger(div, 'foobar.namespace') - EventHandler.off(div, 'foobar.namespace') - EventHandler.trigger(div, 'foobar.namespace') - - EventHandler.trigger(div, 'foofoo.namespace') - - setTimeout(() => { - expect(calledCallback1).toEqual(1) - expect(calledCallback2).toEqual(1) - done() - }, 20) - }) - - it('should remove the all the namespaced listeners for native events', done => { - fixtureEl.innerHTML = '<div></div>' - const div = fixtureEl.querySelector('div') - - let called = 0 - - EventHandler.on(div, 'click.namespace', () => { - called++ - }) - EventHandler.on(div, 'click.namespace2', () => { - called++ - }) - - EventHandler.trigger(div, 'click') - EventHandler.off(div, 'click') - EventHandler.trigger(div, 'click') - - setTimeout(() => { - expect(called).toEqual(2) - done() - }, 20) - }) - - it('should remove the specified namespaced listeners for native events', done => { - fixtureEl.innerHTML = '<div></div>' - const div = fixtureEl.querySelector('div') - - let called1 = 0 - let called2 = 0 - - EventHandler.on(div, 'click.namespace', () => { - called1++ - }) - EventHandler.on(div, 'click.namespace2', () => { - called2++ - }) - EventHandler.trigger(div, 'click') - - EventHandler.off(div, 'click.namespace') - EventHandler.trigger(div, 'click') - - setTimeout(() => { - expect(called1).toEqual(1) - expect(called2).toEqual(2) - done() - }, 20) - }) - - it('should remove a listener registered by .one', done => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - const handler = () => { - throw new Error('called') - } - - EventHandler.one(div, 'foobar', handler) - EventHandler.off(div, 'foobar', handler) - - EventHandler.trigger(div, 'foobar') - setTimeout(() => { - expect().nothing() - done() - }, 20) - }) - - it('should remove the correct delegated event listener', () => { - const element = document.createElement('div') - const subelement = document.createElement('span') - element.appendChild(subelement) - - const anchor = document.createElement('a') - element.appendChild(anchor) - - let i = 0 - const handler = () => { - i++ - } - - EventHandler.on(element, 'click', 'a', handler) - EventHandler.on(element, 'click', 'span', handler) - - fixtureEl.appendChild(element) - - EventHandler.trigger(anchor, 'click') - EventHandler.trigger(subelement, 'click') - - // first listeners called - expect(i === 2).toEqual(true) - - EventHandler.off(element, 'click', 'span', handler) - EventHandler.trigger(subelement, 'click') - - // removed listener not called - expect(i === 2).toEqual(true) - - EventHandler.trigger(anchor, 'click') - - // not removed listener called - expect(i === 3).toEqual(true) - - EventHandler.on(element, 'click', 'span', handler) - EventHandler.trigger(anchor, 'click') - EventHandler.trigger(subelement, 'click') - - // listener re-registered - expect(i === 5).toEqual(true) - - EventHandler.off(element, 'click', 'span') - EventHandler.trigger(subelement, 'click') - - // listener removed again - expect(i === 5).toEqual(true) - }) - }) -}) diff --git a/js/src/dom/manipulator.spec.js b/js/src/dom/manipulator.spec.js deleted file mode 100644 index e96c068d4..000000000 --- a/js/src/dom/manipulator.spec.js +++ /dev/null @@ -1,158 +0,0 @@ -import Manipulator from './manipulator' - -/** Test helpers */ -import { getFixture, clearFixture } from '../../tests/helpers/fixture' - -describe('Manipulator', () => { - let fixtureEl - - beforeAll(() => { - fixtureEl = getFixture() - }) - - afterEach(() => { - clearFixture() - }) - - describe('setDataAttribute', () => { - it('should set data attribute', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - - Manipulator.setDataAttribute(div, 'key', 'value') - expect(div.getAttribute('data-key')).toEqual('value') - }) - - it('should set data attribute in lower case', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - - Manipulator.setDataAttribute(div, 'tEsT', 'value') - expect(div.getAttribute('data-test')).toEqual('value') - }) - }) - - describe('removeDataAttribute', () => { - it('should remove data attribute', () => { - fixtureEl.innerHTML = '<div data-key="value"></div>' - - const div = fixtureEl.querySelector('div') - - Manipulator.removeDataAttribute(div, 'key') - expect(div.getAttribute('data-key')).toBeNull() - }) - - it('should remove data attribute in lower case', () => { - fixtureEl.innerHTML = '<div data-testkey="value" ></div>' - - const div = fixtureEl.querySelector('div') - - Manipulator.removeDataAttribute(div, 'tEStKeY') - expect(div.getAttribute('data-testkey')).toBeNull() - }) - }) - - describe('getDataAttributes', () => { - it('should return empty object for null', () => { - expect(Manipulator.getDataAttributes(null), {}) - expect().nothing() - }) - - it('should get all data attributes', () => { - fixtureEl.innerHTML = '<div data-test="js" data-test2="js2" ></div>' - - const div = fixtureEl.querySelector('div') - - expect(Manipulator.getDataAttributes(div)).toEqual({ - test: 'js', - test2: 'js2' - }) - }) - }) - - describe('getDataAttribute', () => { - it('should get data attribute', () => { - fixtureEl.innerHTML = '<div data-test="null" ></div>' - - const div = fixtureEl.querySelector('div') - - expect(Manipulator.getDataAttribute(div, 'test')).toBeNull() - }) - - it('should get data attribute in lower case', () => { - fixtureEl.innerHTML = '<div data-test="value" ></div>' - - const div = fixtureEl.querySelector('div') - - expect(Manipulator.getDataAttribute(div, 'tEsT')).toEqual('value') - }) - - it('should normalize data', () => { - fixtureEl.innerHTML = '<div data-test="false" ></div>' - - const div = fixtureEl.querySelector('div') - - expect(Manipulator.getDataAttribute(div, 'test')).toEqual(false) - - div.setAttribute('data-test', 'true') - expect(Manipulator.getDataAttribute(div, 'test')).toEqual(true) - - div.setAttribute('data-test', '1') - expect(Manipulator.getDataAttribute(div, 'test')).toEqual(1) - }) - }) - - describe('offset', () => { - it('should return object with two properties top and left, both numbers', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - const offset = Manipulator.offset(div) - - expect(offset).toBeDefined() - expect(offset.top).toEqual(jasmine.any(Number)) - expect(offset.left).toEqual(jasmine.any(Number)) - }) - }) - - describe('position', () => { - it('should return object with two properties top and left, both numbers', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - const position = Manipulator.position(div) - - expect(position).toBeDefined() - expect(position.top).toEqual(jasmine.any(Number)) - expect(position.left).toEqual(jasmine.any(Number)) - }) - }) - - describe('toggleClass', () => { - it('should not error out if element is null or undefined', () => { - Manipulator.toggleClass(null, 'test') - Manipulator.toggleClass(undefined, 'test') - expect().nothing() - }) - - it('should add class if it is missing', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - - Manipulator.toggleClass(div, 'test') - expect(div.classList.contains('test')).toEqual(true) - }) - - it('should remove class if it is set', () => { - fixtureEl.innerHTML = '<div class="test"></div>' - - const div = fixtureEl.querySelector('div') - - Manipulator.toggleClass(div, 'test') - expect(div.classList.contains('test')).toEqual(false) - }) - }) -}) diff --git a/js/src/dom/selector-engine.spec.js b/js/src/dom/selector-engine.spec.js deleted file mode 100644 index 28ccdf40b..000000000 --- a/js/src/dom/selector-engine.spec.js +++ /dev/null @@ -1,115 +0,0 @@ -import SelectorEngine from './selector-engine' -import { makeArray } from '../util/index' - -/** Test helpers */ -import { getFixture, clearFixture } from '../../tests/helpers/fixture' - -describe('SelectorEngine', () => { - let fixtureEl - - beforeAll(() => { - fixtureEl = getFixture() - }) - - afterEach(() => { - clearFixture() - }) - - describe('matches', () => { - it('should return matched elements', () => { - fixtureEl.innerHTML = '<div></div>' - - expect(SelectorEngine.matches(fixtureEl, 'div')).toEqual(true) - }) - }) - - describe('find', () => { - it('should find elements', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - - expect(makeArray(SelectorEngine.find('div', fixtureEl))).toEqual([div]) - }) - - it('should find elements globaly', () => { - fixtureEl.innerHTML = '<div id="test"></div>' - - const div = fixtureEl.querySelector('#test') - - expect(makeArray(SelectorEngine.find('#test'))).toEqual([div]) - }) - - it('should handle :scope selectors', () => { - fixtureEl.innerHTML = `<ul> - <li></li> - <li> - <a href="#" class="active">link</a> - </li> - <li></li> - </ul>` - - const listEl = fixtureEl.querySelector('ul') - const aActive = fixtureEl.querySelector('.active') - - expect(makeArray(SelectorEngine.find(':scope > li > .active', listEl))).toEqual([aActive]) - }) - }) - - describe('findOne', () => { - it('should return one element', () => { - fixtureEl.innerHTML = '<div id="test"></div>' - - const div = fixtureEl.querySelector('#test') - - expect(SelectorEngine.findOne('#test')).toEqual(div) - }) - }) - - describe('children', () => { - it('should find children', () => { - fixtureEl.innerHTML = `<ul> - <li></li> - <li></li> - <li></li> - </ul>` - - const list = fixtureEl.querySelector('ul') - const liList = makeArray(fixtureEl.querySelectorAll('li')) - const result = makeArray(SelectorEngine.children(list, 'li')) - - expect(result).toEqual(liList) - }) - }) - - describe('parents', () => { - it('should return parents', () => { - expect(SelectorEngine.parents(fixtureEl, 'body').length).toEqual(1) - }) - }) - - describe('prev', () => { - it('should return previous element', () => { - fixtureEl.innerHTML = '<div class="test"></div><button class="btn"></button>' - - const btn = fixtureEl.querySelector('.btn') - const divTest = fixtureEl.querySelector('.test') - - expect(SelectorEngine.prev(btn, '.test')).toEqual([divTest]) - }) - - it('should return previous element with an extra element between', () => { - fixtureEl.innerHTML = [ - '<div class="test"></div>', - '<span></span>', - '<button class="btn"></button>' - ].join('') - - const btn = fixtureEl.querySelector('.btn') - const divTest = fixtureEl.querySelector('.test') - - expect(SelectorEngine.prev(btn, '.test')).toEqual([divTest]) - }) - }) -}) - diff --git a/js/src/dropdown/dropdown.js b/js/src/dropdown.js index b84035689..06a271ef8 100644 --- a/js/src/dropdown/dropdown.js +++ b/js/src/dropdown.js @@ -12,12 +12,12 @@ import { makeArray, noop, typeCheckConfig -} from '../util/index' -import Data from '../dom/data' -import EventHandler from '../dom/event-handler' -import Manipulator from '../dom/manipulator' +} from './util/index' +import Data from './dom/data' +import EventHandler from './dom/event-handler' +import Manipulator from './dom/manipulator' import Popper from 'popper.js' -import SelectorEngine from '../dom/selector-engine' +import SelectorEngine from './dom/selector-engine' /** * ------------------------------------------------------------------------ diff --git a/js/src/dropdown/dropdown.spec.js b/js/src/dropdown/dropdown.spec.js deleted file mode 100644 index 46374453c..000000000 --- a/js/src/dropdown/dropdown.spec.js +++ /dev/null @@ -1,1564 +0,0 @@ -import Popper from 'popper.js' - -import Dropdown from './dropdown' -import EventHandler from '../dom/event-handler' - -/** Test helpers */ -import { getFixture, clearFixture, createEvent, jQueryMock } from '../../tests/helpers/fixture' - -describe('Dropdown', () => { - let fixtureEl - - beforeAll(() => { - fixtureEl = getFixture() - }) - - afterEach(() => { - clearFixture() - }) - - describe('VERSION', () => { - it('should return plugin version', () => { - expect(Dropdown.VERSION).toEqual(jasmine.any(String)) - }) - }) - - describe('Default', () => { - it('should return plugin default config', () => { - expect(Dropdown.Default).toEqual(jasmine.any(Object)) - }) - }) - - describe('DefaultType', () => { - it('should return plugin default type config', () => { - expect(Dropdown.DefaultType).toEqual(jasmine.any(Object)) - }) - }) - - describe('constructor', () => { - it('should create offset modifier correctly when offset option is a function', () => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const getOffset = offsets => offsets - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdown = new Dropdown(btnDropdown, { - offset: getOffset - }) - - const offset = dropdown._getOffset() - - expect(offset.offset).toBeUndefined() - expect(typeof offset.fn).toEqual('function') - }) - - it('should create offset modifier correctly when offset option is not a function', () => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const myOffset = 7 - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdown = new Dropdown(btnDropdown, { - offset: myOffset - }) - - const offset = dropdown._getOffset() - - expect(offset.offset).toEqual(myOffset) - expect(offset.fn).toBeUndefined() - }) - - it('should add a listener on trigger which do not have data-toggle="dropdown"', () => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('.btn') - const dropdown = new Dropdown(btnDropdown) - - spyOn(dropdown, 'toggle') - - btnDropdown.click() - - expect(dropdown.toggle).toHaveBeenCalled() - }) - - it('should allow to pass config to popper.js with `popperConfig`', () => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdown = new Dropdown(btnDropdown, { - popperConfig: { - placement: 'left' - } - }) - - const popperConfig = dropdown._getPopperConfig() - - expect(popperConfig.placement).toEqual('left') - }) - }) - - describe('toggle', () => { - it('should toggle a dropdown', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown" aria-expanded="false">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') - const dropdown = new Dropdown(btnDropdown) - - dropdownEl.addEventListener('shown.bs.dropdown', () => { - expect(dropdownEl.classList.contains('show')).toEqual(true) - expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') - done() - }) - - dropdown.toggle() - }) - - it('should destroy old popper references on toggle', done => { - fixtureEl.innerHTML = [ - '<div class="first dropdown">', - ' <button href="#" class="firstBtn btn" data-toggle="dropdown" aria-expanded="false">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>', - '<div class="second dropdown">', - ' <button href="#" class="secondBtn btn" data-toggle="dropdown" aria-expanded="false">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown1 = fixtureEl.querySelector('.firstBtn') - const btnDropdown2 = fixtureEl.querySelector('.secondBtn') - const firstDropdownEl = fixtureEl.querySelector('.first') - const secondDropdownEl = fixtureEl.querySelector('.second') - const dropdown1 = new Dropdown(btnDropdown1) - const dropdown2 = new Dropdown(btnDropdown2) - - firstDropdownEl.addEventListener('shown.bs.dropdown', () => { - expect(firstDropdownEl.classList.contains('show')).toEqual(true) - spyOn(dropdown1._popper, 'destroy') - dropdown2.toggle() - }) - - secondDropdownEl.addEventListener('shown.bs.dropdown', () => { - expect(dropdown1._popper.destroy).toHaveBeenCalled() - done() - }) - - dropdown1.toggle() - }) - - it('should toggle a dropdown and add/remove event listener on mobile', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown" aria-expanded="false">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const defaultValueOnTouchStart = document.documentElement.ontouchstart - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') - const dropdown = new Dropdown(btnDropdown) - - document.documentElement.ontouchstart = () => {} - spyOn(EventHandler, 'on') - spyOn(EventHandler, 'off') - - dropdownEl.addEventListener('shown.bs.dropdown', () => { - expect(dropdownEl.classList.contains('show')).toEqual(true) - expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') - expect(EventHandler.on).toHaveBeenCalled() - - dropdown.toggle() - }) - - dropdownEl.addEventListener('hidden.bs.dropdown', () => { - expect(dropdownEl.classList.contains('show')).toEqual(false) - expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false') - expect(EventHandler.off).toHaveBeenCalled() - - document.documentElement.ontouchstart = defaultValueOnTouchStart - done() - }) - - dropdown.toggle() - }) - - it('should toggle a dropdown at the right', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown" aria-expanded="false">Dropdown</button>', - ' <div class="dropdown-menu dropdown-menu-right">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') - const dropdown = new Dropdown(btnDropdown) - - dropdownEl.addEventListener('shown.bs.dropdown', () => { - expect(dropdownEl.classList.contains('show')).toEqual(true) - expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') - done() - }) - - dropdown.toggle() - }) - - it('should toggle a dropup', done => { - fixtureEl.innerHTML = [ - '<div class="dropup">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown" aria-expanded="false">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropupEl = fixtureEl.querySelector('.dropup') - const dropdown = new Dropdown(btnDropdown) - - dropupEl.addEventListener('shown.bs.dropdown', () => { - expect(dropupEl.classList.contains('show')).toEqual(true) - expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') - done() - }) - - dropdown.toggle() - }) - - it('should toggle a dropup at the right', done => { - fixtureEl.innerHTML = [ - '<div class="dropup">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown" aria-expanded="false">Dropdown</button>', - ' <div class="dropdown-menu dropdown-menu-right">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropupEl = fixtureEl.querySelector('.dropup') - const dropdown = new Dropdown(btnDropdown) - - dropupEl.addEventListener('shown.bs.dropdown', () => { - expect(dropupEl.classList.contains('show')).toEqual(true) - expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') - done() - }) - - dropdown.toggle() - }) - - it('should toggle a dropright', done => { - fixtureEl.innerHTML = [ - '<div class="dropright">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown" aria-expanded="false">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const droprightEl = fixtureEl.querySelector('.dropright') - const dropdown = new Dropdown(btnDropdown) - - droprightEl.addEventListener('shown.bs.dropdown', () => { - expect(droprightEl.classList.contains('show')).toEqual(true) - expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') - done() - }) - - dropdown.toggle() - }) - - it('should toggle a dropleft', done => { - fixtureEl.innerHTML = [ - '<div class="dropleft">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown" aria-expanded="false">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropleftEl = fixtureEl.querySelector('.dropleft') - const dropdown = new Dropdown(btnDropdown) - - dropleftEl.addEventListener('shown.bs.dropdown', () => { - expect(dropleftEl.classList.contains('show')).toEqual(true) - expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') - done() - }) - - dropdown.toggle() - }) - - it('should toggle a dropdown with parent reference', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown" aria-expanded="false">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') - const dropdown = new Dropdown(btnDropdown, { - reference: 'parent' - }) - - dropdownEl.addEventListener('shown.bs.dropdown', () => { - expect(dropdownEl.classList.contains('show')).toEqual(true) - expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') - done() - }) - - dropdown.toggle() - }) - - it('should toggle a dropdown with a dom node reference', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown" aria-expanded="false">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') - const dropdown = new Dropdown(btnDropdown, { - reference: fixtureEl - }) - - dropdownEl.addEventListener('shown.bs.dropdown', () => { - expect(dropdownEl.classList.contains('show')).toEqual(true) - expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') - done() - }) - - dropdown.toggle() - }) - - it('should toggle a dropdown with a jquery object reference', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown" aria-expanded="false">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') - const dropdown = new Dropdown(btnDropdown, { - reference: { 0: fixtureEl, jquery: 'jQuery' } - }) - - dropdownEl.addEventListener('shown.bs.dropdown', () => { - expect(dropdownEl.classList.contains('show')).toEqual(true) - expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') - done() - }) - - dropdown.toggle() - }) - - it('should not toggle a dropdown if the element is disabled', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button disabled href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') - const dropdown = new Dropdown(btnDropdown) - - dropdownEl.addEventListener('shown.bs.dropdown', () => { - throw new Error('should not throw shown.bs.dropdown event') - }) - - dropdown.toggle() - - setTimeout(() => { - expect().nothing() - done() - }) - }) - - it('should not toggle a dropdown if the element contains .disabled', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle disabled" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') - const dropdown = new Dropdown(btnDropdown) - - dropdownEl.addEventListener('shown.bs.dropdown', () => { - throw new Error('should not throw shown.bs.dropdown event') - }) - - dropdown.toggle() - - setTimeout(() => { - expect().nothing() - done() - }) - }) - - it('should not toggle a dropdown if the menu is shown', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu show">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') - const dropdown = new Dropdown(btnDropdown) - - dropdownEl.addEventListener('shown.bs.dropdown', () => { - throw new Error('should not throw shown.bs.dropdown event') - }) - - dropdown.toggle() - - setTimeout(() => { - expect().nothing() - done() - }) - }) - - it('should not toggle a dropdown if show event is prevented', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') - const dropdown = new Dropdown(btnDropdown) - - dropdownEl.addEventListener('show.bs.dropdown', e => { - e.preventDefault() - }) - - dropdownEl.addEventListener('shown.bs.dropdown', () => { - throw new Error('should not throw shown.bs.dropdown event') - }) - - dropdown.toggle() - - setTimeout(() => { - expect().nothing() - done() - }) - }) - }) - - describe('show', () => { - it('should show a dropdown', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') - const dropdown = new Dropdown(btnDropdown) - - dropdownEl.addEventListener('shown.bs.dropdown', () => { - expect(dropdownEl.classList.contains('show')).toEqual(true) - done() - }) - - dropdown.show() - }) - - it('should not show a dropdown if the element is disabled', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button disabled href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') - const dropdown = new Dropdown(btnDropdown) - - dropdownEl.addEventListener('shown.bs.dropdown', () => { - throw new Error('should not throw shown.bs.dropdown event') - }) - - dropdown.show() - - setTimeout(() => { - expect().nothing() - done() - }, 10) - }) - - it('should not show a dropdown if the element contains .disabled', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle disabled" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') - const dropdown = new Dropdown(btnDropdown) - - dropdownEl.addEventListener('shown.bs.dropdown', () => { - throw new Error('should not throw shown.bs.dropdown event') - }) - - dropdown.show() - - setTimeout(() => { - expect().nothing() - done() - }, 10) - }) - - it('should not show a dropdown if the menu is shown', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu show">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') - const dropdown = new Dropdown(btnDropdown) - - dropdownEl.addEventListener('shown.bs.dropdown', () => { - throw new Error('should not throw shown.bs.dropdown event') - }) - - dropdown.show() - - setTimeout(() => { - expect().nothing() - done() - }, 10) - }) - - it('should not show a dropdown if show event is prevented', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') - const dropdown = new Dropdown(btnDropdown) - - dropdownEl.addEventListener('show.bs.dropdown', e => { - e.preventDefault() - }) - - dropdownEl.addEventListener('shown.bs.dropdown', () => { - throw new Error('should not throw shown.bs.dropdown event') - }) - - dropdown.show() - - setTimeout(() => { - expect().nothing() - done() - }, 10) - }) - }) - - describe('hide', () => { - it('should hide a dropdown', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu show">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') - const dropdownMenu = fixtureEl.querySelector('.dropdown-menu') - const dropdown = new Dropdown(btnDropdown) - - dropdownEl.addEventListener('hidden.bs.dropdown', () => { - expect(dropdownMenu.classList.contains('show')).toEqual(false) - done() - }) - - dropdown.hide() - }) - - it('should hide a dropdown and destroy popper', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') - const dropdown = new Dropdown(btnDropdown) - - dropdownEl.addEventListener('shown.bs.dropdown', () => { - spyOn(dropdown._popper, 'destroy') - dropdown.hide() - }) - - dropdownEl.addEventListener('hidden.bs.dropdown', () => { - expect(dropdown._popper.destroy).toHaveBeenCalled() - done() - }) - - dropdown.show() - }) - - it('should not hide a dropdown if the element is disabled', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button disabled href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu show">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') - const dropdownMenu = fixtureEl.querySelector('.dropdown-menu') - const dropdown = new Dropdown(btnDropdown) - - dropdownEl.addEventListener('hidden.bs.dropdown', () => { - throw new Error('should not throw hidden.bs.dropdown event') - }) - - dropdown.hide() - - setTimeout(() => { - expect(dropdownMenu.classList.contains('show')).toEqual(true) - done() - }, 10) - }) - - it('should not hide a dropdown if the element contains .disabled', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle disabled" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu show">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') - const dropdownMenu = fixtureEl.querySelector('.dropdown-menu') - const dropdown = new Dropdown(btnDropdown) - - dropdownEl.addEventListener('hidden.bs.dropdown', () => { - throw new Error('should not throw hidden.bs.dropdown event') - }) - - dropdown.hide() - - setTimeout(() => { - expect(dropdownMenu.classList.contains('show')).toEqual(true) - done() - }, 10) - }) - - it('should not hide a dropdown if the menu is not shown', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') - const dropdown = new Dropdown(btnDropdown) - - dropdownEl.addEventListener('hidden.bs.dropdown', () => { - throw new Error('should not throw hidden.bs.dropdown event') - }) - - dropdown.hide() - - setTimeout(() => { - expect().nothing() - done() - }, 10) - }) - - it('should not hide a dropdown if hide event is prevented', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu show">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') - const dropdownMenu = fixtureEl.querySelector('.dropdown-menu') - const dropdown = new Dropdown(btnDropdown) - - dropdownEl.addEventListener('hide.bs.dropdown', e => { - e.preventDefault() - }) - - dropdownEl.addEventListener('hidden.bs.dropdown', () => { - throw new Error('should not throw hidden.bs.dropdown event') - }) - - dropdown.hide() - - setTimeout(() => { - expect(dropdownMenu.classList.contains('show')).toEqual(true) - done() - }) - }) - }) - - describe('dispose', () => { - it('should dispose dropdown', () => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdown = new Dropdown(btnDropdown) - - expect(dropdown._popper).toBeNull() - expect(dropdown._menu).toBeDefined() - expect(dropdown._element).toBeDefined() - - dropdown.dispose() - - expect(dropdown._menu).toBeNull() - expect(dropdown._element).toBeNull() - }) - - it('should dispose dropdown with popper.js', () => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdown = new Dropdown(btnDropdown) - - dropdown.toggle() - - expect(dropdown._popper).toBeDefined() - expect(dropdown._menu).toBeDefined() - expect(dropdown._element).toBeDefined() - - spyOn(Popper.prototype, 'destroy') - - dropdown.dispose() - - expect(dropdown._popper).toBeNull() - expect(dropdown._menu).toBeNull() - expect(dropdown._element).toBeNull() - expect(Popper.prototype.destroy).toHaveBeenCalled() - }) - }) - - describe('update', () => { - it('should call popper.js and detect navbar on update', () => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdown = new Dropdown(btnDropdown) - - dropdown.toggle() - - expect(dropdown._popper).toBeDefined() - - spyOn(dropdown._popper, 'scheduleUpdate') - spyOn(dropdown, '_detectNavbar') - - dropdown.update() - - expect(dropdown._popper.scheduleUpdate).toHaveBeenCalled() - expect(dropdown._detectNavbar).toHaveBeenCalled() - }) - - it('should just detect navbar on update', () => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdown = new Dropdown(btnDropdown) - - spyOn(dropdown, '_detectNavbar') - - dropdown.update() - - expect(dropdown._popper).toBeNull() - expect(dropdown._detectNavbar).toHaveBeenCalled() - }) - }) - - describe('data-api', () => { - it('should not add class position-static to dropdown if boundary not set', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') - - dropdownEl.addEventListener('shown.bs.dropdown', () => { - expect(dropdownEl.classList.contains('position-static')).toEqual(false) - done() - }) - - btnDropdown.click() - }) - - it('should add class position-static to dropdown if boundary not scrollParent', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown" data-boundary="viewport">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') - - dropdownEl.addEventListener('shown.bs.dropdown', () => { - expect(dropdownEl.classList.contains('position-static')).toEqual(true) - done() - }) - - btnDropdown.click() - }) - - it('should show and hide a dropdown', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown" aria-expanded="false">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') - let showEventTriggered = false - let hideEventTriggered = false - - dropdownEl.addEventListener('show.bs.dropdown', () => { - showEventTriggered = true - }) - - dropdownEl.addEventListener('shown.bs.dropdown', e => { - expect(dropdownEl.classList.contains('show')).toEqual(true) - expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true') - expect(showEventTriggered).toEqual(true) - expect(e.relatedTarget).toEqual(btnDropdown) - document.body.click() - }) - - dropdownEl.addEventListener('hide.bs.dropdown', () => { - hideEventTriggered = true - }) - - dropdownEl.addEventListener('hidden.bs.dropdown', e => { - expect(dropdownEl.classList.contains('show')).toEqual(false) - expect(btnDropdown.getAttribute('aria-expanded')).toEqual('false') - expect(hideEventTriggered).toEqual(true) - expect(e.relatedTarget).toEqual(btnDropdown) - done() - }) - - btnDropdown.click() - }) - - it('should not use popper.js in navbar', done => { - fixtureEl.innerHTML = [ - '<nav class="navbar navbar-expand-md navbar-light bg-light">', - ' <div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown" aria-expanded="false">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - ' </div>', - '</nav>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') - const dropdownMenu = fixtureEl.querySelector('.dropdown-menu') - - dropdownEl.addEventListener('shown.bs.dropdown', () => { - expect(dropdownMenu.getAttribute('style')).toEqual(null, 'no inline style applied by popper.js') - done() - }) - - btnDropdown.click() - }) - - it('should not use popper.js if display set to static', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown" data-display="static">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') - const dropdownMenu = fixtureEl.querySelector('.dropdown-menu') - - dropdownEl.addEventListener('shown.bs.dropdown', () => { - // popper.js add this attribute when we use it - expect(dropdownMenu.getAttribute('x-placement')).toEqual(null) - done() - }) - - btnDropdown.click() - }) - - it('should remove "show" class if tabbing outside of menu', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' </div>', - '</div>' - ].join('') - - const btnDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdownEl = fixtureEl.querySelector('.dropdown') - - dropdownEl.addEventListener('shown.bs.dropdown', () => { - expect(dropdownEl.classList.contains('show')).toEqual(true) - - const keyUp = createEvent('keyup') - - keyUp.which = 9 // Tab - document.dispatchEvent(keyUp) - }) - - dropdownEl.addEventListener('hidden.bs.dropdown', () => { - expect(dropdownEl.classList.contains('show')).toEqual(false) - done() - }) - - btnDropdown.click() - }) - - it('should remove "show" class if body is clicked, with multiple dropdowns', done => { - fixtureEl.innerHTML = [ - '<div class="nav">', - ' <div class="dropdown" id="testmenu">', - ' <a class="dropdown-toggle" data-toggle="dropdown" href="#testmenu">Test menu <span class="caret"/></a>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#sub1">Submenu 1</a>', - ' </div>', - ' </div>', - '</div>', - '<div class="btn-group">', - ' <button class="btn">Actions</button>', - ' <button class="btn dropdown-toggle" data-toggle="dropdown"></button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Action 1</a>', - ' </div>', - '</div>' - ].join('') - - const triggerDropdownList = fixtureEl.querySelectorAll('[data-toggle="dropdown"]') - - expect(triggerDropdownList.length).toEqual(2) - - const first = triggerDropdownList[0] - const last = triggerDropdownList[1] - const dropdownTestMenu = first.parentNode - const btnGroup = last.parentNode - - dropdownTestMenu.addEventListener('shown.bs.dropdown', () => { - expect(dropdownTestMenu.classList.contains('show')).toEqual(true) - expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(1) - document.body.click() - }) - - dropdownTestMenu.addEventListener('hidden.bs.dropdown', () => { - expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(0) - last.click() - }) - - btnGroup.addEventListener('shown.bs.dropdown', () => { - expect(btnGroup.classList.contains('show')).toEqual(true) - expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(1) - document.body.click() - }) - - btnGroup.addEventListener('hidden.bs.dropdown', () => { - expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(0) - done() - }) - - first.click() - }) - - it('should remove "show" class if body if tabbing outside of menu, with multiple dropdowns', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <a class="dropdown-toggle" data-toggle="dropdown" href="#testmenu">Test menu</a>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#sub1">Submenu 1</a>', - ' </div>', - '</div>', - '<div class="btn-group">', - ' <button class="btn">Actions</button>', - ' <button class="btn dropdown-toggle" data-toggle="dropdown"></button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Action 1</a>', - ' </div>', - '</div>' - ].join('') - - const triggerDropdownList = fixtureEl.querySelectorAll('[data-toggle="dropdown"]') - - expect(triggerDropdownList.length).toEqual(2) - - const first = triggerDropdownList[0] - const last = triggerDropdownList[1] - const dropdownTestMenu = first.parentNode - const btnGroup = last.parentNode - - dropdownTestMenu.addEventListener('shown.bs.dropdown', () => { - expect(dropdownTestMenu.classList.contains('show')).toEqual(true, '"show" class added on click') - expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(1, 'only one dropdown is shown') - - const keyUp = createEvent('keyup') - keyUp.which = 9 // Tab - - document.dispatchEvent(keyUp) - }) - - dropdownTestMenu.addEventListener('hidden.bs.dropdown', () => { - expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(0, '"show" class removed') - last.click() - }) - - btnGroup.addEventListener('shown.bs.dropdown', () => { - expect(btnGroup.classList.contains('show')).toEqual(true, '"show" class added on click') - expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(1, 'only one dropdown is shown') - - const keyUp = createEvent('keyup') - keyUp.which = 9 // Tab - - document.dispatchEvent(keyUp) - }) - - btnGroup.addEventListener('hidden.bs.dropdown', () => { - expect(fixtureEl.querySelectorAll('.dropdown-menu.show').length).toEqual(0, '"show" class removed') - done() - }) - - first.click() - }) - - it('should fire hide and hidden event without a clickEvent if event type is not click', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#sub1">Submenu 1</a>', - ' </div>', - '</div>' - ].join('') - - const triggerDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdown = fixtureEl.querySelector('.dropdown') - - dropdown.addEventListener('hide.bs.dropdown', e => { - expect(e.clickEvent).toBeUndefined() - }) - - dropdown.addEventListener('hidden.bs.dropdown', e => { - expect(e.clickEvent).toBeUndefined() - done() - }) - - dropdown.addEventListener('shown.bs.dropdown', () => { - const keyDown = createEvent('keydown') - - keyDown.which = 27 - triggerDropdown.dispatchEvent(keyDown) - }) - - triggerDropdown.click() - }) - - it('should ignore keyboard events within <input>s and <textarea>s', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#sub1">Submenu 1</a>', - ' <input type="text" />', - ' <textarea></textarea>', - ' </div>', - '</div>' - ].join('') - - const triggerDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdown = fixtureEl.querySelector('.dropdown') - const input = fixtureEl.querySelector('input') - const textarea = fixtureEl.querySelector('textarea') - - dropdown.addEventListener('shown.bs.dropdown', () => { - input.focus() - const keyDown = createEvent('keydown') - - keyDown.which = 38 - input.dispatchEvent(keyDown) - - expect(document.activeElement).toEqual(input, 'input still focused') - - textarea.focus() - textarea.dispatchEvent(keyDown) - - expect(document.activeElement).toEqual(textarea, 'textarea still focused') - done() - }) - - triggerDropdown.click() - }) - - it('should skip disabled element when using keyboard navigation', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item disabled" href="#sub1">Submenu 1</a>', - ' <button class="dropdown-item" type="button" disabled>Disabled button</button>', - ' <a id="item1" class="dropdown-item" href="#">Another link</a>', - ' </div>', - '</div>' - ].join('') - - const triggerDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdown = fixtureEl.querySelector('.dropdown') - - dropdown.addEventListener('shown.bs.dropdown', () => { - const keyDown = createEvent('keydown') - keyDown.which = 40 - - triggerDropdown.dispatchEvent(keyDown) - triggerDropdown.dispatchEvent(keyDown) - - expect(document.activeElement.classList.contains('disabled')).toEqual(false, '.disabled not focused') - expect(document.activeElement.hasAttribute('disabled')).toEqual(false, ':disabled not focused') - done() - }) - - triggerDropdown.click() - }) - - it('should focus next/previous element when using keyboard navigation', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a id="item1" class="dropdown-item" href="#">A link</a>', - ' <a id="item2" class="dropdown-item" href="#">Another link</a>', - ' </div>', - '</div>' - ].join('') - - const triggerDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdown = fixtureEl.querySelector('.dropdown') - const item1 = fixtureEl.querySelector('#item1') - const item2 = fixtureEl.querySelector('#item2') - - dropdown.addEventListener('shown.bs.dropdown', () => { - const keyDown40 = createEvent('keydown') - keyDown40.which = 40 - - triggerDropdown.dispatchEvent(keyDown40) - expect(document.activeElement).toEqual(item1, 'item1 is focused') - - document.activeElement.dispatchEvent(keyDown40) - expect(document.activeElement).toEqual(item2, 'item2 is focused') - - const keyDown38 = createEvent('keydown') - keyDown38.which = 38 - - document.activeElement.dispatchEvent(keyDown38) - expect(document.activeElement).toEqual(item1, 'item1 is focused') - - done() - }) - - triggerDropdown.click() - }) - - it('should not close the dropdown if the user clicks on a text field', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <input type="text" />', - ' </div>', - '</div>' - ].join('') - - const triggerDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdown = fixtureEl.querySelector('.dropdown') - const input = fixtureEl.querySelector('input') - - input.addEventListener('click', () => { - expect(dropdown.classList.contains('show')).toEqual(true, 'dropdown menu is shown') - done() - }) - - dropdown.addEventListener('shown.bs.dropdown', () => { - expect(dropdown.classList.contains('show')).toEqual(true, 'dropdown menu is shown') - input.dispatchEvent(createEvent('click')) - }) - - triggerDropdown.click() - }) - - it('should not close the dropdown if the user clicks on a textarea', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <textarea></textarea>', - ' </div>', - '</div>' - ].join('') - - const triggerDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdown = fixtureEl.querySelector('.dropdown') - const textarea = fixtureEl.querySelector('textarea') - - textarea.addEventListener('click', () => { - expect(dropdown.classList.contains('show')).toEqual(true, 'dropdown menu is shown') - done() - }) - - dropdown.addEventListener('shown.bs.dropdown', () => { - expect(dropdown.classList.contains('show')).toEqual(true, 'dropdown menu is shown') - textarea.dispatchEvent(createEvent('click')) - }) - - triggerDropdown.click() - }) - - it('should ignore keyboard events for <input>s and <textarea>s within dropdown-menu, except for escape key', done => { - fixtureEl.innerHTML = [ - '<div class="dropdown">', - ' <button href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#sub1">Submenu 1</a>', - ' <input type="text" />', - ' <textarea></textarea>', - ' </div>', - '</div>' - ].join('') - - const triggerDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdown = fixtureEl.querySelector('.dropdown') - const input = fixtureEl.querySelector('input') - const textarea = fixtureEl.querySelector('textarea') - - // Space key - const keyDownSpace = createEvent('keydown') - keyDownSpace.which = 32 - - // Key up - const keyDownUp = createEvent('keydown') - keyDownSpace.which = 38 - - // Key down - const keyDown = createEvent('keydown') - keyDownSpace.which = 40 - - // Key escape - const keyDownEscape = createEvent('keydown') - keyDownEscape.which = 27 - - dropdown.addEventListener('shown.bs.dropdown', () => { - // Space key - input.focus() - input.dispatchEvent(keyDownSpace) - - expect(document.activeElement).toEqual(input, 'input still focused') - - textarea.focus() - textarea.dispatchEvent(keyDownSpace) - - expect(document.activeElement).toEqual(textarea, 'textarea still focused') - - // Key up - input.focus() - input.dispatchEvent(keyDownUp) - - expect(document.activeElement).toEqual(input, 'input still focused') - - textarea.focus() - textarea.dispatchEvent(keyDownUp) - - expect(document.activeElement).toEqual(textarea, 'textarea still focused') - - // Key down - input.focus() - input.dispatchEvent(keyDown) - - expect(document.activeElement).toEqual(input, 'input still focused') - - textarea.focus() - textarea.dispatchEvent(keyDown) - - expect(document.activeElement).toEqual(textarea, 'textarea still focused') - - // Key escape - input.focus() - input.dispatchEvent(keyDownEscape) - - expect(dropdown.classList.contains('show')).toEqual(false, 'dropdown menu is not shown') - done() - }) - - triggerDropdown.click() - }) - - it('should not open dropdown if escape key was pressed on the toggle', done => { - fixtureEl.innerHTML = [ - '<div class="tabs">', - ' <div class="dropdown">', - ' <button disabled href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item" href="#">Secondary link</a>', - ' <a class="dropdown-item" href="#">Something else here</a>', - ' <div class="divider"/>', - ' <a class="dropdown-item" href="#">Another link</a>', - ' </div>', - ' </div>', - '</div>' - ] - - const triggerDropdown = fixtureEl.querySelector('[data-toggle="dropdown"]') - const dropdown = new Dropdown(triggerDropdown) - const button = fixtureEl.querySelector('button[data-toggle="dropdown"]') - - spyOn(dropdown, 'toggle') - - // Key escape - button.focus() - // Key escape - const keyDownEscape = createEvent('keydown') - keyDownEscape.which = 27 - button.dispatchEvent(keyDownEscape) - - setTimeout(() => { - expect(dropdown.toggle).not.toHaveBeenCalled() - expect(triggerDropdown.parentNode.classList.contains('show')).toEqual(false) - done() - }, 20) - }) - }) - - describe('jQueryInterface', () => { - it('should create a dropdown', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - - jQueryMock.fn.dropdown = Dropdown.jQueryInterface - jQueryMock.elements = [div] - - jQueryMock.fn.dropdown.call(jQueryMock) - - expect(Dropdown.getInstance(div)).toBeDefined() - }) - - it('should not re create a dropdown', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - const dropdown = new Dropdown(div) - - jQueryMock.fn.dropdown = Dropdown.jQueryInterface - jQueryMock.elements = [div] - - jQueryMock.fn.dropdown.call(jQueryMock) - - expect(Dropdown.getInstance(div)).toEqual(dropdown) - }) - - it('should throw error on undefined method', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - const action = 'undefinedMethod' - - jQueryMock.fn.dropdown = Dropdown.jQueryInterface - jQueryMock.elements = [div] - - try { - jQueryMock.fn.dropdown.call(jQueryMock, action) - } catch (error) { - expect(error.message).toEqual(`No method named "${action}"`) - } - }) - }) - - describe('getInstance', () => { - it('should return dropdown instance', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - const dropdown = new Dropdown(div) - - expect(Dropdown.getInstance(div)).toEqual(dropdown) - }) - - it('should return null when there is no dropdown instance', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - - expect(Dropdown.getInstance(div)).toEqual(null) - }) - }) -}) diff --git a/js/src/modal/modal.js b/js/src/modal.js index 4864cad9d..bee5e23f8 100644 --- a/js/src/modal/modal.js +++ b/js/src/modal.js @@ -15,11 +15,11 @@ import { makeArray, reflow, typeCheckConfig -} from '../util/index' -import Data from '../dom/data' -import EventHandler from '../dom/event-handler' -import Manipulator from '../dom/manipulator' -import SelectorEngine from '../dom/selector-engine' +} from './util/index' +import Data from './dom/data' +import EventHandler from './dom/event-handler' +import Manipulator from './dom/manipulator' +import SelectorEngine from './dom/selector-engine' /** * ------------------------------------------------------------------------ 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) - }) - }) -}) diff --git a/js/src/popover/popover.js b/js/src/popover.js index 5da7ffe56..a633af4ba 100644 --- a/js/src/popover/popover.js +++ b/js/src/popover.js @@ -5,10 +5,10 @@ * -------------------------------------------------------------------------- */ -import { getjQuery } from '../util/index' -import Data from '../dom/data' -import SelectorEngine from '../dom/selector-engine' -import Tooltip from '../tooltip/tooltip' +import { getjQuery } from './util/index' +import Data from './dom/data' +import SelectorEngine from './dom/selector-engine' +import Tooltip from './tooltip' /** * ------------------------------------------------------------------------ diff --git a/js/src/popover/popover.spec.js b/js/src/popover/popover.spec.js deleted file mode 100644 index cf82e36ef..000000000 --- a/js/src/popover/popover.spec.js +++ /dev/null @@ -1,251 +0,0 @@ -import Popover from './popover' -import { makeArray } from '../util/index' - -/** Test helpers */ -import { getFixture, clearFixture, jQueryMock } from '../../tests/helpers/fixture' - -describe('Popover', () => { - let fixtureEl - - beforeAll(() => { - fixtureEl = getFixture() - }) - - afterEach(() => { - clearFixture() - - const popoverList = makeArray(document.querySelectorAll('.popover')) - - popoverList.forEach(popoverEl => { - document.body.removeChild(popoverEl) - }) - }) - - describe('VERSION', () => { - it('should return plugin version', () => { - expect(Popover.VERSION).toEqual(jasmine.any(String)) - }) - }) - - describe('Default', () => { - it('should return plugin default config', () => { - expect(Popover.Default).toEqual(jasmine.any(Object)) - }) - }) - - describe('NAME', () => { - it('should return plugin name', () => { - expect(Popover.NAME).toEqual(jasmine.any(String)) - }) - }) - - describe('DATA_KEY', () => { - it('should return plugin data key', () => { - expect(Popover.DATA_KEY).toEqual('bs.popover') - }) - }) - - describe('Event', () => { - it('should return plugin events', () => { - expect(Popover.Event).toEqual(jasmine.any(Object)) - }) - }) - - describe('EVENT_KEY', () => { - it('should return plugin event key', () => { - expect(Popover.EVENT_KEY).toEqual('.bs.popover') - }) - }) - - describe('DefaultType', () => { - it('should return plugin default type', () => { - expect(Popover.DefaultType).toEqual(jasmine.any(Object)) - }) - }) - - describe('show', () => { - it('should show a popover', done => { - fixtureEl.innerHTML = '<a href="#" title="Popover" data-content="https://twitter.com/getbootstrap">BS twitter</a>' - - const popoverEl = fixtureEl.querySelector('a') - const popover = new Popover(popoverEl) - - popoverEl.addEventListener('shown.bs.popover', () => { - expect(document.querySelector('.popover')).toBeDefined() - done() - }) - - popover.show() - }) - - it('should set title and content from functions', done => { - fixtureEl.innerHTML = '<a href="#">BS twitter</a>' - - const popoverEl = fixtureEl.querySelector('a') - const popover = new Popover(popoverEl, { - title: () => 'Bootstrap', - content: () => 'loves writing tests (╯°□°)╯︵ ┻━┻' - }) - - popoverEl.addEventListener('shown.bs.popover', () => { - const popoverDisplayed = document.querySelector('.popover') - - expect(popoverDisplayed).toBeDefined() - expect(popoverDisplayed.querySelector('.popover-header').textContent).toEqual('Bootstrap') - expect(popoverDisplayed.querySelector('.popover-body').textContent).toEqual('loves writing tests (╯°□°)╯︵ ┻━┻') - done() - }) - - popover.show() - }) - - it('should show a popover with just content', done => { - fixtureEl.innerHTML = '<a href="#">BS twitter</a>' - - const popoverEl = fixtureEl.querySelector('a') - const popover = new Popover(popoverEl, { - content: 'Popover content' - }) - - popoverEl.addEventListener('shown.bs.popover', () => { - const popoverDisplayed = document.querySelector('.popover') - - expect(popoverDisplayed).toBeDefined() - expect(popoverDisplayed.querySelector('.popover-body').textContent).toEqual('Popover content') - done() - }) - - popover.show() - }) - }) - - describe('hide', () => { - it('should hide a popover', done => { - fixtureEl.innerHTML = '<a href="#" title="Popover" data-content="https://twitter.com/getbootstrap">BS twitter</a>' - - const popoverEl = fixtureEl.querySelector('a') - const popover = new Popover(popoverEl) - - popoverEl.addEventListener('shown.bs.popover', () => { - popover.hide() - }) - - popoverEl.addEventListener('hidden.bs.popover', () => { - expect(document.querySelector('.popover')).toBeNull() - done() - }) - - popover.show() - }) - }) - - describe('jQueryInterface', () => { - it('should create a popover', () => { - fixtureEl.innerHTML = '<a href="#" title="Popover" data-content="https://twitter.com/getbootstrap">BS twitter</a>' - - const popoverEl = fixtureEl.querySelector('a') - - jQueryMock.fn.popover = Popover.jQueryInterface - jQueryMock.elements = [popoverEl] - - jQueryMock.fn.popover.call(jQueryMock) - - expect(Popover.getInstance(popoverEl)).toBeDefined() - }) - - it('should create a popover with a config object', () => { - fixtureEl.innerHTML = '<a href="#" title="Popover">BS twitter</a>' - - const popoverEl = fixtureEl.querySelector('a') - - jQueryMock.fn.popover = Popover.jQueryInterface - jQueryMock.elements = [popoverEl] - - jQueryMock.fn.popover.call(jQueryMock, { - content: 'Popover content' - }) - - expect(Popover.getInstance(popoverEl)).toBeDefined() - }) - - it('should not re create a popover', () => { - fixtureEl.innerHTML = '<a href="#" title="Popover" data-content="https://twitter.com/getbootstrap">BS twitter</a>' - - const popoverEl = fixtureEl.querySelector('a') - const popover = new Popover(popoverEl) - - jQueryMock.fn.popover = Popover.jQueryInterface - jQueryMock.elements = [popoverEl] - - jQueryMock.fn.popover.call(jQueryMock) - - expect(Popover.getInstance(popoverEl)).toEqual(popover) - }) - - it('should throw error on undefined method', () => { - fixtureEl.innerHTML = '<a href="#" title="Popover" data-content="https://twitter.com/getbootstrap">BS twitter</a>' - - const popoverEl = fixtureEl.querySelector('a') - const action = 'undefinedMethod' - - jQueryMock.fn.popover = Popover.jQueryInterface - jQueryMock.elements = [popoverEl] - - try { - jQueryMock.fn.popover.call(jQueryMock, action) - } catch (error) { - expect(error.message).toEqual(`No method named "${action}"`) - } - }) - - it('should should call show method', () => { - fixtureEl.innerHTML = '<a href="#" title="Popover" data-content="https://twitter.com/getbootstrap">BS twitter</a>' - - const popoverEl = fixtureEl.querySelector('a') - const popover = new Popover(popoverEl) - - jQueryMock.fn.popover = Popover.jQueryInterface - jQueryMock.elements = [popoverEl] - - spyOn(popover, 'show') - - jQueryMock.fn.popover.call(jQueryMock, 'show') - - expect(popover.show).toHaveBeenCalled() - }) - - it('should do nothing if dipose is called when a popover do not exist', () => { - fixtureEl.innerHTML = '<a href="#" title="Popover" data-content="https://twitter.com/getbootstrap">BS twitter</a>' - - const popoverEl = fixtureEl.querySelector('a') - - jQueryMock.fn.popover = Popover.jQueryInterface - jQueryMock.elements = [popoverEl] - - spyOn(Popover.prototype, 'dispose') - - jQueryMock.fn.popover.call(jQueryMock, 'dispose') - - expect(Popover.prototype.dispose).not.toHaveBeenCalled() - }) - }) - - describe('getInstance', () => { - it('should return popover instance', () => { - fixtureEl.innerHTML = '<a href="#" title="Popover" data-content="https://twitter.com/getbootstrap">BS twitter</a>' - - const popoverEl = fixtureEl.querySelector('a') - const popover = new Popover(popoverEl) - - expect(Popover.getInstance(popoverEl)).toEqual(popover) - }) - - it('should return null when there is no popover instance', () => { - fixtureEl.innerHTML = '<a href="#" title="Popover" data-content="https://twitter.com/getbootstrap">BS twitter</a>' - - const popoverEl = fixtureEl.querySelector('a') - - expect(Popover.getInstance(popoverEl)).toEqual(null) - }) - }) -}) diff --git a/js/src/scrollspy/scrollspy.js b/js/src/scrollspy.js index fa52a1289..c379c6223 100644 --- a/js/src/scrollspy/scrollspy.js +++ b/js/src/scrollspy.js @@ -11,11 +11,11 @@ import { getUID, makeArray, typeCheckConfig -} from '../util/index' -import Data from '../dom/data' -import EventHandler from '../dom/event-handler' -import Manipulator from '../dom/manipulator' -import SelectorEngine from '../dom/selector-engine' +} from './util/index' +import Data from './dom/data' +import EventHandler from './dom/event-handler' +import Manipulator from './dom/manipulator' +import SelectorEngine from './dom/selector-engine' /** * ------------------------------------------------------------------------ diff --git a/js/src/scrollspy/scrollspy.spec.js b/js/src/scrollspy/scrollspy.spec.js deleted file mode 100644 index a019a6d1a..000000000 --- a/js/src/scrollspy/scrollspy.spec.js +++ /dev/null @@ -1,653 +0,0 @@ -import ScrollSpy from './scrollspy' -import Manipulator from '../dom/manipulator' -import EventHandler from '../dom/event-handler' - -/** Test helpers */ -import { getFixture, clearFixture, createEvent, jQueryMock } from '../../tests/helpers/fixture' - -describe('ScrollSpy', () => { - let fixtureEl - - const testElementIsActiveAfterScroll = ({ elementSelector, targetSelector, contentEl, scrollSpy, spy, cb }) => { - const element = fixtureEl.querySelector(elementSelector) - const target = fixtureEl.querySelector(targetSelector) - - // add top padding to fix Chrome on Android failures - const paddingTop = 5 - const scrollHeight = Math.ceil(contentEl.scrollTop + Manipulator.position(target).top) + paddingTop - - function listener() { - expect(element.classList.contains('active')).toEqual(true) - contentEl.removeEventListener('scroll', listener) - expect(scrollSpy._process).toHaveBeenCalled() - spy.calls.reset() - cb() - } - - contentEl.addEventListener('scroll', listener) - contentEl.scrollTop = scrollHeight - } - - beforeAll(() => { - fixtureEl = getFixture() - }) - - afterEach(() => { - clearFixture() - }) - - describe('VERSION', () => { - it('should return plugin version', () => { - expect(ScrollSpy.VERSION).toEqual(jasmine.any(String)) - }) - }) - - describe('Default', () => { - it('should return plugin default config', () => { - expect(ScrollSpy.Default).toEqual(jasmine.any(Object)) - }) - }) - - describe('constructor', () => { - it('should generate an id when there is not one', () => { - fixtureEl.innerHTML = [ - '<nav></nav>', - '<div class="content"></div>' - ].join('') - - const navEl = fixtureEl.querySelector('nav') - const scrollSpy = new ScrollSpy(fixtureEl.querySelector('.content'), { - target: navEl - }) - - expect(scrollSpy).toBeDefined() - expect(navEl.getAttribute('id')).not.toEqual(null) - }) - - it('should not process element without target', () => { - fixtureEl.innerHTML = [ - '<nav id="navigation" class="navbar">', - ' <ul class="navbar-nav">', - ' <li class="nav-item active"><a class="nav-link" id="one-link" href="#">One</a></li>', - ' <li class="nav-item"><a class="nav-link" id="two-link" href="#two">Two</a></li>', - ' <li class="nav-item"><a class="nav-link" id="three-link" href="#three">Three</a></li>', - ' </ul>', - '</nav>', - '<div id="content" style="height: 200px; overflow-y: auto;">', - ' <div id="two" style="height: 300px;"></div>', - ' <div id="three" style="height: 10px;"></div>', - '</div>' - ].join('') - - const scrollSpy = new ScrollSpy(fixtureEl.querySelector('#content'), { - target: '#navigation' - }) - - expect(scrollSpy._targets.length).toEqual(2) - }) - - it('should only switch "active" class on current target', done => { - fixtureEl.innerHTML = [ - '<div id="root" class="active" style="display: block">', - ' <div class="topbar">', - ' <div class="topbar-inner">', - ' <div class="container" id="ss-target">', - ' <ul class="nav">', - ' <li class="nav-item"><a href="#masthead">Overview</a></li>', - ' <li class="nav-item"><a href="#detail">Detail</a></li>', - ' </ul>', - ' </div>', - ' </div>', - ' </div>', - ' <div id="scrollspy-example" style="height: 100px; overflow: auto;">', - ' <div style="height: 200px;">', - ' <h4 id="masthead">Overview</h4>', - ' <p style="height: 200px;"></p>', - ' </div>', - ' <div style="height: 200px;">', - ' <h4 id="detail">Detail</h4>', - ' <p style="height: 200px;"></p>', - ' </div>', - ' </div>', - '</div>' - ].join('') - - const scrollSpyEl = fixtureEl.querySelector('#scrollspy-example') - const rootEl = fixtureEl.querySelector('#root') - const scrollSpy = new ScrollSpy(scrollSpyEl, { - target: 'ss-target' - }) - - spyOn(scrollSpy, '_process').and.callThrough() - - scrollSpyEl.addEventListener('scroll', () => { - expect(rootEl.classList.contains('active')).toEqual(true) - expect(scrollSpy._process).toHaveBeenCalled() - done() - }) - - scrollSpyEl.scrollTop = 350 - }) - - it('should only switch "active" class on current target specified w element', done => { - fixtureEl.innerHTML = [ - '<div id="root" class="active" style="display: block">', - ' <div class="topbar">', - ' <div class="topbar-inner">', - ' <div class="container" id="ss-target">', - ' <ul class="nav">', - ' <li class="nav-item"><a href="#masthead">Overview</a></li>', - ' <li class="nav-item"><a href="#detail">Detail</a></li>', - ' </ul>', - ' </div>', - ' </div>', - ' </div>', - ' <div id="scrollspy-example" style="height: 100px; overflow: auto;">', - ' <div style="height: 200px;">', - ' <h4 id="masthead">Overview</h4>', - ' <p style="height: 200px;"></p>', - ' </div>', - ' <div style="height: 200px;">', - ' <h4 id="detail">Detail</h4>', - ' <p style="height: 200px;"></p>', - ' </div>', - ' </div>', - '</div>' - ].join('') - - const scrollSpyEl = fixtureEl.querySelector('#scrollspy-example') - const rootEl = fixtureEl.querySelector('#root') - const scrollSpy = new ScrollSpy(scrollSpyEl, { - target: fixtureEl.querySelector('#ss-target') - }) - - spyOn(scrollSpy, '_process').and.callThrough() - - scrollSpyEl.addEventListener('scroll', () => { - expect(rootEl.classList.contains('active')).toEqual(true) - expect(scrollSpy._process).toHaveBeenCalled() - done() - }) - - scrollSpyEl.scrollTop = 350 - }) - - it('should correctly select middle navigation option when large offset is used', done => { - fixtureEl.innerHTML = [ - '<div id="header" style="height: 500px;"></div>', - '<nav id="navigation" class="navbar">', - ' <ul class="navbar-nav">', - ' <li class="nav-item active"><a class="nav-link" id="one-link" href="#one">One</a></li>', - ' <li class="nav-item"><a class="nav-link" id="two-link" href="#two">Two</a></li>', - ' <li class="nav-item"><a class="nav-link" id="three-link" href="#three">Three</a></li>', - ' </ul>', - '</nav>', - '<div id="content" style="height: 200px; overflow-y: auto;">', - ' <div id="one" style="height: 500px;"></div>', - ' <div id="two" style="height: 300px;"></div>', - ' <div id="three" style="height: 10px;"></div>', - '</div>' - ].join('') - - const contentEl = fixtureEl.querySelector('#content') - const scrollSpy = new ScrollSpy(contentEl, { - target: '#navigation', - offset: Manipulator.position(contentEl).top - }) - - spyOn(scrollSpy, '_process').and.callThrough() - - contentEl.addEventListener('scroll', () => { - expect(fixtureEl.querySelector('#one-link').classList.contains('active')).toEqual(false) - expect(fixtureEl.querySelector('#two-link').classList.contains('active')).toEqual(true) - expect(fixtureEl.querySelector('#three-link').classList.contains('active')).toEqual(false) - expect(scrollSpy._process).toHaveBeenCalled() - done() - }) - - contentEl.scrollTop = 550 - }) - - it('should add the active class to the correct element', done => { - fixtureEl.innerHTML = [ - '<nav class="navbar">', - ' <ul class="nav">', - ' <li class="nav-item"><a class="nav-link" id="a-1" href="#div-1">div 1</a></li>', - ' <li class="nav-item"><a class="nav-link" id="a-2" href="#div-2">div 2</a></li>', - ' </ul>', - '</nav>', - '<div class="content" style="overflow: auto; height: 50px">', - ' <div id="div-1" style="height: 100px; padding: 0; margin: 0">div 1</div>', - ' <div id="div-2" style="height: 200px; padding: 0; margin: 0">div 2</div>', - '</div>' - ].join('') - - const contentEl = fixtureEl.querySelector('.content') - const scrollSpy = new ScrollSpy(contentEl, { - offset: 0, - target: '.navbar' - }) - const spy = spyOn(scrollSpy, '_process').and.callThrough() - - testElementIsActiveAfterScroll({ - elementSelector: '#a-1', - targetSelector: '#div-1', - contentEl, - scrollSpy, - spy, - cb: () => { - testElementIsActiveAfterScroll({ - elementSelector: '#a-2', - targetSelector: '#div-2', - contentEl, - scrollSpy, - spy, - cb: () => done() - }) - } - }) - }) - - it('should add the active class to the correct element (nav markup)', done => { - fixtureEl.innerHTML = [ - '<nav class="navbar">', - ' <nav class="nav">', - ' <a class="nav-link" id="a-1" href="#div-1">div 1</a>', - ' <a class="nav-link" id="a-2" href="#div-2">div 2</a>', - ' </nav>', - '</nav>', - '<div class="content" style="overflow: auto; height: 50px">', - ' <div id="div-1" style="height: 100px; padding: 0; margin: 0">div 1</div>', - ' <div id="div-2" style="height: 200px; padding: 0; margin: 0">div 2</div>', - '</div>' - ].join('') - - const contentEl = fixtureEl.querySelector('.content') - const scrollSpy = new ScrollSpy(contentEl, { - offset: 0, - target: '.navbar' - }) - const spy = spyOn(scrollSpy, '_process').and.callThrough() - - testElementIsActiveAfterScroll({ - elementSelector: '#a-1', - targetSelector: '#div-1', - contentEl, - scrollSpy, - spy, - cb: () => { - testElementIsActiveAfterScroll({ - elementSelector: '#a-2', - targetSelector: '#div-2', - contentEl, - scrollSpy, - spy, - cb: () => done() - }) - } - }) - }) - - it('should add the active class to the correct element (list-group markup)', done => { - fixtureEl.innerHTML = [ - '<nav class="navbar">', - ' <div class="list-group">', - ' <a class="list-group-item" id="a-1" href="#div-1">div 1</a>', - ' <a class="list-group-item" id="a-2" href="#div-2">div 2</a>', - ' </div>', - '</nav>', - '<div class="content" style="overflow: auto; height: 50px">', - ' <div id="div-1" style="height: 100px; padding: 0; margin: 0">div 1</div>', - ' <div id="div-2" style="height: 200px; padding: 0; margin: 0">div 2</div>', - '</div>' - ].join('') - - const contentEl = fixtureEl.querySelector('.content') - const scrollSpy = new ScrollSpy(contentEl, { - offset: 0, - target: '.navbar' - }) - const spy = spyOn(scrollSpy, '_process').and.callThrough() - - testElementIsActiveAfterScroll({ - elementSelector: '#a-1', - targetSelector: '#div-1', - contentEl, - scrollSpy, - spy, - cb: () => { - testElementIsActiveAfterScroll({ - elementSelector: '#a-2', - targetSelector: '#div-2', - contentEl, - scrollSpy, - spy, - cb: () => done() - }) - } - }) - }) - - it('should clear selection if above the first section', done => { - fixtureEl.innerHTML = [ - '<div id="header" style="height: 500px;"></div>', - '<nav id="navigation" class="navbar">', - ' <ul class="navbar-nav">', - ' <li class="nav-item"><a id="one-link" class="nav-link active" href="#one">One</a></li>', - ' <li class="nav-item"><a id="two-link" class="nav-link" href="#two">Two</a></li>', - ' <li class="nav-item"><a id="three-link" class="nav-link" href="#three">Three</a></li>', - ' </ul>', - '</nav>', - '<div id="content" style="height: 200px; overflow-y: auto;">', - ' <div id="spacer" style="height: 100px;"></div>', - ' <div id="one" style="height: 100px;"></div>', - ' <div id="two" style="height: 100px;"></div>', - ' <div id="three" style="height: 100px;"></div>', - ' <div id="spacer" style="height: 100px;"></div>', - '</div>' - ].join('') - - const contentEl = fixtureEl.querySelector('#content') - const scrollSpy = new ScrollSpy(contentEl, { - target: '#navigation', - offset: Manipulator.position(contentEl).top - }) - const spy = spyOn(scrollSpy, '_process').and.callThrough() - - let firstTime = true - - contentEl.addEventListener('scroll', () => { - const active = fixtureEl.querySelector('.active') - - expect(spy).toHaveBeenCalled() - spy.calls.reset() - if (firstTime) { - expect(fixtureEl.querySelectorAll('.active').length).toEqual(1) - expect(active.getAttribute('id')).toEqual('two-link') - firstTime = false - contentEl.scrollTop = 0 - } else { - expect(active).toBeNull() - done() - } - }) - - contentEl.scrollTop = 201 - }) - - it('should not clear selection if above the first section and first section is at the top', done => { - fixtureEl.innerHTML = [ - '<div id="header" style="height: 500px;"></div>', - '<nav id="navigation" class="navbar">', - ' <ul class="navbar-nav">', - ' <li class="nav-item"><a id="one-link" class="nav-link active" href="#one">One</a></li>', - ' <li class="nav-item"><a id="two-link" class="nav-link" href="#two">Two</a></li>', - ' <li class="nav-item"><a id="three-link" class="nav-link" href="#three">Three</a></li>', - ' </ul>', - '</nav>', - '<div id="content" style="height: 200px; overflow-y: auto;">', - ' <div id="one" style="height: 100px;"></div>', - ' <div id="two" style="height: 100px;"></div>', - ' <div id="three" style="height: 100px;"></div>', - ' <div id="spacer" style="height: 100px;"></div>', - '</div>' - ].join('') - - const negativeHeight = -10 - const startOfSectionTwo = 101 - const contentEl = fixtureEl.querySelector('#content') - const scrollSpy = new ScrollSpy(contentEl, { - target: '#navigation', - offset: contentEl.offsetTop - }) - const spy = spyOn(scrollSpy, '_process').and.callThrough() - - let firstTime = true - - contentEl.addEventListener('scroll', () => { - const active = fixtureEl.querySelector('.active') - - expect(spy).toHaveBeenCalled() - spy.calls.reset() - if (firstTime) { - expect(fixtureEl.querySelectorAll('.active').length).toEqual(1) - expect(active.getAttribute('id')).toEqual('two-link') - firstTime = false - contentEl.scrollTop = negativeHeight - } else { - expect(fixtureEl.querySelectorAll('.active').length).toEqual(1) - expect(active.getAttribute('id')).toEqual('one-link') - done() - } - }) - - contentEl.scrollTop = startOfSectionTwo - }) - - it('should correctly select navigation element on backward scrolling when each target section height is 100%', done => { - fixtureEl.innerHTML = [ - '<nav class="navbar">', - ' <ul class="nav">', - ' <li class="nav-item"><a id="li-100-1" class="nav-link" href="#div-100-1">div 1</a></li>', - ' <li class="nav-item"><a id="li-100-2" class="nav-link" href="#div-100-2">div 2</a></li>', - ' <li class="nav-item"><a id="li-100-3" class="nav-link" href="#div-100-3">div 3</a></li>', - ' <li class="nav-item"><a id="li-100-4" class="nav-link" href="#div-100-4">div 4</a></li>', - ' <li class="nav-item"><a id="li-100-5" class="nav-link" href="#div-100-5">div 5</a></li>', - ' </ul>', - '</nav>', - '<div class="content" style="position: relative; overflow: auto; height: 100px">', - ' <div id="div-100-1" style="position: relative; height: 100%; padding: 0; margin: 0">div 1</div>', - ' <div id="div-100-2" style="position: relative; height: 100%; padding: 0; margin: 0">div 2</div>', - ' <div id="div-100-3" style="position: relative; height: 100%; padding: 0; margin: 0">div 3</div>', - ' <div id="div-100-4" style="position: relative; height: 100%; padding: 0; margin: 0">div 4</div>', - ' <div id="div-100-5" style="position: relative; height: 100%; padding: 0; margin: 0">div 5</div>', - '</div>' - ].join('') - - const contentEl = fixtureEl.querySelector('.content') - const scrollSpy = new ScrollSpy(contentEl, { - offset: 0, - target: '.navbar' - }) - const spy = spyOn(scrollSpy, '_process').and.callThrough() - - testElementIsActiveAfterScroll({ - elementSelector: '#li-100-5', - targetSelector: '#div-100-5', - scrollSpy, - spy, - contentEl, - cb() { - contentEl.scrollTop = 0 - testElementIsActiveAfterScroll({ - elementSelector: '#li-100-4', - targetSelector: '#div-100-4', - scrollSpy, - spy, - contentEl, - cb() { - contentEl.scrollTop = 0 - testElementIsActiveAfterScroll({ - elementSelector: '#li-100-3', - targetSelector: '#div-100-3', - scrollSpy, - spy, - contentEl, - cb() { - contentEl.scrollTop = 0 - testElementIsActiveAfterScroll({ - elementSelector: '#li-100-2', - targetSelector: '#div-100-2', - scrollSpy, - spy, - contentEl, - cb() { - contentEl.scrollTop = 0 - testElementIsActiveAfterScroll({ - elementSelector: '#li-100-1', - targetSelector: '#div-100-1', - scrollSpy, - spy, - contentEl, - cb: done - }) - } - }) - } - }) - } - }) - } - }) - }) - - it('should allow passed in option offset method: offset', () => { - fixtureEl.innerHTML = [ - '<nav class="navbar">', - ' <ul class="nav">', - ' <li class="nav-item"><a id="li-jsm-1" class="nav-link" href="#div-jsm-1">div 1</a></li>', - ' <li class="nav-item"><a id="li-jsm-2" class="nav-link" href="#div-jsm-2">div 2</a></li>', - ' <li class="nav-item"><a id="li-jsm-3" class="nav-link" href="#div-jsm-3">div 3</a></li>', - ' </ul>', - '</nav>', - '<div class="content" style="position: relative; overflow: auto; height: 100px">', - ' <div id="div-jsm-1" style="position: relative; height: 200px; padding: 0; margin: 0">div 1</div>', - ' <div id="div-jsm-2" style="position: relative; height: 150px; padding: 0; margin: 0">div 2</div>', - ' <div id="div-jsm-3" style="position: relative; height: 250px; padding: 0; margin: 0">div 3</div>', - '</div>' - ].join('') - - const contentEl = fixtureEl.querySelector('.content') - const targetEl = fixtureEl.querySelector('#div-jsm-2') - const scrollSpy = new ScrollSpy(contentEl, { - target: '.navbar', - offset: 0, - method: 'offset' - }) - - expect(scrollSpy._offsets[1]).toEqual(Manipulator.offset(targetEl).top) - expect(scrollSpy._offsets[1]).not.toEqual(Manipulator.position(targetEl).top) - }) - - it('should allow passed in option offset method: position', () => { - fixtureEl.innerHTML = [ - '<nav class="navbar">', - ' <ul class="nav">', - ' <li class="nav-item"><a id="li-jsm-1" class="nav-link" href="#div-jsm-1">div 1</a></li>', - ' <li class="nav-item"><a id="li-jsm-2" class="nav-link" href="#div-jsm-2">div 2</a></li>', - ' <li class="nav-item"><a id="li-jsm-3" class="nav-link" href="#div-jsm-3">div 3</a></li>', - ' </ul>', - '</nav>', - '<div class="content" style="position: relative; overflow: auto; height: 100px">', - ' <div id="div-jsm-1" style="position: relative; height: 200px; padding: 0; margin: 0">div 1</div>', - ' <div id="div-jsm-2" style="position: relative; height: 150px; padding: 0; margin: 0">div 2</div>', - ' <div id="div-jsm-3" style="position: relative; height: 250px; padding: 0; margin: 0">div 3</div>', - '</div>' - ].join('') - - const contentEl = fixtureEl.querySelector('.content') - const targetEl = fixtureEl.querySelector('#div-jsm-2') - const scrollSpy = new ScrollSpy(contentEl, { - target: '.navbar', - offset: 0, - method: 'position' - }) - - expect(scrollSpy._offsets[1]).not.toEqual(Manipulator.offset(targetEl).top) - expect(scrollSpy._offsets[1]).toEqual(Manipulator.position(targetEl).top) - }) - }) - - describe('dispose', () => { - it('should dispose a scrollspy', () => { - spyOn(EventHandler, 'off') - fixtureEl.innerHTML = '<div style="display: none;"></div>' - - const divEl = fixtureEl.querySelector('div') - const scrollSpy = new ScrollSpy(divEl) - - scrollSpy.dispose() - expect(EventHandler.off).toHaveBeenCalledWith(divEl, '.bs.scrollspy') - }) - }) - - describe('jQueryInterface', () => { - it('should create a scrollspy', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - - jQueryMock.fn.scrollspy = ScrollSpy.jQueryInterface - jQueryMock.elements = [div] - - jQueryMock.fn.scrollspy.call(jQueryMock) - - expect(ScrollSpy.getInstance(div)).toBeDefined() - }) - - it('should not re create a scrollspy', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - const scrollSpy = new ScrollSpy(div) - - jQueryMock.fn.scrollspy = ScrollSpy.jQueryInterface - jQueryMock.elements = [div] - - jQueryMock.fn.scrollspy.call(jQueryMock) - - expect(ScrollSpy.getInstance(div)).toEqual(scrollSpy) - }) - - it('should call a scrollspy method', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - const scrollSpy = new ScrollSpy(div) - - spyOn(scrollSpy, 'refresh') - - jQueryMock.fn.scrollspy = ScrollSpy.jQueryInterface - jQueryMock.elements = [div] - - jQueryMock.fn.scrollspy.call(jQueryMock, 'refresh') - - expect(ScrollSpy.getInstance(div)).toEqual(scrollSpy) - expect(scrollSpy.refresh).toHaveBeenCalled() - }) - - it('should throw error on undefined method', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - const action = 'undefinedMethod' - - jQueryMock.fn.scrollspy = ScrollSpy.jQueryInterface - jQueryMock.elements = [div] - - try { - jQueryMock.fn.scrollspy.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(ScrollSpy.getInstance(fixtureEl)).toEqual(null) - }) - }) - - describe('event handler', () => { - it('should create scrollspy on window load event', () => { - fixtureEl.innerHTML = '<div data-spy="scroll"></div>' - - const scrollSpyEl = fixtureEl.querySelector('div') - - window.dispatchEvent(createEvent('load')) - - expect(ScrollSpy.getInstance(scrollSpyEl)).not.toBeNull() - }) - }) -}) diff --git a/js/src/tab/tab.js b/js/src/tab.js index b356cc0e2..d9bd1fc05 100644 --- a/js/src/tab/tab.js +++ b/js/src/tab.js @@ -13,10 +13,10 @@ import { getTransitionDurationFromElement, makeArray, reflow -} from '../util/index' -import Data from '../dom/data' -import EventHandler from '../dom/event-handler' -import SelectorEngine from '../dom/selector-engine' +} from './util/index' +import Data from './dom/data' +import EventHandler from './dom/event-handler' +import SelectorEngine from './dom/selector-engine' /** * ------------------------------------------------------------------------ diff --git a/js/src/tab/tab.spec.js b/js/src/tab/tab.spec.js deleted file mode 100644 index 0a678e38e..000000000 --- a/js/src/tab/tab.spec.js +++ /dev/null @@ -1,593 +0,0 @@ -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 = [ - '<ul class="nav">', - ' <li><a href="#home" role="tab">Home</a></li>', - ' <li><a id="triggerProfile" role="tab" href="#profile">Profile</a></li>', - '</ul>', - '<ul><li id="home"/><li id="profile"/></ul>' - ].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 = [ - '<ol class="nav nav-pills">', - ' <li><a href="#home">Home</a></li>', - ' <li><a id="triggerProfile" href="#profile">Profile</a></li>', - '</ol>', - '<ol><li id="home"/><li id="profile"/></ol>' - ].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 = [ - '<nav class="nav">', - ' <a href="#home">Home</a>', - ' <a id="triggerProfile" href="#profile">Profile</a>', - '</nav>', - '<nav><div id="home"></div><div id="profile"></div></nav>' - ].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 = [ - '<div class="list-group">', - ' <a href="#home">Home</a>', - ' <a id="triggerProfile" href="#profile">Profile</a>', - '</div>', - '<nav><div id="home"></div><div id="profile"></div></nav>' - ].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 = '<div class="nav"></div>' - - 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 = [ - '<ul class="nav nav-tabs" role="tablist">', - ' <li class="nav-item"><a href="#home" class="nav-link active" role="tab">Home</a></li>', - ' <li class="nav-item"><a href="#profile" class="nav-link" role="tab">Profile</a></li>', - '</ul>', - '<div class="tab-content">', - ' <div class="tab-pane active" id="home" role="tabpanel"></div>', - ' <div class="tab-pane" id="profile" role="tabpanel"></div>', - '</div>' - ].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 = [ - '<ul class="nav nav-tabs" role="tablist">', - ' <li class="nav-item"><a href="#home" class="nav-link active" role="tab">Home</a></li>', - ' <li class="nav-item"><a href="#profile" class="nav-link disabled" role="tab">Profile</a></li>', - '</ul>', - '<div class="tab-content">', - ' <div class="tab-pane active" id="home" role="tabpanel"></div>', - ' <div class="tab-pane" id="profile" role="tabpanel"></div>', - '</div>' - ].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 = [ - '<ul class="nav nav-tabs" role="tablist">', - ' <li class="nav-item"><a href="#home" class="nav-link active" role="tab">Home</a></li>', - ' <li class="nav-item"><a id="triggerProfile" href="#profile" class="nav-link" role="tab">Profile</a></li>', - '</ul>', - '<div class="tab-content">', - ' <div class="tab-pane active" id="home" role="tabpanel"></div>', - ' <div class="tab-pane" id="profile" role="tabpanel"></div>', - '</div>' - ].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 = [ - '<ul class="nav">', - ' <li><a href="#home">Home</a></li>', - ' <li><a href="#profile">Profile</a></li>', - '</ul>' - ].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 = [ - '<ul class="nav">', - ' <li><a href="#home">Home</a></li>', - ' <li><a href="#profile">Profile</a></li>', - '</ul>' - ].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 = [ - '<ul class="nav nav-tabs" role="tablist">', - ' <li class="nav-item">', - ' <a class="nav-link nav-tab" href="#profile" role="tab" data-toggle="tab">', - ' <button class="close"><span aria-hidden="true">×</span></button>', - ' </a>', - ' </li>', - ' <li class="nav-item">', - ' <a id="secondNav" class="nav-link nav-tab" href="#buzz" role="tab" data-toggle="tab">', - ' <button class="close"><span aria-hidden="true">×</span></button>', - ' </a>', - ' </li>', - ' <li class="nav-item">', - ' <a class="nav-link nav-tab" href="#references" role="tab" data-toggle="tab">', - ' <button id="btnClose" class="close"><span aria-hidden="true">×</span></button>', - ' </a>', - ' </li>', - '</ul>', - '<div class="tab-content">', - ' <div role="tabpanel" class="tab-pane fade show active" id="profile">test 1</div>', - ' <div role="tabpanel" class="tab-pane fade" id="buzz">test 2</div>', - ' <div role="tabpanel" class="tab-pane fade" id="references">test 3</div>', - '</div>' - ].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 = '<div></div>' - - 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 = '<div></div>' - - 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 = '<div></div>' - - 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 = '<div></div>' - - 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 = '<div></div>' - - 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 = '<div></div>' - - const divEl = fixtureEl.querySelector('div') - const tab = new Tab(divEl) - - expect(Tab.getInstance(divEl)).toEqual(tab) - }) - }) - - describe('data-api', () => { - it('should create dynamically a tab', done => { - fixtureEl.innerHTML = [ - '<ul class="nav nav-tabs" role="tablist">', - ' <li class="nav-item"><a href="#home" class="nav-link active" role="tab">Home</a></li>', - ' <li class="nav-item"><a id="triggerProfile" data-toggle="tab" href="#profile" class="nav-link" role="tab">Profile</a></li>', - '</ul>', - '<div class="tab-content">', - ' <div class="tab-pane active" id="home" role="tabpanel"></div>', - ' <div class="tab-pane" id="profile" role="tabpanel"></div>', - '</div>' - ].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 = [ - '<ul class="nav nav-tabs">', - ' <li class="nav-item"><a class="nav-link" href="#home" data-toggle="tab">Home</a></li>', - ' <li class="nav-item"><a class="nav-link" href="#profile" data-toggle="tab">Profile</a></li>', - ' <li class="nav-item dropdown">', - ' <a class="nav-link dropdown-toggle active" data-toggle="dropdown" href="#">Dropdown</>', - ' <div class="dropdown-menu">', - ' <a class="dropdown-item active" href="#dropdown1" id="dropdown1-tab" data-toggle="tab">@fat</a>', - ' <a class="dropdown-item" href="#dropdown2" id="dropdown2-tab" data-toggle="tab">@mdo</a>', - ' </div>', - ' </li>', - '</ul>' - ].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 = [ - '<nav class="nav nav-tabs" role="tablist">', - ' <a id="tab1" href="#x-tab1" class="nav-item nav-link" data-toggle="tab" role="tab" aria-controls="x-tab1">Tab 1</a>', - ' <a href="#x-tab2" class="nav-item nav-link active" data-toggle="tab" role="tab" aria-controls="x-tab2" aria-selected="true">Tab 2</a>', - ' <a href="#x-tab3" class="nav-item nav-link" data-toggle="tab" role="tab" aria-controls="x-tab3">Tab 3</a>', - '</nav>', - '<div class="tab-content">', - ' <div class="tab-pane" id="x-tab1" role="tabpanel">', - ' <nav class="nav nav-tabs" role="tablist">', - ' <a href="#nested-tab1" class="nav-item nav-link active" data-toggle="tab" role="tab" aria-controls="x-tab1" aria-selected="true">Nested Tab 1</a>', - ' <a id="tabNested2" href="#nested-tab2" class="nav-item nav-link" data-toggle="tab" role="tab" aria-controls="x-profile">Nested Tab2</a>', - ' </nav>', - ' <div class="tab-content">', - ' <div class="tab-pane active" id="nested-tab1" role="tabpanel">Nested Tab1 Content</div>', - ' <div class="tab-pane" id="nested-tab2" role="tabpanel">Nested Tab2 Content</div>', - ' </div>', - ' </div>', - ' <div class="tab-pane active" id="x-tab2" role="tabpanel">Tab2 Content</div>', - ' <div class="tab-pane" id="x-tab3" role="tabpanel">Tab3 Content</div>', - '</div>' - ].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 = [ - '<ul class="nav nav-tabs" role="tablist">', - ' <li class="nav-item"><a id="tab-home" href="#home" class="nav-link" data-toggle="tab" role="tab">Home</a></li>', - ' <li class="nav-item"><a id="tab-profile" href="#profile" class="nav-link" data-toggle="tab" role="tab">Profile</a></li>', - '</ul>', - '<div class="tab-content">', - ' <div class="tab-pane fade" id="home" role="tabpanel"></div>', - ' <div class="tab-pane fade" id="profile" role="tabpanel"></div>', - '</div>' - ].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 = [ - '<ul class="nav nav-tabs" role="tablist">', - ' <li class="nav-item">', - ' <a class="nav-link nav-tab" href="#home" role="tab" data-toggle="tab">Home</a>', - ' </li>', - ' <li class="nav-item">', - ' <a id="secondNav" class="nav-link nav-tab" href="#profile" role="tab" data-toggle="tab">Profile</a>', - ' </li>', - '</ul>', - '<div class="tab-content">', - ' <div role="tabpanel" class="tab-pane" id="home">test 1</div>', - ' <div role="tabpanel" class="tab-pane" id="profile">test 2</div>', - '</div>' - ].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 = [ - '<ul class="nav nav-tabs" role="tablist">', - ' <li class="nav-item">', - ' <a class="nav-link nav-tab" href="#home" role="tab" data-toggle="tab">Home</a>', - ' </li>', - ' <li class="nav-item">', - ' <a id="secondNav" class="nav-link nav-tab" href="#profile" role="tab" data-toggle="tab">Profile</a>', - ' </li>', - '</ul>', - '<div class="tab-content">', - ' <div role="tabpanel" class="tab-pane fade" id="home">test 1</div>', - ' <div role="tabpanel" class="tab-pane fade" id="profile">test 2</div>', - '</div>' - ].join('') - - const secondNavEl = fixtureEl.querySelector('#secondNav') - - secondNavEl.addEventListener('shown.bs.tab', () => { - expect(fixtureEl.querySelectorAll('.show').length).toEqual(1) - done() - }) - - secondNavEl.click() - }) - }) -}) diff --git a/js/src/toast/toast.js b/js/src/toast.js index 4de7db1cd..e0d2a8b3f 100644 --- a/js/src/toast/toast.js +++ b/js/src/toast.js @@ -12,10 +12,10 @@ import { getTransitionDurationFromElement, reflow, typeCheckConfig -} from '../util/index' -import Data from '../dom/data' -import EventHandler from '../dom/event-handler' -import Manipulator from '../dom/manipulator' +} from './util/index' +import Data from './dom/data' +import EventHandler from './dom/event-handler' +import Manipulator from './dom/manipulator' /** * ------------------------------------------------------------------------ diff --git a/js/src/toast/toast.spec.js b/js/src/toast/toast.spec.js deleted file mode 100644 index b00b86085..000000000 --- a/js/src/toast/toast.spec.js +++ /dev/null @@ -1,374 +0,0 @@ -import Toast from './toast' - -/** Test helpers */ -import { getFixture, clearFixture, jQueryMock } from '../../tests/helpers/fixture' - -describe('Toast', () => { - let fixtureEl - - beforeAll(() => { - fixtureEl = getFixture() - }) - - afterEach(() => { - clearFixture() - }) - - describe('VERSION', () => { - it('should return plugin version', () => { - expect(Toast.VERSION).toEqual(jasmine.any(String)) - }) - }) - - describe('constructor', () => { - it('should allow to config in js', done => { - fixtureEl.innerHTML = [ - '<div class="toast">', - ' <div class="toast-body">', - ' a simple toast', - ' </div>', - '</div>' - ].join('') - - const toastEl = fixtureEl.querySelector('div') - const toast = new Toast(toastEl, { - delay: 1 - }) - - toastEl.addEventListener('shown.bs.toast', () => { - expect(toastEl.classList.contains('show')).toEqual(true) - done() - }) - - toast.show() - }) - - it('should close toast when close element with data-dismiss attribute is set', done => { - fixtureEl.innerHTML = [ - '<div class="toast" data-delay="1" data-autohide="false" data-animation="false">', - ' <button type="button" class="ml-2 mb-1 close" data-dismiss="toast">', - ' close', - ' </button>', - '</div>' - ].join('') - - const toastEl = fixtureEl.querySelector('div') - const toast = new Toast(toastEl) - - toastEl.addEventListener('shown.bs.toast', () => { - expect(toastEl.classList.contains('show')).toEqual(true) - - const button = toastEl.querySelector('.close') - - button.click() - }) - - toastEl.addEventListener('hidden.bs.toast', () => { - expect(toastEl.classList.contains('show')).toEqual(false) - done() - }) - - toast.show() - }) - }) - - describe('Default', () => { - it('should expose default setting to allow to override them', () => { - const defaultDelay = 1000 - - Toast.Default.delay = defaultDelay - - fixtureEl.innerHTML = [ - '<div class="toast" data-autohide="false" data-animation="false">', - ' <button type="button" class="ml-2 mb-1 close" data-dismiss="toast">', - ' close', - ' </button>', - '</div>' - ].join('') - - const toastEl = fixtureEl.querySelector('div') - const toast = new Toast(toastEl) - - expect(toast._config.delay).toEqual(defaultDelay) - }) - }) - - describe('DefaultType', () => { - it('should expose default setting types for read', () => { - expect(Toast.DefaultType).toEqual(jasmine.any(Object)) - }) - }) - - describe('show', () => { - it('should auto hide', done => { - fixtureEl.innerHTML = [ - '<div class="toast" data-delay="1">', - ' <div class="toast-body">', - ' a simple toast', - ' </div>', - '</div>' - ].join('') - - const toastEl = fixtureEl.querySelector('.toast') - const toast = new Toast(toastEl) - - toastEl.addEventListener('hidden.bs.toast', () => { - expect(toastEl.classList.contains('show')).toEqual(false) - done() - }) - - toast.show() - }) - - it('should not add fade class', done => { - fixtureEl.innerHTML = [ - '<div class="toast" data-delay="1" data-animation="false">', - ' <div class="toast-body">', - ' a simple toast', - ' </div>', - '</div>' - ].join('') - - const toastEl = fixtureEl.querySelector('.toast') - const toast = new Toast(toastEl) - - toastEl.addEventListener('shown.bs.toast', () => { - expect(toastEl.classList.contains('fade')).toEqual(false) - done() - }) - - toast.show() - }) - - it('should not trigger shown if show is prevented', done => { - fixtureEl.innerHTML = [ - '<div class="toast" data-delay="1" data-animation="false">', - ' <div class="toast-body">', - ' a simple toast', - ' </div>', - '</div>' - ].join('') - - const toastEl = fixtureEl.querySelector('.toast') - const toast = new Toast(toastEl) - - const assertDone = () => { - setTimeout(() => { - expect(toastEl.classList.contains('show')).toEqual(false) - done() - }, 20) - } - - toastEl.addEventListener('show.bs.toast', event => { - event.preventDefault() - assertDone() - }) - - toastEl.addEventListener('shown.bs.toast', () => { - throw new Error('shown event should not be triggered if show is prevented') - }) - - toast.show() - }) - }) - - describe('hide', () => { - it('should allow to hide toast manually', done => { - fixtureEl.innerHTML = [ - '<div class="toast" data-delay="1" data-autohide="false">', - ' <div class="toast-body">', - ' a simple toast', - ' </div>', - ' </div>' - ].join('') - - const toastEl = fixtureEl.querySelector('.toast') - const toast = new Toast(toastEl) - - toastEl.addEventListener('shown.bs.toast', () => { - toast.hide() - }) - - toastEl.addEventListener('hidden.bs.toast', () => { - expect(toastEl.classList.contains('show')).toEqual(false) - done() - }) - - toast.show() - }) - - it('should do nothing when we call hide on a non shown toast', () => { - fixtureEl.innerHTML = '<div></div>' - - const toastEl = fixtureEl.querySelector('div') - const toast = new Toast(toastEl) - - spyOn(toastEl.classList, 'contains') - - toast.hide() - - expect(toastEl.classList.contains).toHaveBeenCalled() - }) - - it('should not trigger hidden if hide is prevented', done => { - fixtureEl.innerHTML = [ - '<div class="toast" data-delay="1" data-animation="false">', - ' <div class="toast-body">', - ' a simple toast', - ' </div>', - '</div>' - ].join('') - - const toastEl = fixtureEl.querySelector('.toast') - const toast = new Toast(toastEl) - - const assertDone = () => { - setTimeout(() => { - expect(toastEl.classList.contains('show')).toEqual(true) - done() - }, 20) - } - - toastEl.addEventListener('shown.bs.toast', () => { - toast.hide() - }) - - toastEl.addEventListener('hide.bs.toast', event => { - event.preventDefault() - assertDone() - }) - - toastEl.addEventListener('hidden.bs.toast', () => { - throw new Error('hidden event should not be triggered if hide is prevented') - }) - - toast.show() - }) - }) - - describe('dispose', () => { - it('should allow to destroy toast', () => { - fixtureEl.innerHTML = '<div></div>' - - const toastEl = fixtureEl.querySelector('div') - const toast = new Toast(toastEl) - - expect(Toast.getInstance(toastEl)).toBeDefined() - - toast.dispose() - - expect(Toast.getInstance(toastEl)).toBeNull() - }) - - it('should allow to destroy toast and hide it before that', done => { - fixtureEl.innerHTML = [ - '<div class="toast" data-delay="0" data-autohide="false">', - ' <div class="toast-body">', - ' a simple toast', - ' </div>', - '</div>' - ].join('') - - const toastEl = fixtureEl.querySelector('div') - const toast = new Toast(toastEl) - const expected = () => { - expect(toastEl.classList.contains('show')).toEqual(true) - expect(Toast.getInstance(toastEl)).toBeDefined() - - toast.dispose() - - expect(Toast.getInstance(toastEl)).toBeNull() - expect(toastEl.classList.contains('show')).toEqual(false) - - done() - } - - toastEl.addEventListener('shown.bs.toast', () => { - setTimeout(expected, 1) - }) - - toast.show() - }) - }) - - describe('jQueryInterface', () => { - it('should create a toast', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - - jQueryMock.fn.toast = Toast.jQueryInterface - jQueryMock.elements = [div] - - jQueryMock.fn.toast.call(jQueryMock) - - expect(Toast.getInstance(div)).toBeDefined() - }) - - it('should not re create a toast', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - const toast = new Toast(div) - - jQueryMock.fn.toast = Toast.jQueryInterface - jQueryMock.elements = [div] - - jQueryMock.fn.toast.call(jQueryMock) - - expect(Toast.getInstance(div)).toEqual(toast) - }) - - it('should call a toast method', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - const toast = new Toast(div) - - spyOn(toast, 'show') - - jQueryMock.fn.toast = Toast.jQueryInterface - jQueryMock.elements = [div] - - jQueryMock.fn.toast.call(jQueryMock, 'show') - - expect(Toast.getInstance(div)).toEqual(toast) - expect(toast.show).toHaveBeenCalled() - }) - - it('should throw error on undefined method', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - const action = 'undefinedMethod' - - jQueryMock.fn.toast = Toast.jQueryInterface - jQueryMock.elements = [div] - - try { - jQueryMock.fn.toast.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 toast = new Toast(div) - - expect(Toast.getInstance(div)).toEqual(toast) - }) - - it('should return null when there is no collapse instance', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - - expect(Toast.getInstance(div)).toEqual(null) - }) - }) -}) diff --git a/js/src/tooltip/tooltip.js b/js/src/tooltip.js index 99ac29af6..b4f047b70 100644 --- a/js/src/tooltip/tooltip.js +++ b/js/src/tooltip.js @@ -16,16 +16,16 @@ import { makeArray, noop, typeCheckConfig -} from '../util/index' +} from './util/index' import { DefaultWhitelist, sanitizeHtml -} from '../util/sanitizer' -import Data from '../dom/data' -import EventHandler from '../dom/event-handler' -import Manipulator from '../dom/manipulator' +} from './util/sanitizer' +import Data from './dom/data' +import EventHandler from './dom/event-handler' +import Manipulator from './dom/manipulator' import Popper from 'popper.js' -import SelectorEngine from '../dom/selector-engine' +import SelectorEngine from './dom/selector-engine' /** * ------------------------------------------------------------------------ diff --git a/js/src/tooltip/tooltip.spec.js b/js/src/tooltip/tooltip.spec.js deleted file mode 100644 index a6cbd7847..000000000 --- a/js/src/tooltip/tooltip.spec.js +++ /dev/null @@ -1,1020 +0,0 @@ -import Tooltip from './tooltip' -import EventHandler from '../dom/event-handler' -import { makeArray, noop } from '../util/index' - -/** Test helpers */ -import { getFixture, clearFixture, jQueryMock, createEvent } from '../../tests/helpers/fixture' - -describe('Tooltip', () => { - let fixtureEl - - beforeAll(() => { - fixtureEl = getFixture() - }) - - afterEach(() => { - clearFixture() - - const tooltipList = makeArray(document.querySelectorAll('.tooltip')) - - tooltipList.forEach(tooltipEl => { - document.body.removeChild(tooltipEl) - }) - }) - - describe('VERSION', () => { - it('should return plugin version', () => { - expect(Tooltip.VERSION).toEqual(jasmine.any(String)) - }) - }) - - describe('Default', () => { - it('should return plugin default config', () => { - expect(Tooltip.Default).toEqual(jasmine.any(Object)) - }) - }) - - describe('NAME', () => { - it('should return plugin name', () => { - expect(Tooltip.NAME).toEqual(jasmine.any(String)) - }) - }) - - describe('DATA_KEY', () => { - it('should return plugin data key', () => { - expect(Tooltip.DATA_KEY).toEqual('bs.tooltip') - }) - }) - - describe('Event', () => { - it('should return plugin events', () => { - expect(Tooltip.Event).toEqual(jasmine.any(Object)) - }) - }) - - describe('EVENT_KEY', () => { - it('should return plugin event key', () => { - expect(Tooltip.EVENT_KEY).toEqual('.bs.tooltip') - }) - }) - - describe('DefaultType', () => { - it('should return plugin default type', () => { - expect(Tooltip.DefaultType).toEqual(jasmine.any(Object)) - }) - }) - - describe('constructor', () => { - it('should not take care of disallowed data attributes', () => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-sanitize="false" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl) - - expect(tooltip.config.sanitize).toEqual(true) - }) - - it('should convert title and content to string if numbers', () => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl, { - title: 1, - content: 7 - }) - - expect(tooltip.config.title).toEqual('1') - expect(tooltip.config.content).toEqual('7') - }) - - it('should enable selector delegation', done => { - fixtureEl.innerHTML = '<div></div>' - - const containerEl = fixtureEl.querySelector('div') - const tooltipContainer = new Tooltip(containerEl, { - selector: 'a[rel="tooltip"]', - trigger: 'click' - }) - - containerEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipInContainerEl = containerEl.querySelector('a') - - tooltipInContainerEl.addEventListener('shown.bs.tooltip', () => { - expect(document.querySelector('.tooltip')).not.toBeNull() - tooltipContainer.dispose() - done() - }) - - tooltipInContainerEl.click() - }) - - it('should allow to pass config to popper.js with `popperConfig`', () => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl, { - popperConfig: { - placement: 'left' - } - }) - - const popperConfig = tooltip._getPopperConfig('top') - - expect(popperConfig.placement).toEqual('left') - }) - }) - - describe('enable', () => { - it('should enable a tooltip', done => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl) - - tooltip.enable() - - tooltipEl.addEventListener('shown.bs.tooltip', () => { - expect(document.querySelector('.tooltip')).toBeDefined() - done() - }) - - tooltip.show() - }) - }) - - describe('disable', () => { - it('should disable tooltip', done => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl) - - tooltip.disable() - - tooltipEl.addEventListener('show.bs.tooltip', () => { - throw new Error('should not show a disabled tooltip') - }) - - tooltip.show() - - setTimeout(() => { - expect().nothing() - done() - }, 10) - }) - }) - - describe('toggleEnabled', () => { - it('should toggle enabled', () => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl) - - expect(tooltip._isEnabled).toEqual(true) - - tooltip.toggleEnabled() - - expect(tooltip._isEnabled).toEqual(false) - }) - }) - - describe('toggle', () => { - it('should do nothing if disabled', done => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl) - - tooltip.disable() - - tooltipEl.addEventListener('show.bs.tooltip', () => { - throw new Error('should not show a disabled tooltip') - }) - - tooltip.toggle() - - setTimeout(() => { - expect().nothing() - done() - }, 10) - }) - - it('should show a tooltip', done => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl) - - tooltipEl.addEventListener('shown.bs.tooltip', () => { - expect(document.querySelector('.tooltip')).toBeDefined() - done() - }) - - tooltip.toggle() - }) - - it('should call toggle and show the tooltip when trigger is "click"', done => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl, { - trigger: 'click' - }) - - spyOn(tooltip, 'toggle').and.callThrough() - - tooltipEl.addEventListener('shown.bs.tooltip', () => { - expect(tooltip.toggle).toHaveBeenCalled() - done() - }) - - tooltipEl.click() - }) - - it('should hide a tooltip', done => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl) - - tooltipEl.addEventListener('shown.bs.tooltip', () => { - tooltip.toggle() - }) - - tooltipEl.addEventListener('hidden.bs.tooltip', () => { - expect(document.querySelector('.tooltip')).toBeNull() - done() - }) - - tooltip.toggle() - }) - - it('should call toggle and hide the tooltip when trigger is "click"', done => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl, { - trigger: 'click' - }) - - spyOn(tooltip, 'toggle').and.callThrough() - - tooltipEl.addEventListener('shown.bs.tooltip', () => { - tooltipEl.click() - }) - - tooltipEl.addEventListener('hidden.bs.tooltip', () => { - expect(tooltip.toggle).toHaveBeenCalled() - done() - }) - - tooltipEl.click() - }) - }) - - describe('dispose', () => { - it('should destroy a tooltip', () => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl) - - expect(Tooltip.getInstance(tooltipEl)).toEqual(tooltip) - - tooltip.dispose() - - expect(Tooltip.getInstance(tooltipEl)).toEqual(null) - }) - - it('should destroy a tooltip and remove it from the dom', done => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl) - - tooltipEl.addEventListener('shown.bs.tooltip', () => { - expect(document.querySelector('.tooltip')).toBeDefined() - - tooltip.dispose() - - expect(document.querySelector('.tooltip')).toBeNull() - done() - }) - - tooltip.show() - }) - }) - - describe('show', () => { - it('should show a tooltip', done => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl) - - tooltipEl.addEventListener('shown.bs.tooltip', () => { - const tooltipShown = document.querySelector('.tooltip') - - expect(tooltipShown).toBeDefined() - expect(tooltipEl.getAttribute('aria-describedby')).toEqual(tooltipShown.getAttribute('id')) - expect(tooltipShown.getAttribute('id').indexOf('tooltip') !== -1).toEqual(true) - done() - }) - - tooltip.show() - }) - - it('should show a tooltip on mobile', done => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl) - document.documentElement.ontouchstart = noop - - spyOn(EventHandler, 'on') - - tooltipEl.addEventListener('shown.bs.tooltip', () => { - expect(document.querySelector('.tooltip')).not.toBeNull() - expect(EventHandler.on).toHaveBeenCalled() - document.documentElement.ontouchstart = undefined - done() - }) - - tooltip.show() - }) - - it('should show a tooltip relative to placement option', done => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl, { - placement: 'bottom' - }) - - tooltipEl.addEventListener('inserted.bs.tooltip', () => { - expect(tooltip.getTipElement().classList.contains('bs-tooltip-bottom')).toEqual(true) - }) - - tooltipEl.addEventListener('shown.bs.tooltip', () => { - const tooltipShown = document.querySelector('.tooltip') - - expect(tooltipShown.classList.contains('bs-tooltip-bottom')).toEqual(true) - done() - }) - - tooltip.show() - }) - - it('should not error when trying to show a tooltip that has been removed from the dom', done => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl) - - const firstCallback = () => { - tooltipEl.removeEventListener('shown.bs.tooltip', firstCallback) - let tooltipShown = document.querySelector('.tooltip') - - tooltipShown.parentNode.removeChild(tooltipShown) - - tooltipEl.addEventListener('shown.bs.tooltip', () => { - tooltipShown = document.querySelector('.tooltip') - - expect(tooltipShown).not.toBeNull() - done() - }) - - tooltip.show() - } - - tooltipEl.addEventListener('shown.bs.tooltip', firstCallback) - - tooltip.show() - }) - - it('should show a tooltip with a dom element container', done => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl, { - container: fixtureEl - }) - - tooltipEl.addEventListener('shown.bs.tooltip', () => { - expect(fixtureEl.querySelector('.tooltip')).toBeDefined() - done() - }) - - tooltip.show() - }) - - it('should show a tooltip with a jquery element container', done => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl, { - container: { - 0: fixtureEl, - jquery: 'jQuery' - } - }) - - tooltipEl.addEventListener('shown.bs.tooltip', () => { - expect(fixtureEl.querySelector('.tooltip')).toBeDefined() - done() - }) - - tooltip.show() - }) - - it('should show a tooltip with a selector in container', done => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl, { - container: '#fixture' - }) - - tooltipEl.addEventListener('shown.bs.tooltip', () => { - expect(fixtureEl.querySelector('.tooltip')).toBeDefined() - done() - }) - - tooltip.show() - }) - - it('should show a tooltip with placement as a function', done => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const spy = jasmine.createSpy('placement').and.returnValue('top') - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl, { - placement: spy - }) - - tooltipEl.addEventListener('shown.bs.tooltip', () => { - expect(document.querySelector('.tooltip')).toBeDefined() - expect(spy).toHaveBeenCalled() - done() - }) - - tooltip.show() - }) - - it('should show a tooltip with offset as a function', done => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const spy = jasmine.createSpy('offset').and.returnValue({}) - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl, { - offset: spy - }) - - tooltipEl.addEventListener('shown.bs.tooltip', () => { - expect(document.querySelector('.tooltip')).toBeDefined() - expect(spy).toHaveBeenCalled() - done() - }) - - tooltip.show() - }) - - it('should show a tooltip without the animation', done => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl, { - animation: false - }) - - tooltipEl.addEventListener('shown.bs.tooltip', () => { - const tip = document.querySelector('.tooltip') - - expect(tip).toBeDefined() - expect(tip.classList.contains('fade')).toEqual(false) - done() - }) - - tooltip.show() - }) - - it('should throw an error the element is not visible', () => { - fixtureEl.innerHTML = '<a href="#" style="display: none" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl) - - try { - tooltip.show() - } catch (error) { - expect(error.message).toEqual('Please use show on visible elements') - } - }) - - it('should not show a tooltip if show.bs.tooltip is prevented', done => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl) - - const expectedDone = () => { - setTimeout(() => { - expect(document.querySelector('.tooltip')).toBeNull() - done() - }, 10) - } - - tooltipEl.addEventListener('show.bs.tooltip', ev => { - ev.preventDefault() - expectedDone() - }) - - tooltipEl.addEventListener('shown.bs.tooltip', () => { - throw new Error('Tooltip should not be shown') - }) - - tooltip.show() - }) - - it('should show tooltip if leave event hasn\'t occurred before delay expires', done => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl, { - delay: 150 - }) - - spyOn(tooltip, 'show') - - setTimeout(() => { - expect(tooltip.show).not.toHaveBeenCalled() - }, 100) - - setTimeout(() => { - expect(tooltip.show).toHaveBeenCalled() - done() - }, 200) - - tooltipEl.dispatchEvent(createEvent('mouseover')) - }) - - it('should not show tooltip if leave event occurs before delay expires', done => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl, { - delay: 150 - }) - - spyOn(tooltip, 'show') - - setTimeout(() => { - expect(tooltip.show).not.toHaveBeenCalled() - tooltipEl.dispatchEvent(createEvent('mouseover')) - }, 100) - - setTimeout(() => { - expect(tooltip.show).toHaveBeenCalled() - expect(document.querySelectorAll('.tooltip').length).toEqual(0) - done() - }, 200) - - tooltipEl.dispatchEvent(createEvent('mouseover')) - }) - - it('should not hide tooltip if leave event occurs and enter event occurs within the hide delay', done => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl, { - delay: { - show: 0, - hide: 150 - } - }) - - setTimeout(() => { - expect(tooltip.getTipElement().classList.contains('show')).toEqual(true) - tooltipEl.dispatchEvent(createEvent('mouseout')) - - setTimeout(() => { - expect(tooltip.getTipElement().classList.contains('show')).toEqual(true) - tooltipEl.dispatchEvent(createEvent('mouseover')) - }, 100) - - setTimeout(() => { - expect(tooltip.getTipElement().classList.contains('show')).toEqual(true) - done() - }, 200) - }, 0) - - tooltipEl.dispatchEvent(createEvent('mouseover')) - }) - }) - - describe('hide', () => { - it('should hide a tooltip', done => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl) - - tooltipEl.addEventListener('shown.bs.tooltip', () => tooltip.hide()) - tooltipEl.addEventListener('hidden.bs.tooltip', () => { - expect(document.querySelector('.tooltip')).toBeNull() - expect(tooltipEl.getAttribute('aria-describedby')).toBeNull() - done() - }) - - tooltip.show() - }) - - it('should hide a tooltip on mobile', done => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl) - - tooltipEl.addEventListener('shown.bs.tooltip', () => { - document.documentElement.ontouchstart = noop - spyOn(EventHandler, 'off') - tooltip.hide() - }) - - tooltipEl.addEventListener('hidden.bs.tooltip', () => { - expect(document.querySelector('.tooltip')).toBeNull() - expect(EventHandler.off).toHaveBeenCalled() - document.documentElement.ontouchstart = undefined - done() - }) - - tooltip.show() - }) - - it('should hide a tooltip without animation', done => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl, { - animation: false - }) - - tooltipEl.addEventListener('shown.bs.tooltip', () => tooltip.hide()) - tooltipEl.addEventListener('hidden.bs.tooltip', () => { - expect(document.querySelector('.tooltip')).toBeNull() - expect(tooltipEl.getAttribute('aria-describedby')).toBeNull() - done() - }) - - tooltip.show() - }) - - it('should not hide a tooltip if hide event is prevented', done => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const assertDone = () => { - setTimeout(() => { - expect(document.querySelector('.tooltip')).not.toBeNull() - done() - }, 20) - } - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl, { - animation: false - }) - - tooltipEl.addEventListener('shown.bs.tooltip', () => tooltip.hide()) - tooltipEl.addEventListener('hide.bs.tooltip', event => { - event.preventDefault() - assertDone() - }) - tooltipEl.addEventListener('hidden.bs.tooltip', () => { - throw new Error('should not trigger hidden event') - }) - - tooltip.show() - }) - }) - - describe('update', () => { - it('should call popper schedule update', done => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl) - - tooltipEl.addEventListener('shown.bs.tooltip', () => { - spyOn(tooltip._popper, 'scheduleUpdate') - - tooltip.update() - - expect(tooltip._popper.scheduleUpdate).toHaveBeenCalled() - done() - }) - - tooltip.show() - }) - - it('should do nothing if the tooltip is not shown', () => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl) - - tooltip.update() - expect().nothing() - }) - }) - - describe('isWithContent', () => { - it('should return true if there is content', () => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl) - - expect(tooltip.isWithContent()).toEqual(true) - }) - - it('should return false if there is no content', () => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title=""/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl) - - expect(tooltip.isWithContent()).toEqual(false) - }) - }) - - describe('getTipElement', () => { - it('should create the tip element and return it', () => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl) - - spyOn(document, 'createElement').and.callThrough() - - expect(tooltip.getTipElement()).toBeDefined() - expect(document.createElement).toHaveBeenCalled() - }) - - it('should return the created tip element', () => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl) - - const spy = spyOn(document, 'createElement').and.callThrough() - - expect(tooltip.getTipElement()).toBeDefined() - expect(spy).toHaveBeenCalled() - - spy.calls.reset() - - expect(tooltip.getTipElement()).toBeDefined() - expect(spy).not.toHaveBeenCalled() - }) - }) - - describe('setContent', () => { - it('should set tip content', () => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl) - - tooltip.setContent() - - const tip = tooltip.getTipElement() - - expect(tip.classList.contains('show')).toEqual(false) - expect(tip.classList.contains('fade')).toEqual(false) - expect(tip.querySelector('.tooltip-inner').textContent).toEqual('Another tooltip') - }) - }) - - describe('setElementContent', () => { - it('should do nothing if the element is null', () => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl) - - tooltip.setElementContent(null, null) - expect().nothing() - }) - - it('should add the content as a child of the element', () => { - fixtureEl.innerHTML = [ - '<a href="#" rel="tooltip" title="Another tooltip"/>', - '<div id="childContent"></div>' - ].join('') - - const tooltipEl = fixtureEl.querySelector('a') - const childContent = fixtureEl.querySelector('div') - const tooltip = new Tooltip(tooltipEl, { - html: true - }) - - tooltip.setElementContent(tooltip.getTipElement(), childContent) - - expect(childContent.parentNode).toEqual(tooltip.getTipElement()) - }) - - it('should do nothing if the content is a child of the element', () => { - fixtureEl.innerHTML = [ - '<a href="#" rel="tooltip" title="Another tooltip"/>', - '<div id="childContent"></div>' - ].join('') - - const tooltipEl = fixtureEl.querySelector('a') - const childContent = fixtureEl.querySelector('div') - const tooltip = new Tooltip(tooltipEl, { - html: true - }) - - tooltip.getTipElement().appendChild(childContent) - tooltip.setElementContent(tooltip.getTipElement(), childContent) - - expect().nothing() - }) - - it('should add the content as a child of the element for jQuery elements', () => { - fixtureEl.innerHTML = [ - '<a href="#" rel="tooltip" title="Another tooltip"/>', - '<div id="childContent"></div>' - ].join('') - - const tooltipEl = fixtureEl.querySelector('a') - const childContent = fixtureEl.querySelector('div') - const tooltip = new Tooltip(tooltipEl, { - html: true - }) - - tooltip.setElementContent(tooltip.getTipElement(), { 0: childContent, jquery: 'jQuery' }) - - expect(childContent.parentNode).toEqual(tooltip.getTipElement()) - }) - - it('should add the child text content in the element', () => { - fixtureEl.innerHTML = [ - '<a href="#" rel="tooltip" title="Another tooltip"/>', - '<div id="childContent">Tooltip</div>' - ].join('') - - const tooltipEl = fixtureEl.querySelector('a') - const childContent = fixtureEl.querySelector('div') - const tooltip = new Tooltip(tooltipEl) - - tooltip.setElementContent(tooltip.getTipElement(), childContent) - - expect(childContent.textContent).toEqual(tooltip.getTipElement().textContent) - }) - - it('should add html without sanitize it', () => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl, { - sanitize: false, - html: true - }) - - tooltip.setElementContent(tooltip.getTipElement(), '<div id="childContent">Tooltip</div>') - - expect(tooltip.getTipElement().querySelector('div').id).toEqual('childContent') - }) - - it('should add html sanitized', () => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl, { - html: true - }) - - tooltip.setElementContent(tooltip.getTipElement(), [ - '<div id="childContent">', - ' <button type="button">test btn</button>', - '</div>' - ].join('')) - - expect(tooltip.getTipElement().querySelector('div').id).toEqual('childContent') - expect(tooltip.getTipElement().querySelector('button')).toEqual(null) - }) - - it('should add text content', () => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl) - - tooltip.setElementContent(tooltip.getTipElement(), 'test') - - expect(tooltip.getTipElement().innerText).toEqual('test') - }) - }) - - describe('getTitle', () => { - it('should return the title', () => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip"/>' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl) - - expect(tooltip.getTitle()).toEqual('Another tooltip') - }) - - it('should call title function', () => { - fixtureEl.innerHTML = '<a href="#" rel="tooltip" />' - - const tooltipEl = fixtureEl.querySelector('a') - const tooltip = new Tooltip(tooltipEl, { - title: () => 'test' - }) - - expect(tooltip.getTitle()).toEqual('test') - }) - }) - - describe('jQueryInterface', () => { - it('should create a tooltip', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - - jQueryMock.fn.tooltip = Tooltip.jQueryInterface - jQueryMock.elements = [div] - - jQueryMock.fn.tooltip.call(jQueryMock) - - expect(Tooltip.getInstance(div)).toBeDefined() - }) - - it('should not re create a tooltip', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - const tooltip = new Tooltip(div) - - jQueryMock.fn.tooltip = Tooltip.jQueryInterface - jQueryMock.elements = [div] - - jQueryMock.fn.tooltip.call(jQueryMock) - - expect(Tooltip.getInstance(div)).toEqual(tooltip) - }) - - it('should call a tooltip method', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - const tooltip = new Tooltip(div) - - spyOn(tooltip, 'show') - - jQueryMock.fn.tooltip = Tooltip.jQueryInterface - jQueryMock.elements = [div] - - jQueryMock.fn.tooltip.call(jQueryMock, 'show') - - expect(Tooltip.getInstance(div)).toEqual(tooltip) - expect(tooltip.show).toHaveBeenCalled() - }) - - it('should do nothing when we call dispose or hide if there is no tooltip created', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - - spyOn(Tooltip.prototype, 'dispose') - - jQueryMock.fn.tooltip = Tooltip.jQueryInterface - jQueryMock.elements = [div] - - jQueryMock.fn.tooltip.call(jQueryMock, 'dispose') - - expect(Tooltip.prototype.dispose).not.toHaveBeenCalled() - }) - - it('should throw error on undefined method', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - const action = 'undefinedMethod' - - jQueryMock.fn.tooltip = Tooltip.jQueryInterface - jQueryMock.elements = [div] - - try { - jQueryMock.fn.tooltip.call(jQueryMock, action) - } catch (error) { - expect(error.message).toEqual(`No method named "${action}"`) - } - }) - }) -}) diff --git a/js/src/util/index.spec.js b/js/src/util/index.spec.js deleted file mode 100644 index 9512c2fe0..000000000 --- a/js/src/util/index.spec.js +++ /dev/null @@ -1,382 +0,0 @@ -import * as Util from './index' - -/** Test helpers */ -import { getFixture, clearFixture } from '../../tests/helpers/fixture' - -describe('Util', () => { - let fixtureEl - - beforeAll(() => { - fixtureEl = getFixture() - }) - - afterEach(() => { - clearFixture() - }) - - describe('getUID', () => { - it('should generate uid', () => { - const uid = Util.getUID('bs') - const uid2 = Util.getUID('bs') - - expect(uid).not.toEqual(uid2) - }) - }) - - describe('getSelectorFromElement', () => { - it('should get selector from data-target', () => { - fixtureEl.innerHTML = [ - '<div id="test" data-target=".target"></div>', - '<div class="target"></div>' - ].join('') - - const testEl = fixtureEl.querySelector('#test') - - expect(Util.getSelectorFromElement(testEl)).toEqual('.target') - }) - - it('should get selector from href if no data-target set', () => { - fixtureEl.innerHTML = [ - '<a id="test" href=".target"></a>', - '<div class="target"></div>' - ].join('') - - const testEl = fixtureEl.querySelector('#test') - - expect(Util.getSelectorFromElement(testEl)).toEqual('.target') - }) - - it('should get selector from href if data-target equal to #', () => { - fixtureEl.innerHTML = [ - '<a id="test" data-target="#" href=".target"></a>', - '<div class="target"></div>' - ].join('') - - const testEl = fixtureEl.querySelector('#test') - - expect(Util.getSelectorFromElement(testEl)).toEqual('.target') - }) - - it('should return null if selector not found', () => { - fixtureEl.innerHTML = '<a id="test" href=".target"></a>' - - const testEl = fixtureEl.querySelector('#test') - - expect(Util.getSelectorFromElement(testEl)).toBeNull() - }) - - it('should return null if no selector', () => { - fixtureEl.innerHTML = '<div></div>' - - const testEl = fixtureEl.querySelector('div') - - expect(Util.getSelectorFromElement(testEl)).toBeNull() - }) - }) - - describe('getElementFromSelector', () => { - it('should get element from data-target', () => { - fixtureEl.innerHTML = [ - '<div id="test" data-target=".target"></div>', - '<div class="target"></div>' - ].join('') - - const testEl = fixtureEl.querySelector('#test') - - expect(Util.getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target')) - }) - - it('should get element from href if no data-target set', () => { - fixtureEl.innerHTML = [ - '<a id="test" href=".target"></a>', - '<div class="target"></div>' - ].join('') - - const testEl = fixtureEl.querySelector('#test') - - expect(Util.getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target')) - }) - - it('should return null if element not found', () => { - fixtureEl.innerHTML = '<a id="test" href=".target"></a>' - - const testEl = fixtureEl.querySelector('#test') - - expect(Util.getElementFromSelector(testEl)).toBeNull() - }) - - it('should return null if no selector', () => { - fixtureEl.innerHTML = '<div></div>' - - const testEl = fixtureEl.querySelector('div') - - expect(Util.getElementFromSelector(testEl)).toBeNull() - }) - }) - - describe('getTransitionDurationFromElement', () => { - it('should get transition from element', () => { - fixtureEl.innerHTML = '<div style="transition: all 300ms ease-out;"></div>' - - expect(Util.getTransitionDurationFromElement(fixtureEl.querySelector('div'))).toEqual(300) - }) - - it('should return 0 if the element is undefined or null', () => { - expect(Util.getTransitionDurationFromElement(null)).toEqual(0) - expect(Util.getTransitionDurationFromElement(undefined)).toEqual(0) - }) - - it('should return 0 if the element do not possess transition', () => { - fixtureEl.innerHTML = '<div></div>' - - expect(Util.getTransitionDurationFromElement(fixtureEl.querySelector('div'))).toEqual(0) - }) - }) - - describe('triggerTransitionEnd', () => { - it('should trigger transitionend event', done => { - fixtureEl.innerHTML = '<div style="transition: all 300ms ease-out;"></div>' - - const el = fixtureEl.querySelector('div') - - el.addEventListener('transitionend', () => { - expect().nothing() - done() - }) - - Util.triggerTransitionEnd(el) - }) - }) - - describe('isElement', () => { - it('should detect if the parameter is an element or not', () => { - fixtureEl.innerHTML = '<div></div>' - - const el = document.querySelector('div') - - expect(Util.isElement(el)).toEqual(el.nodeType) - expect(Util.isElement({})).toEqual(undefined) - }) - - it('should detect jQuery element', () => { - fixtureEl.innerHTML = '<div></div>' - - const el = document.querySelector('div') - const fakejQuery = { - 0: el - } - - expect(Util.isElement(fakejQuery)).toEqual(el.nodeType) - }) - }) - - describe('emulateTransitionEnd', () => { - it('should emulate transition end', () => { - fixtureEl.innerHTML = '<div></div>' - - const el = document.querySelector('div') - const spy = spyOn(window, 'setTimeout') - - Util.emulateTransitionEnd(el, 10) - expect(spy).toHaveBeenCalled() - }) - - it('should not emulate transition end if already triggered', done => { - fixtureEl.innerHTML = '<div></div>' - - const el = fixtureEl.querySelector('div') - const spy = spyOn(el, 'removeEventListener') - - Util.emulateTransitionEnd(el, 10) - Util.triggerTransitionEnd(el) - - setTimeout(() => { - expect(spy).toHaveBeenCalled() - done() - }, 20) - }) - }) - - describe('typeCheckConfig', () => { - it('should check type of the config object', () => { - const namePlugin = 'collapse' - const defaultType = { - toggle: 'boolean', - parent: '(string|element)' - } - const config = { - toggle: true, - parent: 777 - } - - expect(() => { - Util.typeCheckConfig(namePlugin, config, defaultType) - }).toThrow(new Error('COLLAPSE: Option "parent" provided type "number" but expected type "(string|element)".')) - }) - }) - - describe('makeArray', () => { - it('should convert node list to array', () => { - const nodeList = document.querySelectorAll('div') - - expect(Array.isArray(nodeList)).toEqual(false) - expect(Array.isArray(Util.makeArray(nodeList))).toEqual(true) - }) - - it('should return an empty array if the nodeList is undefined', () => { - expect(Util.makeArray(null)).toEqual([]) - expect(Util.makeArray(undefined)).toEqual([]) - }) - }) - - describe('isVisible', () => { - it('should return false if the element is not defined', () => { - expect(Util.isVisible(null)).toEqual(false) - expect(Util.isVisible(undefined)).toEqual(false) - }) - - it('should return false if the element provided is not a dom element', () => { - expect(Util.isVisible({})).toEqual(false) - }) - - it('should return false if the element is not visible with display none', () => { - fixtureEl.innerHTML = '<div style="display: none;"></div>' - - const div = fixtureEl.querySelector('div') - - expect(Util.isVisible(div)).toEqual(false) - }) - - it('should return false if the element is not visible with visibility hidden', () => { - fixtureEl.innerHTML = '<div style="visibility: hidden;"></div>' - - const div = fixtureEl.querySelector('div') - - expect(Util.isVisible(div)).toEqual(false) - }) - - it('should return false if the parent element is not visible', () => { - fixtureEl.innerHTML = [ - '<div style="display: none;">', - ' <div class="content"></div>', - '</div>' - ].join('') - - const div = fixtureEl.querySelector('.content') - - expect(Util.isVisible(div)).toEqual(false) - }) - - it('should return true if the element is visible', () => { - fixtureEl.innerHTML = [ - '<div>', - ' <div id="element"></div>', - '</div>' - ].join('') - - const div = fixtureEl.querySelector('#element') - - expect(Util.isVisible(div)).toEqual(true) - }) - }) - - describe('findShadowRoot', () => { - it('should return null if shadow dom is not available', () => { - // Only for newer browsers - if (!document.documentElement.attachShadow) { - expect().nothing() - return - } - - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - - spyOn(document.documentElement, 'attachShadow').and.returnValue(null) - - expect(Util.findShadowRoot(div)).toEqual(null) - }) - - it('should return null when we do not find a shadow root', () => { - // Only for newer browsers - if (!document.documentElement.attachShadow) { - expect().nothing() - return - } - - spyOn(document, 'getRootNode').and.returnValue(undefined) - - expect(Util.findShadowRoot(document)).toEqual(null) - }) - - it('should return the shadow root when found', () => { - // Only for newer browsers - if (!document.documentElement.attachShadow) { - expect().nothing() - return - } - - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - const shadowRoot = div.attachShadow({ - mode: 'open' - }) - - expect(Util.findShadowRoot(shadowRoot)).toEqual(shadowRoot) - - shadowRoot.innerHTML = '<button>Shadow Button</button>' - - expect(Util.findShadowRoot(shadowRoot.firstChild)).toEqual(shadowRoot) - }) - }) - - describe('noop', () => { - it('should return a function', () => { - expect(typeof Util.noop()).toEqual('function') - }) - }) - - describe('reflow', () => { - it('should return element offset height to force the reflow', () => { - fixtureEl.innerHTML = '<div></div>' - - const div = fixtureEl.querySelector('div') - - expect(Util.reflow(div)).toEqual(0) - }) - }) - - describe('getjQuery', () => { - const fakejQuery = { trigger() {} } - - beforeEach(() => { - Object.defineProperty(window, 'jQuery', { - value: fakejQuery, - writable: true - }) - }) - - afterEach(() => { - window.jQuery = undefined - }) - - it('should return jQuery object when present', () => { - expect(Util.getjQuery()).toEqual(fakejQuery) - }) - - it('should not return jQuery object when present if data-no-jquery', () => { - document.body.setAttribute('data-no-jquery', '') - - expect(window.jQuery).toEqual(fakejQuery) - expect(Util.getjQuery()).toEqual(null) - - document.body.removeAttribute('data-no-jquery') - }) - - it('should not return jQuery if not present', () => { - window.jQuery = undefined - expect(Util.getjQuery()).toEqual(null) - }) - }) -}) diff --git a/js/src/util/sanitizer.spec.js b/js/src/util/sanitizer.spec.js deleted file mode 100644 index 6dadd29a5..000000000 --- a/js/src/util/sanitizer.spec.js +++ /dev/null @@ -1,70 +0,0 @@ -import { DefaultWhitelist, sanitizeHtml } from './sanitizer' - -describe('Sanitizer', () => { - describe('sanitizeHtml', () => { - it('should return the same on empty string', () => { - const empty = '' - - const result = sanitizeHtml(empty, DefaultWhitelist, null) - - expect(result).toEqual(empty) - }) - - it('should sanitize template by removing tags with XSS', () => { - const template = [ - '<div>', - ' <a href="javascript:alert(7)">Click me</a>', - ' <span>Some content</span>', - '</div>' - ].join('') - - const result = sanitizeHtml(template, DefaultWhitelist, null) - - expect(result.indexOf('script') === -1).toEqual(true) - }) - - it('should allow aria attributes and safe attributes', () => { - const template = [ - '<div aria-pressed="true">', - ' <span class="test">Some content</span>', - '</div>' - ].join('') - - const result = sanitizeHtml(template, DefaultWhitelist, null) - - expect(result.indexOf('aria-pressed') !== -1).toEqual(true) - expect(result.indexOf('class="test"') !== -1).toEqual(true) - }) - - it('should remove not whitelist tags', () => { - const template = [ - '<div>', - ' <script>alert(7)</script>', - '</div>' - ].join('') - - const result = sanitizeHtml(template, DefaultWhitelist, null) - - expect(result.indexOf('<script>') === -1).toEqual(true) - }) - - it('should not use native api to sanitize if a custom function passed', () => { - const template = [ - '<div>', - ' <span>Some content</span>', - '</div>' - ].join('') - - function mySanitize(htmlUnsafe) { - return htmlUnsafe - } - - spyOn(DOMParser.prototype, 'parseFromString') - - const result = sanitizeHtml(template, DefaultWhitelist, mySanitize) - - expect(result).toEqual(template) - expect(DOMParser.prototype.parseFromString).not.toHaveBeenCalled() - }) - }) -}) |
