mirror of
https://github.com/twbs/bootstrap.git
synced 2025-08-08 14:46:34 +02:00
return to the original file structure to avoid breaking modularity
This commit is contained in:
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"root": true,
|
||||
"extends": [
|
||||
"../../.eslintrc.json"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["**/*.spec.js"],
|
||||
"env": {
|
||||
"jasmine": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@@ -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'
|
||||
|
||||
/**
|
||||
* ------------------------------------------------------------------------
|
@@ -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()
|
||||
})
|
||||
})
|
||||
})
|
@@ -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'
|
||||
|
||||
/**
|
||||
* ------------------------------------------------------------------------
|
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
@@ -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'
|
||||
|
||||
/**
|
||||
* ------------------------------------------------------------------------
|
File diff suppressed because it is too large
Load Diff
@@ -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'
|
||||
|
||||
/**
|
||||
* ------------------------------------------------------------------------
|
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
@@ -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()
|
||||
})
|
||||
})
|
||||
})
|
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
@@ -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])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@@ -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'
|
||||
|
||||
/**
|
||||
* ------------------------------------------------------------------------
|
File diff suppressed because it is too large
Load Diff
@@ -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'
|
||||
|
||||
/**
|
||||
* ------------------------------------------------------------------------
|
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
@@ -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'
|
||||
|
||||
/**
|
||||
* ------------------------------------------------------------------------
|
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
@@ -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'
|
||||
|
||||
/**
|
||||
* ------------------------------------------------------------------------
|
@@ -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()
|
||||
})
|
||||
})
|
||||
})
|
@@ -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'
|
||||
|
||||
/**
|
||||
* ------------------------------------------------------------------------
|
@@ -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()
|
||||
})
|
||||
})
|
||||
})
|
@@ -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'
|
||||
|
||||
/**
|
||||
* ------------------------------------------------------------------------
|
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
@@ -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'
|
||||
|
||||
/**
|
||||
* ------------------------------------------------------------------------
|
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
@@ -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()
|
||||
})
|
||||
})
|
||||
})
|
Reference in New Issue
Block a user