Adding upstream version 5.2.3+dfsg.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
b80f12a01d
commit
bc475d7d0d
617 changed files with 89471 additions and 0 deletions
13
js/tests/unit/.eslintrc.json
Normal file
13
js/tests/unit/.eslintrc.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"extends": [
|
||||
"../../../.eslintrc.json"
|
||||
],
|
||||
"env": {
|
||||
"jasmine": true
|
||||
},
|
||||
"rules": {
|
||||
"unicorn/consistent-function-scoping": "off",
|
||||
"unicorn/no-useless-undefined": "off",
|
||||
"unicorn/prefer-add-event-listener": "off"
|
||||
}
|
||||
}
|
259
js/tests/unit/alert.spec.js
Normal file
259
js/tests/unit/alert.spec.js
Normal file
|
@ -0,0 +1,259 @@
|
|||
import Alert from '../../src/alert'
|
||||
import { getTransitionDurationFromElement } from '../../src/util/index'
|
||||
import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture'
|
||||
|
||||
describe('Alert', () => {
|
||||
let fixtureEl
|
||||
|
||||
beforeAll(() => {
|
||||
fixtureEl = getFixture()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
clearFixture()
|
||||
})
|
||||
|
||||
it('should take care of element either passed as a CSS selector or DOM element', () => {
|
||||
fixtureEl.innerHTML = '<div class="alert"></div>'
|
||||
|
||||
const alertEl = fixtureEl.querySelector('.alert')
|
||||
const alertBySelector = new Alert('.alert')
|
||||
const alertByElement = new Alert(alertEl)
|
||||
|
||||
expect(alertBySelector._element).toEqual(alertEl)
|
||||
expect(alertByElement._element).toEqual(alertEl)
|
||||
})
|
||||
|
||||
it('should return version', () => {
|
||||
expect(Alert.VERSION).toEqual(jasmine.any(String))
|
||||
})
|
||||
|
||||
describe('DATA_KEY', () => {
|
||||
it('should return plugin data key', () => {
|
||||
expect(Alert.DATA_KEY).toEqual('bs.alert')
|
||||
})
|
||||
})
|
||||
|
||||
describe('data-api', () => {
|
||||
it('should close an alert without instantiating it manually', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div class="alert">',
|
||||
' <button type="button" data-bs-dismiss="alert">x</button>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const button = document.querySelector('button')
|
||||
|
||||
button.click()
|
||||
expect(document.querySelectorAll('.alert')).toHaveSize(0)
|
||||
})
|
||||
|
||||
it('should close an alert without instantiating it manually with the parent selector', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div class="alert">',
|
||||
' <button type="button" data-bs-target=".alert" data-bs-dismiss="alert">x</button>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const button = document.querySelector('button')
|
||||
|
||||
button.click()
|
||||
expect(document.querySelectorAll('.alert')).toHaveSize(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('close', () => {
|
||||
it('should close an alert', () => {
|
||||
return new Promise(resolve => {
|
||||
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(document.querySelectorAll('.alert')).toHaveSize(0)
|
||||
expect(spy).not.toHaveBeenCalled()
|
||||
resolve()
|
||||
})
|
||||
|
||||
alert.close()
|
||||
})
|
||||
})
|
||||
|
||||
it('should close alert with fade class', () => {
|
||||
return new Promise(resolve => {
|
||||
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(document.querySelectorAll('.alert')).toHaveSize(0)
|
||||
resolve()
|
||||
})
|
||||
|
||||
alert.close()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not remove alert if close event is prevented', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fixtureEl.innerHTML = '<div class="alert"></div>'
|
||||
|
||||
const getAlert = () => document.querySelector('.alert')
|
||||
const alertEl = getAlert()
|
||||
const alert = new Alert(alertEl)
|
||||
|
||||
alertEl.addEventListener('close.bs.alert', event => {
|
||||
event.preventDefault()
|
||||
setTimeout(() => {
|
||||
expect(getAlert()).not.toBeNull()
|
||||
resolve()
|
||||
}, 10)
|
||||
})
|
||||
|
||||
alertEl.addEventListener('closed.bs.alert', () => {
|
||||
reject(new Error('should not fire closed event'))
|
||||
})
|
||||
|
||||
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)).not.toBeNull()
|
||||
|
||||
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)
|
||||
|
||||
const spy = spyOn(alert, 'close')
|
||||
|
||||
jQueryMock.fn.alert = Alert.jQueryInterface
|
||||
jQueryMock.elements = [alertEl]
|
||||
|
||||
jQueryMock.fn.alert.call(jQueryMock, 'close')
|
||||
|
||||
expect(spy).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]
|
||||
|
||||
expect(Alert.getInstance(alertEl)).toBeNull()
|
||||
jQueryMock.fn.alert.call(jQueryMock, 'close')
|
||||
|
||||
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)).not.toBeNull()
|
||||
expect(fixtureEl.querySelector('.alert')).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should throw an error on undefined method', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
const action = 'undefinedMethod'
|
||||
|
||||
jQueryMock.fn.alert = Alert.jQueryInterface
|
||||
jQueryMock.elements = [div]
|
||||
|
||||
expect(() => {
|
||||
jQueryMock.fn.alert.call(jQueryMock, action)
|
||||
}).toThrowError(TypeError, `No method named "${action}"`)
|
||||
})
|
||||
|
||||
it('should throw an error on protected method', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
const action = '_getConfig'
|
||||
|
||||
jQueryMock.fn.alert = Alert.jQueryInterface
|
||||
jQueryMock.elements = [div]
|
||||
|
||||
expect(() => {
|
||||
jQueryMock.fn.alert.call(jQueryMock, action)
|
||||
}).toThrowError(TypeError, `No method named "${action}"`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getInstance', () => {
|
||||
it('should return alert instance', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
const alert = new Alert(div)
|
||||
|
||||
expect(Alert.getInstance(div)).toEqual(alert)
|
||||
expect(Alert.getInstance(div)).toBeInstanceOf(Alert)
|
||||
})
|
||||
|
||||
it('should return null when there is no alert instance', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Alert.getInstance(div)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getOrCreateInstance', () => {
|
||||
it('should return alert instance', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
const alert = new Alert(div)
|
||||
|
||||
expect(Alert.getOrCreateInstance(div)).toEqual(alert)
|
||||
expect(Alert.getInstance(div)).toEqual(Alert.getOrCreateInstance(div, {}))
|
||||
expect(Alert.getOrCreateInstance(div)).toBeInstanceOf(Alert)
|
||||
})
|
||||
|
||||
it('should return new instance when there is no alert instance', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Alert.getInstance(div)).toBeNull()
|
||||
expect(Alert.getOrCreateInstance(div)).toBeInstanceOf(Alert)
|
||||
})
|
||||
})
|
||||
})
|
168
js/tests/unit/base-component.spec.js
Normal file
168
js/tests/unit/base-component.spec.js
Normal file
|
@ -0,0 +1,168 @@
|
|||
import BaseComponent from '../../src/base-component'
|
||||
import { clearFixture, getFixture } from '../helpers/fixture'
|
||||
import EventHandler from '../../src/dom/event-handler'
|
||||
import { noop } from '../../src/util'
|
||||
|
||||
class DummyClass extends BaseComponent {
|
||||
constructor(element) {
|
||||
super(element)
|
||||
|
||||
EventHandler.on(this._element, `click${DummyClass.EVENT_KEY}`, noop)
|
||||
}
|
||||
|
||||
static get NAME() {
|
||||
return 'dummy'
|
||||
}
|
||||
}
|
||||
|
||||
describe('Base Component', () => {
|
||||
let fixtureEl
|
||||
const name = 'dummy'
|
||||
let element
|
||||
let instance
|
||||
const createInstance = () => {
|
||||
fixtureEl.innerHTML = '<div id="foo"></div>'
|
||||
element = fixtureEl.querySelector('#foo')
|
||||
instance = new DummyClass(element)
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
fixtureEl = getFixture()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
clearFixture()
|
||||
})
|
||||
|
||||
describe('Static Methods', () => {
|
||||
describe('VERSION', () => {
|
||||
it('should return version', () => {
|
||||
expect(DummyClass.VERSION).toEqual(jasmine.any(String))
|
||||
})
|
||||
})
|
||||
|
||||
describe('DATA_KEY', () => {
|
||||
it('should return plugin data key', () => {
|
||||
expect(DummyClass.DATA_KEY).toEqual(`bs.${name}`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('NAME', () => {
|
||||
it('should throw an Error if it is not initialized', () => {
|
||||
expect(() => {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
BaseComponent.NAME
|
||||
}).toThrowError(Error)
|
||||
})
|
||||
|
||||
it('should return plugin NAME', () => {
|
||||
expect(DummyClass.NAME).toEqual(name)
|
||||
})
|
||||
})
|
||||
|
||||
describe('EVENT_KEY', () => {
|
||||
it('should return plugin event key', () => {
|
||||
expect(DummyClass.EVENT_KEY).toEqual(`.bs.${name}`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Public Methods', () => {
|
||||
describe('constructor', () => {
|
||||
it('should accept element, either passed as a CSS selector or DOM element', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div id="foo"></div>',
|
||||
'<div id="bar"></div>'
|
||||
].join('')
|
||||
|
||||
const el = fixtureEl.querySelector('#foo')
|
||||
const elInstance = new DummyClass(el)
|
||||
const selectorInstance = new DummyClass('#bar')
|
||||
|
||||
expect(elInstance._element).toEqual(el)
|
||||
expect(selectorInstance._element).toEqual(fixtureEl.querySelector('#bar'))
|
||||
})
|
||||
|
||||
it('should not initialize and add element record to Data (caching), if argument `element` is not an HTML element', () => {
|
||||
fixtureEl.innerHTML = ''
|
||||
|
||||
const el = fixtureEl.querySelector('#foo')
|
||||
const elInstance = new DummyClass(el)
|
||||
const selectorInstance = new DummyClass('#bar')
|
||||
|
||||
expect(elInstance._element).not.toBeDefined()
|
||||
expect(selectorInstance._element).not.toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('dispose', () => {
|
||||
it('should dispose an component', () => {
|
||||
createInstance()
|
||||
expect(DummyClass.getInstance(element)).not.toBeNull()
|
||||
|
||||
instance.dispose()
|
||||
|
||||
expect(DummyClass.getInstance(element)).toBeNull()
|
||||
expect(instance._element).toBeNull()
|
||||
})
|
||||
|
||||
it('should de-register element event listeners', () => {
|
||||
createInstance()
|
||||
const spy = spyOn(EventHandler, 'off')
|
||||
|
||||
instance.dispose()
|
||||
|
||||
expect(spy).toHaveBeenCalledWith(element, DummyClass.EVENT_KEY)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getInstance', () => {
|
||||
it('should return an instance', () => {
|
||||
createInstance()
|
||||
|
||||
expect(DummyClass.getInstance(element)).toEqual(instance)
|
||||
expect(DummyClass.getInstance(element)).toBeInstanceOf(DummyClass)
|
||||
})
|
||||
|
||||
it('should accept element, either passed as a CSS selector, jQuery element, or DOM element', () => {
|
||||
createInstance()
|
||||
|
||||
expect(DummyClass.getInstance('#foo')).toEqual(instance)
|
||||
expect(DummyClass.getInstance(element)).toEqual(instance)
|
||||
|
||||
const fakejQueryObject = {
|
||||
0: element,
|
||||
jquery: 'foo'
|
||||
}
|
||||
|
||||
expect(DummyClass.getInstance(fakejQueryObject)).toEqual(instance)
|
||||
})
|
||||
|
||||
it('should return null when there is no instance', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(DummyClass.getInstance(div)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getOrCreateInstance', () => {
|
||||
it('should return an instance', () => {
|
||||
createInstance()
|
||||
|
||||
expect(DummyClass.getOrCreateInstance(element)).toEqual(instance)
|
||||
expect(DummyClass.getInstance(element)).toEqual(DummyClass.getOrCreateInstance(element, {}))
|
||||
expect(DummyClass.getOrCreateInstance(element)).toBeInstanceOf(DummyClass)
|
||||
})
|
||||
|
||||
it('should return new instance when there is no alert instance', () => {
|
||||
fixtureEl.innerHTML = '<div id="foo"></div>'
|
||||
element = fixtureEl.querySelector('#foo')
|
||||
|
||||
expect(DummyClass.getInstance(element)).toBeNull()
|
||||
expect(DummyClass.getOrCreateInstance(element)).toBeInstanceOf(DummyClass)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
183
js/tests/unit/button.spec.js
Normal file
183
js/tests/unit/button.spec.js
Normal file
|
@ -0,0 +1,183 @@
|
|||
import Button from '../../src/button'
|
||||
import { getFixture, clearFixture, jQueryMock } from '../helpers/fixture'
|
||||
|
||||
describe('Button', () => {
|
||||
let fixtureEl
|
||||
|
||||
beforeAll(() => {
|
||||
fixtureEl = getFixture()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
clearFixture()
|
||||
})
|
||||
|
||||
it('should take care of element either passed as a CSS selector or DOM element', () => {
|
||||
fixtureEl.innerHTML = '<button data-bs-toggle="button">Placeholder</button>'
|
||||
const buttonEl = fixtureEl.querySelector('[data-bs-toggle="button"]')
|
||||
const buttonBySelector = new Button('[data-bs-toggle="button"]')
|
||||
const buttonByElement = new Button(buttonEl)
|
||||
|
||||
expect(buttonBySelector._element).toEqual(buttonEl)
|
||||
expect(buttonByElement._element).toEqual(buttonEl)
|
||||
})
|
||||
|
||||
describe('VERSION', () => {
|
||||
it('should return plugin version', () => {
|
||||
expect(Button.VERSION).toEqual(jasmine.any(String))
|
||||
})
|
||||
})
|
||||
|
||||
describe('DATA_KEY', () => {
|
||||
it('should return plugin data key', () => {
|
||||
expect(Button.DATA_KEY).toEqual('bs.button')
|
||||
})
|
||||
})
|
||||
|
||||
describe('data-api', () => {
|
||||
it('should toggle active class on click', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<button class="btn" data-bs-toggle="button">btn</button>',
|
||||
'<button class="btn testParent" data-bs-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).not.toHaveClass('active')
|
||||
|
||||
btn.click()
|
||||
|
||||
expect(btn).toHaveClass('active')
|
||||
|
||||
btn.click()
|
||||
|
||||
expect(btn).not.toHaveClass('active')
|
||||
|
||||
divTest.click()
|
||||
|
||||
expect(btnTestParent).toHaveClass('active')
|
||||
})
|
||||
})
|
||||
|
||||
describe('toggle', () => {
|
||||
it('should toggle aria-pressed', () => {
|
||||
fixtureEl.innerHTML = '<button class="btn" data-bs-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).not.toHaveClass('active')
|
||||
|
||||
button.toggle()
|
||||
|
||||
expect(btnEl.getAttribute('aria-pressed')).toEqual('true')
|
||||
expect(btnEl).toHaveClass('active')
|
||||
})
|
||||
})
|
||||
|
||||
describe('dispose', () => {
|
||||
it('should dispose a button', () => {
|
||||
fixtureEl.innerHTML = '<button class="btn" data-bs-toggle="button"></button>'
|
||||
|
||||
const btnEl = fixtureEl.querySelector('.btn')
|
||||
const button = new Button(btnEl)
|
||||
|
||||
expect(Button.getInstance(btnEl)).not.toBeNull()
|
||||
|
||||
button.dispose()
|
||||
|
||||
expect(Button.getInstance(btnEl)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('jQueryInterface', () => {
|
||||
it('should handle config passed and toggle existing button', () => {
|
||||
fixtureEl.innerHTML = '<button class="btn" data-bs-toggle="button"></button>'
|
||||
|
||||
const btnEl = fixtureEl.querySelector('.btn')
|
||||
const button = new Button(btnEl)
|
||||
|
||||
const spy = spyOn(button, 'toggle')
|
||||
|
||||
jQueryMock.fn.button = Button.jQueryInterface
|
||||
jQueryMock.elements = [btnEl]
|
||||
|
||||
jQueryMock.fn.button.call(jQueryMock, 'toggle')
|
||||
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should create new button instance and call toggle', () => {
|
||||
fixtureEl.innerHTML = '<button class="btn" data-bs-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)).not.toBeNull()
|
||||
expect(btnEl).toHaveClass('active')
|
||||
})
|
||||
|
||||
it('should just create a button instance without calling toggle', () => {
|
||||
fixtureEl.innerHTML = '<button class="btn" data-bs-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)).not.toBeNull()
|
||||
expect(btnEl).not.toHaveClass('active')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getInstance', () => {
|
||||
it('should return button instance', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
const button = new Button(div)
|
||||
|
||||
expect(Button.getInstance(div)).toEqual(button)
|
||||
expect(Button.getInstance(div)).toBeInstanceOf(Button)
|
||||
})
|
||||
|
||||
it('should return null when there is no button instance', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Button.getInstance(div)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getOrCreateInstance', () => {
|
||||
it('should return button instance', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
const button = new Button(div)
|
||||
|
||||
expect(Button.getOrCreateInstance(div)).toEqual(button)
|
||||
expect(Button.getInstance(div)).toEqual(Button.getOrCreateInstance(div, {}))
|
||||
expect(Button.getOrCreateInstance(div)).toBeInstanceOf(Button)
|
||||
})
|
||||
|
||||
it('should return new instance when there is no button instance', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Button.getInstance(div)).toBeNull()
|
||||
expect(Button.getOrCreateInstance(div)).toBeInstanceOf(Button)
|
||||
})
|
||||
})
|
||||
})
|
1570
js/tests/unit/carousel.spec.js
Normal file
1570
js/tests/unit/carousel.spec.js
Normal file
File diff suppressed because it is too large
Load diff
1062
js/tests/unit/collapse.spec.js
Normal file
1062
js/tests/unit/collapse.spec.js
Normal file
File diff suppressed because it is too large
Load diff
106
js/tests/unit/dom/data.spec.js
Normal file
106
js/tests/unit/dom/data.spec.js
Normal file
|
@ -0,0 +1,106 @@
|
|||
import Data from '../../../src/dom/data'
|
||||
import { getFixture, clearFixture } from '../../helpers/fixture'
|
||||
|
||||
describe('Data', () => {
|
||||
const TEST_KEY = 'bs.test'
|
||||
const UNKNOWN_KEY = 'bs.unknown'
|
||||
const TEST_DATA = {
|
||||
test: 'bsData'
|
||||
}
|
||||
|
||||
let fixtureEl
|
||||
let div
|
||||
|
||||
beforeAll(() => {
|
||||
fixtureEl = getFixture()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
div = fixtureEl.querySelector('div')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
Data.remove(div, TEST_KEY)
|
||||
clearFixture()
|
||||
})
|
||||
|
||||
it('should return null for unknown elements', () => {
|
||||
const data = { ...TEST_DATA }
|
||||
|
||||
Data.set(div, TEST_KEY, data)
|
||||
|
||||
expect(Data.get(null)).toBeNull()
|
||||
expect(Data.get(undefined)).toBeNull()
|
||||
expect(Data.get(document.createElement('div'), TEST_KEY)).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null for unknown keys', () => {
|
||||
const data = { ...TEST_DATA }
|
||||
|
||||
Data.set(div, TEST_KEY, data)
|
||||
|
||||
expect(Data.get(div, null)).toBeNull()
|
||||
expect(Data.get(div, undefined)).toBeNull()
|
||||
expect(Data.get(div, UNKNOWN_KEY)).toBeNull()
|
||||
})
|
||||
|
||||
it('should store data for an element with a given key and return it', () => {
|
||||
const data = { ...TEST_DATA }
|
||||
|
||||
Data.set(div, TEST_KEY, data)
|
||||
|
||||
expect(Data.get(div, TEST_KEY)).toEqual(data)
|
||||
})
|
||||
|
||||
it('should overwrite data if something is already stored', () => {
|
||||
const data = { ...TEST_DATA }
|
||||
const copy = { ...data }
|
||||
|
||||
Data.set(div, TEST_KEY, data)
|
||||
Data.set(div, TEST_KEY, copy)
|
||||
|
||||
// Using `toBe` since spread creates a shallow copy
|
||||
expect(Data.get(div, TEST_KEY)).not.toBe(data)
|
||||
expect(Data.get(div, TEST_KEY)).toBe(copy)
|
||||
})
|
||||
|
||||
it('should do nothing when an element has nothing stored', () => {
|
||||
Data.remove(div, TEST_KEY)
|
||||
|
||||
expect().nothing()
|
||||
})
|
||||
|
||||
it('should remove nothing for an unknown key', () => {
|
||||
const data = { ...TEST_DATA }
|
||||
|
||||
Data.set(div, TEST_KEY, data)
|
||||
Data.remove(div, UNKNOWN_KEY)
|
||||
|
||||
expect(Data.get(div, TEST_KEY)).toEqual(data)
|
||||
})
|
||||
|
||||
it('should remove data for a given key', () => {
|
||||
const data = { ...TEST_DATA }
|
||||
|
||||
Data.set(div, TEST_KEY, data)
|
||||
Data.remove(div, TEST_KEY)
|
||||
|
||||
expect(Data.get(div, TEST_KEY)).toBeNull()
|
||||
})
|
||||
|
||||
/* eslint-disable no-console */
|
||||
it('should console.error a message if called with multiple keys', () => {
|
||||
console.error = jasmine.createSpy('console.error')
|
||||
|
||||
const data = { ...TEST_DATA }
|
||||
const copy = { ...data }
|
||||
|
||||
Data.set(div, TEST_KEY, data)
|
||||
Data.set(div, UNKNOWN_KEY, copy)
|
||||
|
||||
expect(console.error).toHaveBeenCalled()
|
||||
expect(Data.get(div, UNKNOWN_KEY)).toBeNull()
|
||||
})
|
||||
/* eslint-enable no-console */
|
||||
})
|
480
js/tests/unit/dom/event-handler.spec.js
Normal file
480
js/tests/unit/dom/event-handler.spec.js
Normal file
|
@ -0,0 +1,480 @@
|
|||
import EventHandler from '../../../src/dom/event-handler'
|
||||
import { clearFixture, getFixture } from '../../helpers/fixture'
|
||||
import { noop } from '../../../src/util'
|
||||
|
||||
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, noop)
|
||||
EventHandler.on(null, 'click', noop)
|
||||
|
||||
expect().nothing()
|
||||
})
|
||||
|
||||
it('should add event listener', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
EventHandler.on(div, 'click', () => {
|
||||
expect().nothing()
|
||||
resolve()
|
||||
})
|
||||
|
||||
div.click()
|
||||
})
|
||||
})
|
||||
|
||||
it('should add namespaced event listener', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
EventHandler.on(div, 'bs.namespace', () => {
|
||||
expect().nothing()
|
||||
resolve()
|
||||
})
|
||||
|
||||
EventHandler.trigger(div, 'bs.namespace')
|
||||
})
|
||||
})
|
||||
|
||||
it('should add native namespaced event listener', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
EventHandler.on(div, 'click.namespace', () => {
|
||||
expect().nothing()
|
||||
resolve()
|
||||
})
|
||||
|
||||
EventHandler.trigger(div, 'click')
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle event delegation', () => {
|
||||
return new Promise(resolve => {
|
||||
EventHandler.on(document, 'click', '.test', () => {
|
||||
expect().nothing()
|
||||
resolve()
|
||||
})
|
||||
|
||||
fixtureEl.innerHTML = '<div class="test"></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
div.click()
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle mouseenter/mouseleave like the native counterpart', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div class="outer">',
|
||||
'<div class="inner">',
|
||||
'<div class="nested">',
|
||||
'<div class="deep"></div>',
|
||||
'</div>',
|
||||
'</div>',
|
||||
'<div class="sibling"></div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const outer = fixtureEl.querySelector('.outer')
|
||||
const inner = fixtureEl.querySelector('.inner')
|
||||
const nested = fixtureEl.querySelector('.nested')
|
||||
const deep = fixtureEl.querySelector('.deep')
|
||||
const sibling = fixtureEl.querySelector('.sibling')
|
||||
|
||||
const enterSpy = jasmine.createSpy('mouseenter')
|
||||
const leaveSpy = jasmine.createSpy('mouseleave')
|
||||
const delegateEnterSpy = jasmine.createSpy('mouseenter')
|
||||
const delegateLeaveSpy = jasmine.createSpy('mouseleave')
|
||||
|
||||
EventHandler.on(inner, 'mouseenter', enterSpy)
|
||||
EventHandler.on(inner, 'mouseleave', leaveSpy)
|
||||
EventHandler.on(outer, 'mouseenter', '.inner', delegateEnterSpy)
|
||||
EventHandler.on(outer, 'mouseleave', '.inner', delegateLeaveSpy)
|
||||
|
||||
EventHandler.on(sibling, 'mouseenter', () => {
|
||||
expect(enterSpy.calls.count()).toEqual(2)
|
||||
expect(leaveSpy.calls.count()).toEqual(2)
|
||||
expect(delegateEnterSpy.calls.count()).toEqual(2)
|
||||
expect(delegateLeaveSpy.calls.count()).toEqual(2)
|
||||
resolve()
|
||||
})
|
||||
|
||||
const moveMouse = (from, to) => {
|
||||
from.dispatchEvent(new MouseEvent('mouseout', {
|
||||
bubbles: true,
|
||||
relatedTarget: to
|
||||
}))
|
||||
|
||||
to.dispatchEvent(new MouseEvent('mouseover', {
|
||||
bubbles: true,
|
||||
relatedTarget: from
|
||||
}))
|
||||
}
|
||||
|
||||
// from outer to deep and back to outer (nested)
|
||||
moveMouse(outer, inner)
|
||||
moveMouse(inner, nested)
|
||||
moveMouse(nested, deep)
|
||||
moveMouse(deep, nested)
|
||||
moveMouse(nested, inner)
|
||||
moveMouse(inner, outer)
|
||||
|
||||
setTimeout(() => {
|
||||
expect(enterSpy.calls.count()).toEqual(1)
|
||||
expect(leaveSpy.calls.count()).toEqual(1)
|
||||
expect(delegateEnterSpy.calls.count()).toEqual(1)
|
||||
expect(delegateLeaveSpy.calls.count()).toEqual(1)
|
||||
|
||||
// from outer to inner to sibling (adjacent)
|
||||
moveMouse(outer, inner)
|
||||
moveMouse(inner, sibling)
|
||||
}, 20)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('one', () => {
|
||||
it('should call listener just once', () => {
|
||||
return new Promise(resolve => {
|
||||
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)
|
||||
resolve()
|
||||
}, 20)
|
||||
})
|
||||
})
|
||||
|
||||
it('should call delegated listener just once', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
let called = 0
|
||||
const div = fixtureEl.querySelector('div')
|
||||
const obj = {
|
||||
oneListener() {
|
||||
called++
|
||||
}
|
||||
}
|
||||
|
||||
EventHandler.one(fixtureEl, 'bootstrap', 'div', obj.oneListener)
|
||||
|
||||
EventHandler.trigger(div, 'bootstrap')
|
||||
EventHandler.trigger(div, 'bootstrap')
|
||||
|
||||
setTimeout(() => {
|
||||
expect(called).toEqual(1)
|
||||
resolve()
|
||||
}, 20)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('off', () => {
|
||||
it('should not remove a listener', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
EventHandler.off(div, null, noop)
|
||||
EventHandler.off(null, 'click', noop)
|
||||
expect().nothing()
|
||||
})
|
||||
|
||||
it('should remove a listener', () => {
|
||||
return new Promise(resolve => {
|
||||
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)
|
||||
resolve()
|
||||
}, 20)
|
||||
})
|
||||
})
|
||||
|
||||
it('should remove all the events', () => {
|
||||
return new Promise(resolve => {
|
||||
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)
|
||||
resolve()
|
||||
}, 20)
|
||||
})
|
||||
})
|
||||
|
||||
it('should remove all the namespaced listeners if namespace is passed', () => {
|
||||
return new Promise(resolve => {
|
||||
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)
|
||||
resolve()
|
||||
}, 20)
|
||||
})
|
||||
})
|
||||
|
||||
it('should remove the namespaced listeners', () => {
|
||||
return new Promise(resolve => {
|
||||
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)
|
||||
resolve()
|
||||
}, 20)
|
||||
})
|
||||
})
|
||||
|
||||
it('should remove the all the namespaced listeners for native events', () => {
|
||||
return new Promise(resolve => {
|
||||
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)
|
||||
resolve()
|
||||
}, 20)
|
||||
})
|
||||
})
|
||||
|
||||
it('should remove the specified namespaced listeners for native events', () => {
|
||||
return new Promise(resolve => {
|
||||
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)
|
||||
resolve()
|
||||
}, 20)
|
||||
})
|
||||
})
|
||||
|
||||
it('should remove a listener registered by .one', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
const handler = () => {
|
||||
reject(new Error('called'))
|
||||
}
|
||||
|
||||
EventHandler.one(div, 'foobar', handler)
|
||||
EventHandler.off(div, 'foobar', handler)
|
||||
|
||||
EventHandler.trigger(div, 'foobar')
|
||||
setTimeout(() => {
|
||||
expect().nothing()
|
||||
resolve()
|
||||
}, 20)
|
||||
})
|
||||
})
|
||||
|
||||
it('should remove the correct delegated event listener', () => {
|
||||
const element = document.createElement('div')
|
||||
const subelement = document.createElement('span')
|
||||
element.append(subelement)
|
||||
|
||||
const anchor = document.createElement('a')
|
||||
element.append(anchor)
|
||||
|
||||
let i = 0
|
||||
const handler = () => {
|
||||
i++
|
||||
}
|
||||
|
||||
EventHandler.on(element, 'click', 'a', handler)
|
||||
EventHandler.on(element, 'click', 'span', handler)
|
||||
|
||||
fixtureEl.append(element)
|
||||
|
||||
EventHandler.trigger(anchor, 'click')
|
||||
EventHandler.trigger(subelement, 'click')
|
||||
|
||||
// first listeners called
|
||||
expect(i).toEqual(2)
|
||||
|
||||
EventHandler.off(element, 'click', 'span', handler)
|
||||
EventHandler.trigger(subelement, 'click')
|
||||
|
||||
// removed listener not called
|
||||
expect(i).toEqual(2)
|
||||
|
||||
EventHandler.trigger(anchor, 'click')
|
||||
|
||||
// not removed listener called
|
||||
expect(i).toEqual(3)
|
||||
|
||||
EventHandler.on(element, 'click', 'span', handler)
|
||||
EventHandler.trigger(anchor, 'click')
|
||||
EventHandler.trigger(subelement, 'click')
|
||||
|
||||
// listener re-registered
|
||||
expect(i).toEqual(5)
|
||||
|
||||
EventHandler.off(element, 'click', 'span')
|
||||
EventHandler.trigger(subelement, 'click')
|
||||
|
||||
// listener removed again
|
||||
expect(i).toEqual(5)
|
||||
})
|
||||
})
|
||||
|
||||
describe('general functionality', () => {
|
||||
it('should hydrate properties, and make them configurable', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div id="div1">',
|
||||
' <div id="div2"></div>',
|
||||
' <div id="div3"></div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const div1 = fixtureEl.querySelector('#div1')
|
||||
const div2 = fixtureEl.querySelector('#div2')
|
||||
|
||||
EventHandler.on(div1, 'click', event => {
|
||||
expect(event.currentTarget).toBe(div2)
|
||||
expect(event.delegateTarget).toBe(div1)
|
||||
expect(event.originalTarget).toBeNull()
|
||||
|
||||
Object.defineProperty(event, 'currentTarget', {
|
||||
configurable: true,
|
||||
get() {
|
||||
return div1
|
||||
}
|
||||
})
|
||||
|
||||
expect(event.currentTarget).toBe(div1)
|
||||
resolve()
|
||||
})
|
||||
|
||||
expect(() => {
|
||||
EventHandler.trigger(div1, 'click', { originalTarget: null, currentTarget: div2 })
|
||||
}).not.toThrowError(TypeError)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
135
js/tests/unit/dom/manipulator.spec.js
Normal file
135
js/tests/unit/dom/manipulator.spec.js
Normal file
|
@ -0,0 +1,135 @@
|
|||
import Manipulator from '../../../src/dom/manipulator'
|
||||
import { clearFixture, getFixture } from '../../helpers/fixture'
|
||||
|
||||
describe('Manipulator', () => {
|
||||
let fixtureEl
|
||||
|
||||
beforeAll(() => {
|
||||
fixtureEl = getFixture()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
clearFixture()
|
||||
})
|
||||
|
||||
describe('setDataAttribute', () => {
|
||||
it('should set data attribute prefixed with bs', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
Manipulator.setDataAttribute(div, 'key', 'value')
|
||||
expect(div.getAttribute('data-bs-key')).toEqual('value')
|
||||
})
|
||||
|
||||
it('should set data attribute in kebab case', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
Manipulator.setDataAttribute(div, 'testKey', 'value')
|
||||
expect(div.getAttribute('data-bs-test-key')).toEqual('value')
|
||||
})
|
||||
})
|
||||
|
||||
describe('removeDataAttribute', () => {
|
||||
it('should only remove bs-prefixed data attribute', () => {
|
||||
fixtureEl.innerHTML = '<div data-bs-key="value" data-key-bs="postfixed" data-key="value"></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
Manipulator.removeDataAttribute(div, 'key')
|
||||
expect(div.getAttribute('data-bs-key')).toBeNull()
|
||||
expect(div.getAttribute('data-key-bs')).toEqual('postfixed')
|
||||
expect(div.getAttribute('data-key')).toEqual('value')
|
||||
})
|
||||
|
||||
it('should remove data attribute in kebab case', () => {
|
||||
fixtureEl.innerHTML = '<div data-bs-test-key="value"></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
Manipulator.removeDataAttribute(div, 'testKey')
|
||||
expect(div.getAttribute('data-bs-test-key')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getDataAttributes', () => {
|
||||
it('should return an empty object for null', () => {
|
||||
expect(Manipulator.getDataAttributes(null)).toEqual({})
|
||||
expect().nothing()
|
||||
})
|
||||
|
||||
it('should get only bs-prefixed data attributes without bs namespace', () => {
|
||||
fixtureEl.innerHTML = '<div data-bs-toggle="tabs" data-bs-target="#element" data-another="value" data-target-bs="#element" data-in-bs-out="in-between"></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Manipulator.getDataAttributes(div)).toEqual({
|
||||
toggle: 'tabs',
|
||||
target: '#element'
|
||||
})
|
||||
})
|
||||
|
||||
it('should omit `bs-config` data attribute', () => {
|
||||
fixtureEl.innerHTML = '<div data-bs-toggle="tabs" data-bs-target="#element" data-bs-config=\'{"testBool":false}\'></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Manipulator.getDataAttributes(div)).toEqual({
|
||||
toggle: 'tabs',
|
||||
target: '#element'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getDataAttribute', () => {
|
||||
it('should only get bs-prefixed data attribute', () => {
|
||||
fixtureEl.innerHTML = '<div data-bs-key="value" data-test-bs="postFixed" data-toggle="tab"></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Manipulator.getDataAttribute(div, 'key')).toEqual('value')
|
||||
expect(Manipulator.getDataAttribute(div, 'test')).toBeNull()
|
||||
expect(Manipulator.getDataAttribute(div, 'toggle')).toBeNull()
|
||||
})
|
||||
|
||||
it('should get data attribute in kebab case', () => {
|
||||
fixtureEl.innerHTML = '<div data-bs-test-key="value" ></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Manipulator.getDataAttribute(div, 'testKey')).toEqual('value')
|
||||
})
|
||||
|
||||
it('should normalize data', () => {
|
||||
fixtureEl.innerHTML = '<div data-bs-test="false" ></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Manipulator.getDataAttribute(div, 'test')).toBeFalse()
|
||||
|
||||
div.setAttribute('data-bs-test', 'true')
|
||||
expect(Manipulator.getDataAttribute(div, 'test')).toBeTrue()
|
||||
|
||||
div.setAttribute('data-bs-test', '1')
|
||||
expect(Manipulator.getDataAttribute(div, 'test')).toEqual(1)
|
||||
})
|
||||
|
||||
it('should normalize json data', () => {
|
||||
fixtureEl.innerHTML = '<div data-bs-test=\'{"delay":{"show":100,"hide":10}}\'></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Manipulator.getDataAttribute(div, 'test')).toEqual({ delay: { show: 100, hide: 10 } })
|
||||
|
||||
const objectData = { 'Super Hero': ['Iron Man', 'Super Man'], testNum: 90, url: 'http://localhost:8080/test?foo=bar' }
|
||||
const dataStr = JSON.stringify(objectData)
|
||||
div.setAttribute('data-bs-test', encodeURIComponent(dataStr))
|
||||
expect(Manipulator.getDataAttribute(div, 'test')).toEqual(objectData)
|
||||
|
||||
div.setAttribute('data-bs-test', dataStr)
|
||||
expect(Manipulator.getDataAttribute(div, 'test')).toEqual(objectData)
|
||||
})
|
||||
})
|
||||
})
|
236
js/tests/unit/dom/selector-engine.spec.js
Normal file
236
js/tests/unit/dom/selector-engine.spec.js
Normal file
|
@ -0,0 +1,236 @@
|
|||
import SelectorEngine from '../../../src/dom/selector-engine'
|
||||
import { getFixture, clearFixture } from '../../helpers/fixture'
|
||||
|
||||
describe('SelectorEngine', () => {
|
||||
let fixtureEl
|
||||
|
||||
beforeAll(() => {
|
||||
fixtureEl = getFixture()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
clearFixture()
|
||||
})
|
||||
|
||||
describe('find', () => {
|
||||
it('should find elements', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(SelectorEngine.find('div', fixtureEl)).toEqual([div])
|
||||
})
|
||||
|
||||
it('should find elements globally', () => {
|
||||
fixtureEl.innerHTML = '<div id="test"></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(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>'
|
||||
].join('')
|
||||
|
||||
const listEl = fixtureEl.querySelector('ul')
|
||||
const aActive = fixtureEl.querySelector('.active')
|
||||
|
||||
expect(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>'
|
||||
].join('')
|
||||
|
||||
const list = fixtureEl.querySelector('ul')
|
||||
const liList = [].concat(...fixtureEl.querySelectorAll('li'))
|
||||
const result = SelectorEngine.children(list, 'li')
|
||||
|
||||
expect(result).toEqual(liList)
|
||||
})
|
||||
})
|
||||
|
||||
describe('parents', () => {
|
||||
it('should return parents', () => {
|
||||
expect(SelectorEngine.parents(fixtureEl, 'body')).toHaveSize(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])
|
||||
})
|
||||
|
||||
it('should return previous element with comments or text nodes between', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div class="test"></div>',
|
||||
'<div class="test"></div>',
|
||||
'<!-- Comment-->',
|
||||
'Text',
|
||||
'<button class="btn"></button>'
|
||||
].join('')
|
||||
|
||||
const btn = fixtureEl.querySelector('.btn')
|
||||
const divTest = fixtureEl.querySelectorAll('.test')[1]
|
||||
|
||||
expect(SelectorEngine.prev(btn, '.test')).toEqual([divTest])
|
||||
})
|
||||
})
|
||||
|
||||
describe('next', () => {
|
||||
it('should return next element', () => {
|
||||
fixtureEl.innerHTML = '<div class="test"></div><button class="btn"></button>'
|
||||
|
||||
const btn = fixtureEl.querySelector('.btn')
|
||||
const divTest = fixtureEl.querySelector('.test')
|
||||
|
||||
expect(SelectorEngine.next(divTest, '.btn')).toEqual([btn])
|
||||
})
|
||||
|
||||
it('should return next 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.next(divTest, '.btn')).toEqual([btn])
|
||||
})
|
||||
|
||||
it('should return next element with comments or text nodes between', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div class="test"></div>',
|
||||
'<!-- Comment-->',
|
||||
'Text',
|
||||
'<button class="btn"></button>',
|
||||
'<button class="btn"></button>'
|
||||
].join('')
|
||||
|
||||
const btn = fixtureEl.querySelector('.btn')
|
||||
const divTest = fixtureEl.querySelector('.test')
|
||||
|
||||
expect(SelectorEngine.next(divTest, '.btn')).toEqual([btn])
|
||||
})
|
||||
})
|
||||
|
||||
describe('focusableChildren', () => {
|
||||
it('should return only elements with specific tag names', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div>lorem</div>',
|
||||
'<span>lorem</span>',
|
||||
'<a>lorem</a>',
|
||||
'<button>lorem</button>',
|
||||
'<input>',
|
||||
'<textarea></textarea>',
|
||||
'<select></select>',
|
||||
'<details>lorem</details>'
|
||||
].join('')
|
||||
|
||||
const expectedElements = [
|
||||
fixtureEl.querySelector('a'),
|
||||
fixtureEl.querySelector('button'),
|
||||
fixtureEl.querySelector('input'),
|
||||
fixtureEl.querySelector('textarea'),
|
||||
fixtureEl.querySelector('select'),
|
||||
fixtureEl.querySelector('details')
|
||||
]
|
||||
|
||||
expect(SelectorEngine.focusableChildren(fixtureEl)).toEqual(expectedElements)
|
||||
})
|
||||
|
||||
it('should return any element with non negative tab index', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div tabindex>lorem</div>',
|
||||
'<div tabindex="0">lorem</div>',
|
||||
'<div tabindex="10">lorem</div>'
|
||||
].join('')
|
||||
|
||||
const expectedElements = [
|
||||
fixtureEl.querySelector('[tabindex]'),
|
||||
fixtureEl.querySelector('[tabindex="0"]'),
|
||||
fixtureEl.querySelector('[tabindex="10"]')
|
||||
]
|
||||
|
||||
expect(SelectorEngine.focusableChildren(fixtureEl)).toEqual(expectedElements)
|
||||
})
|
||||
|
||||
it('should return not return elements with negative tab index', () => {
|
||||
fixtureEl.innerHTML = '<button tabindex="-1">lorem</button>'
|
||||
|
||||
const expectedElements = []
|
||||
|
||||
expect(SelectorEngine.focusableChildren(fixtureEl)).toEqual(expectedElements)
|
||||
})
|
||||
|
||||
it('should return contenteditable elements', () => {
|
||||
fixtureEl.innerHTML = '<div contenteditable="true">lorem</div>'
|
||||
|
||||
const expectedElements = [fixtureEl.querySelector('[contenteditable="true"]')]
|
||||
|
||||
expect(SelectorEngine.focusableChildren(fixtureEl)).toEqual(expectedElements)
|
||||
})
|
||||
|
||||
it('should not return disabled elements', () => {
|
||||
fixtureEl.innerHTML = '<button disabled="true">lorem</button>'
|
||||
|
||||
const expectedElements = []
|
||||
|
||||
expect(SelectorEngine.focusableChildren(fixtureEl)).toEqual(expectedElements)
|
||||
})
|
||||
|
||||
it('should not return invisible elements', () => {
|
||||
fixtureEl.innerHTML = '<button style="display:none;">lorem</button>'
|
||||
|
||||
const expectedElements = []
|
||||
|
||||
expect(SelectorEngine.focusableChildren(fixtureEl)).toEqual(expectedElements)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
2430
js/tests/unit/dropdown.spec.js
Normal file
2430
js/tests/unit/dropdown.spec.js
Normal file
File diff suppressed because it is too large
Load diff
60
js/tests/unit/jquery.spec.js
Normal file
60
js/tests/unit/jquery.spec.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
/* eslint-env jquery */
|
||||
|
||||
import Alert from '../../src/alert'
|
||||
import Button from '../../src/button'
|
||||
import Carousel from '../../src/carousel'
|
||||
import Collapse from '../../src/collapse'
|
||||
import Dropdown from '../../src/dropdown'
|
||||
import Modal from '../../src/modal'
|
||||
import Offcanvas from '../../src/offcanvas'
|
||||
import Popover from '../../src/popover'
|
||||
import ScrollSpy from '../../src/scrollspy'
|
||||
import Tab from '../../src/tab'
|
||||
import Toast from '../../src/toast'
|
||||
import Tooltip from '../../src/tooltip'
|
||||
import { clearFixture, getFixture } from '../helpers/fixture'
|
||||
|
||||
describe('jQuery', () => {
|
||||
let fixtureEl
|
||||
|
||||
beforeAll(() => {
|
||||
fixtureEl = getFixture()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
clearFixture()
|
||||
})
|
||||
|
||||
it('should add all plugins in jQuery', () => {
|
||||
expect(Alert.jQueryInterface).toEqual(jQuery.fn.alert)
|
||||
expect(Button.jQueryInterface).toEqual(jQuery.fn.button)
|
||||
expect(Carousel.jQueryInterface).toEqual(jQuery.fn.carousel)
|
||||
expect(Collapse.jQueryInterface).toEqual(jQuery.fn.collapse)
|
||||
expect(Dropdown.jQueryInterface).toEqual(jQuery.fn.dropdown)
|
||||
expect(Modal.jQueryInterface).toEqual(jQuery.fn.modal)
|
||||
expect(Offcanvas.jQueryInterface).toEqual(jQuery.fn.offcanvas)
|
||||
expect(Popover.jQueryInterface).toEqual(jQuery.fn.popover)
|
||||
expect(ScrollSpy.jQueryInterface).toEqual(jQuery.fn.scrollspy)
|
||||
expect(Tab.jQueryInterface).toEqual(jQuery.fn.tab)
|
||||
expect(Toast.jQueryInterface).toEqual(jQuery.fn.toast)
|
||||
expect(Tooltip.jQueryInterface).toEqual(jQuery.fn.tooltip)
|
||||
})
|
||||
|
||||
it('should use jQuery event system', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div class="alert">',
|
||||
' <button type="button" data-bs-dismiss="alert">x</button>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
$(fixtureEl).find('.alert')
|
||||
.one('closed.bs.alert', () => {
|
||||
expect($(fixtureEl).find('.alert')).toHaveSize(0)
|
||||
resolve()
|
||||
})
|
||||
|
||||
$(fixtureEl).find('button').trigger('click')
|
||||
})
|
||||
})
|
||||
})
|
1298
js/tests/unit/modal.spec.js
Normal file
1298
js/tests/unit/modal.spec.js
Normal file
File diff suppressed because it is too large
Load diff
912
js/tests/unit/offcanvas.spec.js
Normal file
912
js/tests/unit/offcanvas.spec.js
Normal file
|
@ -0,0 +1,912 @@
|
|||
import Offcanvas from '../../src/offcanvas'
|
||||
import EventHandler from '../../src/dom/event-handler'
|
||||
import { clearBodyAndDocument, clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'
|
||||
import { isVisible } from '../../src/util/index'
|
||||
import ScrollBarHelper from '../../src/util/scrollbar'
|
||||
|
||||
describe('Offcanvas', () => {
|
||||
let fixtureEl
|
||||
|
||||
beforeAll(() => {
|
||||
fixtureEl = getFixture()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
clearFixture()
|
||||
document.body.classList.remove('offcanvas-open')
|
||||
clearBodyAndDocument()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
clearBodyAndDocument()
|
||||
})
|
||||
|
||||
describe('VERSION', () => {
|
||||
it('should return plugin version', () => {
|
||||
expect(Offcanvas.VERSION).toEqual(jasmine.any(String))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Default', () => {
|
||||
it('should return plugin default config', () => {
|
||||
expect(Offcanvas.Default).toEqual(jasmine.any(Object))
|
||||
})
|
||||
})
|
||||
|
||||
describe('DATA_KEY', () => {
|
||||
it('should return plugin data key', () => {
|
||||
expect(Offcanvas.DATA_KEY).toEqual('bs.offcanvas')
|
||||
})
|
||||
})
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should call hide when a element with data-bs-dismiss="offcanvas" is clicked', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div class="offcanvas">',
|
||||
' <a href="#" data-bs-dismiss="offcanvas">Close</a>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const offCanvasEl = fixtureEl.querySelector('.offcanvas')
|
||||
const closeEl = fixtureEl.querySelector('a')
|
||||
const offCanvas = new Offcanvas(offCanvasEl)
|
||||
|
||||
const spy = spyOn(offCanvas, 'hide')
|
||||
|
||||
closeEl.click()
|
||||
|
||||
expect(offCanvas._config.keyboard).toBeTrue()
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should hide if esc is pressed', () => {
|
||||
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
|
||||
|
||||
const offCanvasEl = fixtureEl.querySelector('.offcanvas')
|
||||
const offCanvas = new Offcanvas(offCanvasEl)
|
||||
const keyDownEsc = createEvent('keydown')
|
||||
keyDownEsc.key = 'Escape'
|
||||
|
||||
const spy = spyOn(offCanvas, 'hide')
|
||||
|
||||
offCanvasEl.dispatchEvent(keyDownEsc)
|
||||
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should hide if esc is pressed and backdrop is static', () => {
|
||||
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
|
||||
|
||||
const offCanvasEl = fixtureEl.querySelector('.offcanvas')
|
||||
const offCanvas = new Offcanvas(offCanvasEl, { backdrop: 'static' })
|
||||
const keyDownEsc = createEvent('keydown')
|
||||
keyDownEsc.key = 'Escape'
|
||||
|
||||
const spy = spyOn(offCanvas, 'hide')
|
||||
|
||||
offCanvasEl.dispatchEvent(keyDownEsc)
|
||||
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not hide if esc is not pressed', () => {
|
||||
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
|
||||
|
||||
const offCanvasEl = fixtureEl.querySelector('.offcanvas')
|
||||
const offCanvas = new Offcanvas(offCanvasEl)
|
||||
const keydownTab = createEvent('keydown')
|
||||
keydownTab.key = 'Tab'
|
||||
|
||||
const spy = spyOn(offCanvas, 'hide')
|
||||
|
||||
offCanvasEl.dispatchEvent(keydownTab)
|
||||
|
||||
expect(spy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not hide if esc is pressed but with keyboard = false', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
|
||||
|
||||
const offCanvasEl = fixtureEl.querySelector('.offcanvas')
|
||||
const offCanvas = new Offcanvas(offCanvasEl, { keyboard: false })
|
||||
const keyDownEsc = createEvent('keydown')
|
||||
keyDownEsc.key = 'Escape'
|
||||
|
||||
const spy = spyOn(offCanvas, 'hide')
|
||||
const hidePreventedSpy = jasmine.createSpy('hidePrevented')
|
||||
offCanvasEl.addEventListener('hidePrevented.bs.offcanvas', hidePreventedSpy)
|
||||
|
||||
offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
|
||||
expect(offCanvas._config.keyboard).toBeFalse()
|
||||
offCanvasEl.dispatchEvent(keyDownEsc)
|
||||
|
||||
expect(hidePreventedSpy).toHaveBeenCalled()
|
||||
expect(spy).not.toHaveBeenCalled()
|
||||
resolve()
|
||||
})
|
||||
|
||||
offCanvas.show()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not hide if user clicks on static backdrop', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
|
||||
|
||||
const offCanvasEl = fixtureEl.querySelector('div')
|
||||
const offCanvas = new Offcanvas(offCanvasEl, { backdrop: 'static' })
|
||||
|
||||
const clickEvent = new Event('mousedown', { bubbles: true, cancelable: true })
|
||||
const spyClick = spyOn(offCanvas._backdrop._config, 'clickCallback').and.callThrough()
|
||||
const spyHide = spyOn(offCanvas._backdrop, 'hide').and.callThrough()
|
||||
const hidePreventedSpy = jasmine.createSpy('hidePrevented')
|
||||
offCanvasEl.addEventListener('hidePrevented.bs.offcanvas', hidePreventedSpy)
|
||||
|
||||
offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
|
||||
expect(spyClick).toEqual(jasmine.any(Function))
|
||||
|
||||
offCanvas._backdrop._getElement().dispatchEvent(clickEvent)
|
||||
expect(hidePreventedSpy).toHaveBeenCalled()
|
||||
expect(spyHide).not.toHaveBeenCalled()
|
||||
resolve()
|
||||
})
|
||||
|
||||
offCanvas.show()
|
||||
})
|
||||
})
|
||||
|
||||
it('should call `hide` on resize, if element\'s position is not fixed any more', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div class="offcanvas-lg"></div>'
|
||||
|
||||
const offCanvasEl = fixtureEl.querySelector('div')
|
||||
const offCanvas = new Offcanvas(offCanvasEl)
|
||||
|
||||
const spy = spyOn(offCanvas, 'hide').and.callThrough()
|
||||
|
||||
offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
|
||||
const resizeEvent = createEvent('resize')
|
||||
offCanvasEl.style.removeProperty('position')
|
||||
|
||||
window.dispatchEvent(resizeEvent)
|
||||
expect(spy).toHaveBeenCalled()
|
||||
resolve()
|
||||
})
|
||||
|
||||
offCanvas.show()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('config', () => {
|
||||
it('should have default values', () => {
|
||||
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
|
||||
|
||||
const offCanvasEl = fixtureEl.querySelector('.offcanvas')
|
||||
const offCanvas = new Offcanvas(offCanvasEl)
|
||||
|
||||
expect(offCanvas._config.backdrop).toBeTrue()
|
||||
expect(offCanvas._backdrop._config.isVisible).toBeTrue()
|
||||
expect(offCanvas._config.keyboard).toBeTrue()
|
||||
expect(offCanvas._config.scroll).toBeFalse()
|
||||
})
|
||||
|
||||
it('should read data attributes and override default config', () => {
|
||||
fixtureEl.innerHTML = '<div class="offcanvas" data-bs-scroll="true" data-bs-backdrop="false" data-bs-keyboard="false"></div>'
|
||||
|
||||
const offCanvasEl = fixtureEl.querySelector('.offcanvas')
|
||||
const offCanvas = new Offcanvas(offCanvasEl)
|
||||
|
||||
expect(offCanvas._config.backdrop).toBeFalse()
|
||||
expect(offCanvas._backdrop._config.isVisible).toBeFalse()
|
||||
expect(offCanvas._config.keyboard).toBeFalse()
|
||||
expect(offCanvas._config.scroll).toBeTrue()
|
||||
})
|
||||
|
||||
it('given a config object must override data attributes', () => {
|
||||
fixtureEl.innerHTML = '<div class="offcanvas" data-bs-scroll="true" data-bs-backdrop="false" data-bs-keyboard="false"></div>'
|
||||
|
||||
const offCanvasEl = fixtureEl.querySelector('.offcanvas')
|
||||
const offCanvas = new Offcanvas(offCanvasEl, {
|
||||
backdrop: true,
|
||||
keyboard: true,
|
||||
scroll: false
|
||||
})
|
||||
expect(offCanvas._config.backdrop).toBeTrue()
|
||||
expect(offCanvas._config.keyboard).toBeTrue()
|
||||
expect(offCanvas._config.scroll).toBeFalse()
|
||||
})
|
||||
})
|
||||
|
||||
describe('options', () => {
|
||||
it('if scroll is enabled, should allow body to scroll while offcanvas is open', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
|
||||
|
||||
const spyHide = spyOn(ScrollBarHelper.prototype, 'hide').and.callThrough()
|
||||
const spyReset = spyOn(ScrollBarHelper.prototype, 'reset').and.callThrough()
|
||||
const offCanvasEl = fixtureEl.querySelector('.offcanvas')
|
||||
const offCanvas = new Offcanvas(offCanvasEl, { scroll: true })
|
||||
|
||||
offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
|
||||
expect(spyHide).not.toHaveBeenCalled()
|
||||
offCanvas.hide()
|
||||
})
|
||||
offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
|
||||
expect(spyReset).not.toHaveBeenCalled()
|
||||
resolve()
|
||||
})
|
||||
offCanvas.show()
|
||||
})
|
||||
})
|
||||
|
||||
it('if scroll is disabled, should call ScrollBarHelper to handle scrollBar on body', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
|
||||
|
||||
const spyHide = spyOn(ScrollBarHelper.prototype, 'hide').and.callThrough()
|
||||
const spyReset = spyOn(ScrollBarHelper.prototype, 'reset').and.callThrough()
|
||||
const offCanvasEl = fixtureEl.querySelector('.offcanvas')
|
||||
const offCanvas = new Offcanvas(offCanvasEl, { scroll: false })
|
||||
|
||||
offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
|
||||
expect(spyHide).toHaveBeenCalled()
|
||||
offCanvas.hide()
|
||||
})
|
||||
offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
|
||||
expect(spyReset).toHaveBeenCalled()
|
||||
resolve()
|
||||
})
|
||||
offCanvas.show()
|
||||
})
|
||||
})
|
||||
|
||||
it('should hide a shown element if user click on backdrop', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
|
||||
|
||||
const offCanvasEl = fixtureEl.querySelector('div')
|
||||
const offCanvas = new Offcanvas(offCanvasEl, { backdrop: true })
|
||||
|
||||
const clickEvent = new Event('mousedown', { bubbles: true, cancelable: true })
|
||||
const spy = spyOn(offCanvas._backdrop._config, 'clickCallback').and.callThrough()
|
||||
|
||||
offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
|
||||
expect(offCanvas._backdrop._config.clickCallback).toEqual(jasmine.any(Function))
|
||||
|
||||
offCanvas._backdrop._getElement().dispatchEvent(clickEvent)
|
||||
})
|
||||
|
||||
offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
|
||||
expect(spy).toHaveBeenCalled()
|
||||
resolve()
|
||||
})
|
||||
|
||||
offCanvas.show()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not trap focus if scroll is allowed', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
|
||||
|
||||
const offCanvasEl = fixtureEl.querySelector('.offcanvas')
|
||||
const offCanvas = new Offcanvas(offCanvasEl, {
|
||||
scroll: true,
|
||||
backdrop: false
|
||||
})
|
||||
|
||||
const spy = spyOn(offCanvas._focustrap, 'activate').and.callThrough()
|
||||
|
||||
offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
|
||||
expect(spy).not.toHaveBeenCalled()
|
||||
resolve()
|
||||
})
|
||||
|
||||
offCanvas.show()
|
||||
})
|
||||
})
|
||||
|
||||
it('should trap focus if scroll is allowed OR backdrop is enabled', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
|
||||
|
||||
const offCanvasEl = fixtureEl.querySelector('.offcanvas')
|
||||
const offCanvas = new Offcanvas(offCanvasEl, {
|
||||
scroll: true,
|
||||
backdrop: true
|
||||
})
|
||||
|
||||
const spy = spyOn(offCanvas._focustrap, 'activate').and.callThrough()
|
||||
|
||||
offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
|
||||
expect(spy).toHaveBeenCalled()
|
||||
resolve()
|
||||
})
|
||||
|
||||
offCanvas.show()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('toggle', () => {
|
||||
it('should call show method if show class is not present', () => {
|
||||
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
|
||||
|
||||
const offCanvasEl = fixtureEl.querySelector('.offcanvas')
|
||||
const offCanvas = new Offcanvas(offCanvasEl)
|
||||
|
||||
const spy = spyOn(offCanvas, 'show')
|
||||
|
||||
offCanvas.toggle()
|
||||
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should call hide method if show class is present', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
|
||||
|
||||
const offCanvasEl = fixtureEl.querySelector('.offcanvas')
|
||||
const offCanvas = new Offcanvas(offCanvasEl)
|
||||
|
||||
offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
|
||||
expect(offCanvasEl).toHaveClass('show')
|
||||
const spy = spyOn(offCanvas, 'hide')
|
||||
|
||||
offCanvas.toggle()
|
||||
|
||||
expect(spy).toHaveBeenCalled()
|
||||
resolve()
|
||||
})
|
||||
|
||||
offCanvas.show()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('show', () => {
|
||||
it('should add `showing` class during opening and `show` class on end', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
|
||||
const offCanvasEl = fixtureEl.querySelector('.offcanvas')
|
||||
const offCanvas = new Offcanvas(offCanvasEl)
|
||||
|
||||
offCanvasEl.addEventListener('show.bs.offcanvas', () => {
|
||||
expect(offCanvasEl).not.toHaveClass('show')
|
||||
})
|
||||
|
||||
offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
|
||||
expect(offCanvasEl).not.toHaveClass('showing')
|
||||
expect(offCanvasEl).toHaveClass('show')
|
||||
resolve()
|
||||
})
|
||||
|
||||
offCanvas.show()
|
||||
expect(offCanvasEl).toHaveClass('showing')
|
||||
})
|
||||
})
|
||||
|
||||
it('should do nothing if already shown', () => {
|
||||
fixtureEl.innerHTML = '<div class="offcanvas show"></div>'
|
||||
|
||||
const offCanvasEl = fixtureEl.querySelector('div')
|
||||
const offCanvas = new Offcanvas(offCanvasEl)
|
||||
offCanvas.show()
|
||||
|
||||
expect(offCanvasEl).toHaveClass('show')
|
||||
|
||||
const spyShow = spyOn(offCanvas._backdrop, 'show').and.callThrough()
|
||||
const spyTrigger = spyOn(EventHandler, 'trigger').and.callThrough()
|
||||
offCanvas.show()
|
||||
|
||||
expect(spyTrigger).not.toHaveBeenCalled()
|
||||
expect(spyShow).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should show a hidden element', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
|
||||
|
||||
const offCanvasEl = fixtureEl.querySelector('div')
|
||||
const offCanvas = new Offcanvas(offCanvasEl)
|
||||
const spy = spyOn(offCanvas._backdrop, 'show').and.callThrough()
|
||||
|
||||
offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
|
||||
expect(offCanvasEl).toHaveClass('show')
|
||||
expect(spy).toHaveBeenCalled()
|
||||
resolve()
|
||||
})
|
||||
|
||||
offCanvas.show()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not fire shown when show is prevented', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
|
||||
|
||||
const offCanvasEl = fixtureEl.querySelector('div')
|
||||
const offCanvas = new Offcanvas(offCanvasEl)
|
||||
const spy = spyOn(offCanvas._backdrop, 'show').and.callThrough()
|
||||
|
||||
const expectEnd = () => {
|
||||
setTimeout(() => {
|
||||
expect(spy).not.toHaveBeenCalled()
|
||||
resolve()
|
||||
}, 10)
|
||||
}
|
||||
|
||||
offCanvasEl.addEventListener('show.bs.offcanvas', event => {
|
||||
event.preventDefault()
|
||||
expectEnd()
|
||||
})
|
||||
|
||||
offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
|
||||
reject(new Error('should not fire shown event'))
|
||||
})
|
||||
|
||||
offCanvas.show()
|
||||
})
|
||||
})
|
||||
|
||||
it('on window load, should make visible an offcanvas element, if its markup contains class "show"', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div class="offcanvas show"></div>'
|
||||
|
||||
const offCanvasEl = fixtureEl.querySelector('div')
|
||||
const spy = spyOn(Offcanvas.prototype, 'show').and.callThrough()
|
||||
|
||||
offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
|
||||
resolve()
|
||||
})
|
||||
|
||||
window.dispatchEvent(createEvent('load'))
|
||||
|
||||
const instance = Offcanvas.getInstance(offCanvasEl)
|
||||
expect(instance).not.toBeNull()
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
it('should trap focus', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
|
||||
|
||||
const offCanvasEl = fixtureEl.querySelector('.offcanvas')
|
||||
const offCanvas = new Offcanvas(offCanvasEl)
|
||||
|
||||
const spy = spyOn(offCanvas._focustrap, 'activate').and.callThrough()
|
||||
|
||||
offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
|
||||
expect(spy).toHaveBeenCalled()
|
||||
resolve()
|
||||
})
|
||||
|
||||
offCanvas.show()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('hide', () => {
|
||||
it('should add `hiding` class during closing and remover `show` & `hiding` classes on end', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
|
||||
const offCanvasEl = fixtureEl.querySelector('.offcanvas')
|
||||
const offCanvas = new Offcanvas(offCanvasEl)
|
||||
|
||||
offCanvasEl.addEventListener('hide.bs.offcanvas', () => {
|
||||
expect(offCanvasEl).not.toHaveClass('showing')
|
||||
expect(offCanvasEl).toHaveClass('show')
|
||||
})
|
||||
|
||||
offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
|
||||
expect(offCanvasEl).not.toHaveClass('hiding')
|
||||
expect(offCanvasEl).not.toHaveClass('show')
|
||||
resolve()
|
||||
})
|
||||
|
||||
offCanvas.show()
|
||||
offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
|
||||
offCanvas.hide()
|
||||
expect(offCanvasEl).not.toHaveClass('showing')
|
||||
expect(offCanvasEl).toHaveClass('hiding')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should do nothing if already shown', () => {
|
||||
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
|
||||
|
||||
const spyTrigger = spyOn(EventHandler, 'trigger').and.callThrough()
|
||||
|
||||
const offCanvasEl = fixtureEl.querySelector('div')
|
||||
const offCanvas = new Offcanvas(offCanvasEl)
|
||||
const spyHide = spyOn(offCanvas._backdrop, 'hide').and.callThrough()
|
||||
|
||||
offCanvas.hide()
|
||||
expect(spyHide).not.toHaveBeenCalled()
|
||||
expect(spyTrigger).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should hide a shown element', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
|
||||
|
||||
const offCanvasEl = fixtureEl.querySelector('div')
|
||||
const offCanvas = new Offcanvas(offCanvasEl)
|
||||
const spy = spyOn(offCanvas._backdrop, 'hide').and.callThrough()
|
||||
offCanvas.show()
|
||||
|
||||
offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
|
||||
expect(offCanvasEl).not.toHaveClass('show')
|
||||
expect(spy).toHaveBeenCalled()
|
||||
resolve()
|
||||
})
|
||||
|
||||
offCanvas.hide()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not fire hidden when hide is prevented', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
|
||||
|
||||
const offCanvasEl = fixtureEl.querySelector('div')
|
||||
const offCanvas = new Offcanvas(offCanvasEl)
|
||||
const spy = spyOn(offCanvas._backdrop, 'hide').and.callThrough()
|
||||
|
||||
offCanvas.show()
|
||||
|
||||
const expectEnd = () => {
|
||||
setTimeout(() => {
|
||||
expect(spy).not.toHaveBeenCalled()
|
||||
resolve()
|
||||
}, 10)
|
||||
}
|
||||
|
||||
offCanvasEl.addEventListener('hide.bs.offcanvas', event => {
|
||||
event.preventDefault()
|
||||
expectEnd()
|
||||
})
|
||||
|
||||
offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
|
||||
reject(new Error('should not fire hidden event'))
|
||||
})
|
||||
|
||||
offCanvas.hide()
|
||||
})
|
||||
})
|
||||
|
||||
it('should release focus trap', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
|
||||
|
||||
const offCanvasEl = fixtureEl.querySelector('div')
|
||||
const offCanvas = new Offcanvas(offCanvasEl)
|
||||
const spy = spyOn(offCanvas._focustrap, 'deactivate').and.callThrough()
|
||||
offCanvas.show()
|
||||
|
||||
offCanvasEl.addEventListener('hidden.bs.offcanvas', () => {
|
||||
expect(spy).toHaveBeenCalled()
|
||||
resolve()
|
||||
})
|
||||
|
||||
offCanvas.hide()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('dispose', () => {
|
||||
it('should dispose an offcanvas', () => {
|
||||
fixtureEl.innerHTML = '<div class="offcanvas"></div>'
|
||||
|
||||
const offCanvasEl = fixtureEl.querySelector('div')
|
||||
const offCanvas = new Offcanvas(offCanvasEl)
|
||||
const backdrop = offCanvas._backdrop
|
||||
const spyDispose = spyOn(backdrop, 'dispose').and.callThrough()
|
||||
const focustrap = offCanvas._focustrap
|
||||
const spyDeactivate = spyOn(focustrap, 'deactivate').and.callThrough()
|
||||
|
||||
expect(Offcanvas.getInstance(offCanvasEl)).toEqual(offCanvas)
|
||||
|
||||
offCanvas.dispose()
|
||||
|
||||
expect(spyDispose).toHaveBeenCalled()
|
||||
expect(offCanvas._backdrop).toBeNull()
|
||||
expect(spyDeactivate).toHaveBeenCalled()
|
||||
expect(offCanvas._focustrap).toBeNull()
|
||||
expect(Offcanvas.getInstance(offCanvasEl)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('data-api', () => {
|
||||
it('should not prevent event for input', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<input type="checkbox" data-bs-toggle="offcanvas" data-bs-target="#offcanvasdiv1">',
|
||||
'<div id="offcanvasdiv1" class="offcanvas"></div>'
|
||||
].join('')
|
||||
|
||||
const target = fixtureEl.querySelector('input')
|
||||
const offCanvasEl = fixtureEl.querySelector('#offcanvasdiv1')
|
||||
|
||||
offCanvasEl.addEventListener('shown.bs.offcanvas', () => {
|
||||
expect(offCanvasEl).toHaveClass('show')
|
||||
expect(target.checked).toBeTrue()
|
||||
resolve()
|
||||
})
|
||||
|
||||
target.click()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not call toggle on disabled elements', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a href="#" data-bs-toggle="offcanvas" data-bs-target="#offcanvasdiv1" class="disabled"></a>',
|
||||
'<div id="offcanvasdiv1" class="offcanvas"></div>'
|
||||
].join('')
|
||||
|
||||
const target = fixtureEl.querySelector('a')
|
||||
|
||||
const spy = spyOn(Offcanvas.prototype, 'toggle')
|
||||
|
||||
target.click()
|
||||
|
||||
expect(spy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should call hide first, if another offcanvas is open', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<button id="btn2" data-bs-toggle="offcanvas" data-bs-target="#offcanvas2"></button>',
|
||||
'<div id="offcanvas1" class="offcanvas"></div>',
|
||||
'<div id="offcanvas2" class="offcanvas"></div>'
|
||||
].join('')
|
||||
|
||||
const trigger2 = fixtureEl.querySelector('#btn2')
|
||||
const offcanvasEl1 = document.querySelector('#offcanvas1')
|
||||
const offcanvasEl2 = document.querySelector('#offcanvas2')
|
||||
const offcanvas1 = new Offcanvas(offcanvasEl1)
|
||||
|
||||
offcanvasEl1.addEventListener('shown.bs.offcanvas', () => {
|
||||
trigger2.click()
|
||||
})
|
||||
offcanvasEl1.addEventListener('hidden.bs.offcanvas', () => {
|
||||
expect(Offcanvas.getInstance(offcanvasEl2)).not.toBeNull()
|
||||
resolve()
|
||||
})
|
||||
offcanvas1.show()
|
||||
})
|
||||
})
|
||||
|
||||
it('should focus on trigger element after closing offcanvas', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<button id="btn" data-bs-toggle="offcanvas" data-bs-target="#offcanvas"></button>',
|
||||
'<div id="offcanvas" class="offcanvas"></div>'
|
||||
].join('')
|
||||
|
||||
const trigger = fixtureEl.querySelector('#btn')
|
||||
const offcanvasEl = fixtureEl.querySelector('#offcanvas')
|
||||
const offcanvas = new Offcanvas(offcanvasEl)
|
||||
const spy = spyOn(trigger, 'focus')
|
||||
|
||||
offcanvasEl.addEventListener('shown.bs.offcanvas', () => {
|
||||
offcanvas.hide()
|
||||
})
|
||||
offcanvasEl.addEventListener('hidden.bs.offcanvas', () => {
|
||||
setTimeout(() => {
|
||||
expect(spy).toHaveBeenCalled()
|
||||
resolve()
|
||||
}, 5)
|
||||
})
|
||||
|
||||
trigger.click()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not focus on trigger element after closing offcanvas, if it is not visible', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<button id="btn" data-bs-toggle="offcanvas" data-bs-target="#offcanvas"></button>',
|
||||
'<div id="offcanvas" class="offcanvas"></div>'
|
||||
].join('')
|
||||
|
||||
const trigger = fixtureEl.querySelector('#btn')
|
||||
const offcanvasEl = fixtureEl.querySelector('#offcanvas')
|
||||
const offcanvas = new Offcanvas(offcanvasEl)
|
||||
const spy = spyOn(trigger, 'focus')
|
||||
|
||||
offcanvasEl.addEventListener('shown.bs.offcanvas', () => {
|
||||
trigger.style.display = 'none'
|
||||
offcanvas.hide()
|
||||
})
|
||||
offcanvasEl.addEventListener('hidden.bs.offcanvas', () => {
|
||||
setTimeout(() => {
|
||||
expect(isVisible(trigger)).toBeFalse()
|
||||
expect(spy).not.toHaveBeenCalled()
|
||||
resolve()
|
||||
}, 5)
|
||||
})
|
||||
|
||||
trigger.click()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('jQueryInterface', () => {
|
||||
it('should create an offcanvas', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
jQueryMock.fn.offcanvas = Offcanvas.jQueryInterface
|
||||
jQueryMock.elements = [div]
|
||||
|
||||
jQueryMock.fn.offcanvas.call(jQueryMock)
|
||||
|
||||
expect(Offcanvas.getInstance(div)).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should not re create an offcanvas', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
const offCanvas = new Offcanvas(div)
|
||||
|
||||
jQueryMock.fn.offcanvas = Offcanvas.jQueryInterface
|
||||
jQueryMock.elements = [div]
|
||||
|
||||
jQueryMock.fn.offcanvas.call(jQueryMock)
|
||||
|
||||
expect(Offcanvas.getInstance(div)).toEqual(offCanvas)
|
||||
})
|
||||
|
||||
it('should throw error on undefined method', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
const action = 'undefinedMethod'
|
||||
|
||||
jQueryMock.fn.offcanvas = Offcanvas.jQueryInterface
|
||||
jQueryMock.elements = [div]
|
||||
|
||||
expect(() => {
|
||||
jQueryMock.fn.offcanvas.call(jQueryMock, action)
|
||||
}).toThrowError(TypeError, `No method named "${action}"`)
|
||||
})
|
||||
|
||||
it('should throw error on protected method', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
const action = '_getConfig'
|
||||
|
||||
jQueryMock.fn.offcanvas = Offcanvas.jQueryInterface
|
||||
jQueryMock.elements = [div]
|
||||
|
||||
expect(() => {
|
||||
jQueryMock.fn.offcanvas.call(jQueryMock, action)
|
||||
}).toThrowError(TypeError, `No method named "${action}"`)
|
||||
})
|
||||
|
||||
it('should throw error if method "constructor" is being called', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
const action = 'constructor'
|
||||
|
||||
jQueryMock.fn.offcanvas = Offcanvas.jQueryInterface
|
||||
jQueryMock.elements = [div]
|
||||
|
||||
expect(() => {
|
||||
jQueryMock.fn.offcanvas.call(jQueryMock, action)
|
||||
}).toThrowError(TypeError, `No method named "${action}"`)
|
||||
})
|
||||
|
||||
it('should call offcanvas method', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
const spy = spyOn(Offcanvas.prototype, 'show')
|
||||
|
||||
jQueryMock.fn.offcanvas = Offcanvas.jQueryInterface
|
||||
jQueryMock.elements = [div]
|
||||
|
||||
jQueryMock.fn.offcanvas.call(jQueryMock, 'show')
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should create a offcanvas with given config', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
jQueryMock.fn.offcanvas = Offcanvas.jQueryInterface
|
||||
jQueryMock.elements = [div]
|
||||
|
||||
jQueryMock.fn.offcanvas.call(jQueryMock, { scroll: true })
|
||||
|
||||
const offcanvas = Offcanvas.getInstance(div)
|
||||
expect(offcanvas).not.toBeNull()
|
||||
expect(offcanvas._config.scroll).toBeTrue()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getInstance', () => {
|
||||
it('should return offcanvas instance', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
const offCanvas = new Offcanvas(div)
|
||||
|
||||
expect(Offcanvas.getInstance(div)).toEqual(offCanvas)
|
||||
expect(Offcanvas.getInstance(div)).toBeInstanceOf(Offcanvas)
|
||||
})
|
||||
|
||||
it('should return null when there is no offcanvas instance', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Offcanvas.getInstance(div)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getOrCreateInstance', () => {
|
||||
it('should return offcanvas instance', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
const offcanvas = new Offcanvas(div)
|
||||
|
||||
expect(Offcanvas.getOrCreateInstance(div)).toEqual(offcanvas)
|
||||
expect(Offcanvas.getInstance(div)).toEqual(Offcanvas.getOrCreateInstance(div, {}))
|
||||
expect(Offcanvas.getOrCreateInstance(div)).toBeInstanceOf(Offcanvas)
|
||||
})
|
||||
|
||||
it('should return new instance when there is no Offcanvas instance', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Offcanvas.getInstance(div)).toBeNull()
|
||||
expect(Offcanvas.getOrCreateInstance(div)).toBeInstanceOf(Offcanvas)
|
||||
})
|
||||
|
||||
it('should return new instance when there is no offcanvas instance with given configuration', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Offcanvas.getInstance(div)).toBeNull()
|
||||
const offcanvas = Offcanvas.getOrCreateInstance(div, {
|
||||
scroll: true
|
||||
})
|
||||
expect(offcanvas).toBeInstanceOf(Offcanvas)
|
||||
|
||||
expect(offcanvas._config.scroll).toBeTrue()
|
||||
})
|
||||
|
||||
it('should return the instance when exists without given configuration', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
const offcanvas = new Offcanvas(div, {
|
||||
scroll: true
|
||||
})
|
||||
expect(Offcanvas.getInstance(div)).toEqual(offcanvas)
|
||||
|
||||
const offcanvas2 = Offcanvas.getOrCreateInstance(div, {
|
||||
scroll: false
|
||||
})
|
||||
expect(offcanvas).toBeInstanceOf(Offcanvas)
|
||||
expect(offcanvas2).toEqual(offcanvas)
|
||||
|
||||
expect(offcanvas2._config.scroll).toBeTrue()
|
||||
})
|
||||
})
|
||||
})
|
413
js/tests/unit/popover.spec.js
Normal file
413
js/tests/unit/popover.spec.js
Normal file
|
@ -0,0 +1,413 @@
|
|||
import Popover from '../../src/popover'
|
||||
import EventHandler from '../../src/dom/event-handler'
|
||||
import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture'
|
||||
|
||||
describe('Popover', () => {
|
||||
let fixtureEl
|
||||
|
||||
beforeAll(() => {
|
||||
fixtureEl = getFixture()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
clearFixture()
|
||||
|
||||
const popoverList = document.querySelectorAll('.popover')
|
||||
|
||||
for (const popoverEl of popoverList) {
|
||||
popoverEl.remove()
|
||||
}
|
||||
})
|
||||
|
||||
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_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', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" title="Popover" data-bs-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')).not.toBeNull()
|
||||
resolve()
|
||||
})
|
||||
|
||||
popover.show()
|
||||
})
|
||||
})
|
||||
|
||||
it('should set title and content from functions', () => {
|
||||
return new Promise(resolve => {
|
||||
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).not.toBeNull()
|
||||
expect(popoverDisplayed.querySelector('.popover-header').textContent).toEqual('Bootstrap')
|
||||
expect(popoverDisplayed.querySelector('.popover-body').textContent).toEqual('loves writing tests (╯°□°)╯︵ ┻━┻')
|
||||
resolve()
|
||||
})
|
||||
|
||||
popover.show()
|
||||
})
|
||||
})
|
||||
|
||||
it('should show a popover with just content without having header', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#">Nice link</a>'
|
||||
|
||||
const popoverEl = fixtureEl.querySelector('a')
|
||||
const popover = new Popover(popoverEl, {
|
||||
content: 'Some beautiful content :)'
|
||||
})
|
||||
|
||||
popoverEl.addEventListener('shown.bs.popover', () => {
|
||||
const popoverDisplayed = document.querySelector('.popover')
|
||||
|
||||
expect(popoverDisplayed).not.toBeNull()
|
||||
expect(popoverDisplayed.querySelector('.popover-header')).toBeNull()
|
||||
expect(popoverDisplayed.querySelector('.popover-body').textContent).toEqual('Some beautiful content :)')
|
||||
resolve()
|
||||
})
|
||||
|
||||
popover.show()
|
||||
})
|
||||
})
|
||||
|
||||
it('should show a popover with just title without having body', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#">Nice link</a>'
|
||||
|
||||
const popoverEl = fixtureEl.querySelector('a')
|
||||
const popover = new Popover(popoverEl, {
|
||||
title: 'Title which does not require content'
|
||||
})
|
||||
|
||||
popoverEl.addEventListener('shown.bs.popover', () => {
|
||||
const popoverDisplayed = document.querySelector('.popover')
|
||||
|
||||
expect(popoverDisplayed).not.toBeNull()
|
||||
expect(popoverDisplayed.querySelector('.popover-body')).toBeNull()
|
||||
expect(popoverDisplayed.querySelector('.popover-header').textContent).toEqual('Title which does not require content')
|
||||
resolve()
|
||||
})
|
||||
|
||||
popover.show()
|
||||
})
|
||||
})
|
||||
|
||||
it('should show a popover with just title without having body using data-attribute to get config', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" data-bs-content="" title="Title which does not require content">Nice link</a>'
|
||||
|
||||
const popoverEl = fixtureEl.querySelector('a')
|
||||
const popover = new Popover(popoverEl)
|
||||
|
||||
popoverEl.addEventListener('shown.bs.popover', () => {
|
||||
const popoverDisplayed = document.querySelector('.popover')
|
||||
|
||||
expect(popoverDisplayed).not.toBeNull()
|
||||
expect(popoverDisplayed.querySelector('.popover-body')).toBeNull()
|
||||
expect(popoverDisplayed.querySelector('.popover-header').textContent).toEqual('Title which does not require content')
|
||||
resolve()
|
||||
})
|
||||
|
||||
popover.show()
|
||||
})
|
||||
})
|
||||
|
||||
it('should NOT show a popover without `title` and `content`', () => {
|
||||
fixtureEl.innerHTML = '<a href="#" data-bs-content="" title="">Nice link</a>'
|
||||
|
||||
const popoverEl = fixtureEl.querySelector('a')
|
||||
const popover = new Popover(popoverEl, { animation: false })
|
||||
const spy = spyOn(EventHandler, 'trigger').and.callThrough()
|
||||
|
||||
popover.show()
|
||||
|
||||
expect(spy).not.toHaveBeenCalledWith(popoverEl, Popover.eventName('show'))
|
||||
expect(document.querySelector('.popover')).toBeNull()
|
||||
})
|
||||
|
||||
it('"setContent" should keep the initial template', () => {
|
||||
fixtureEl.innerHTML = '<a href="#" title="Popover" data-bs-content="https://twitter.com/getbootstrap" data-bs-custom-class="custom-class">BS twitter</a>'
|
||||
|
||||
const popoverEl = fixtureEl.querySelector('a')
|
||||
const popover = new Popover(popoverEl)
|
||||
|
||||
popover.setContent({ '.tooltip-inner': 'foo' })
|
||||
const tip = popover._getTipElement()
|
||||
|
||||
expect(tip).toHaveClass('popover')
|
||||
expect(tip).toHaveClass('bs-popover-auto')
|
||||
expect(tip.querySelector('.popover-arrow')).not.toBeNull()
|
||||
expect(tip.querySelector('.popover-header')).not.toBeNull()
|
||||
expect(tip.querySelector('.popover-body')).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should call setContent once', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#">BS twitter</a>'
|
||||
|
||||
const popoverEl = fixtureEl.querySelector('a')
|
||||
const popover = new Popover(popoverEl, {
|
||||
content: 'Popover content'
|
||||
})
|
||||
expect(popover._templateFactory).toBeNull()
|
||||
let spy = null
|
||||
let times = 1
|
||||
|
||||
popoverEl.addEventListener('hidden.bs.popover', () => {
|
||||
popover.show()
|
||||
})
|
||||
|
||||
popoverEl.addEventListener('shown.bs.popover', () => {
|
||||
spy = spy || spyOn(popover._templateFactory, 'constructor').and.callThrough()
|
||||
const popoverDisplayed = document.querySelector('.popover')
|
||||
|
||||
expect(popoverDisplayed).not.toBeNull()
|
||||
expect(popoverDisplayed.querySelector('.popover-body').textContent).toEqual('Popover content')
|
||||
expect(spy).toHaveBeenCalledTimes(0)
|
||||
if (times > 1) {
|
||||
resolve()
|
||||
}
|
||||
|
||||
times++
|
||||
popover.hide()
|
||||
})
|
||||
popover.show()
|
||||
})
|
||||
})
|
||||
|
||||
it('should show a popover with provided custom class', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" title="Popover" data-bs-content="https://twitter.com/getbootstrap" data-bs-custom-class="custom-class">BS twitter</a>'
|
||||
|
||||
const popoverEl = fixtureEl.querySelector('a')
|
||||
const popover = new Popover(popoverEl)
|
||||
|
||||
popoverEl.addEventListener('shown.bs.popover', () => {
|
||||
const tip = document.querySelector('.popover')
|
||||
expect(tip).not.toBeNull()
|
||||
expect(tip).toHaveClass('custom-class')
|
||||
resolve()
|
||||
})
|
||||
|
||||
popover.show()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('hide', () => {
|
||||
it('should hide a popover', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<a href="#" title="Popover" data-bs-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()
|
||||
resolve()
|
||||
})
|
||||
|
||||
popover.show()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('jQueryInterface', () => {
|
||||
it('should create a popover', () => {
|
||||
fixtureEl.innerHTML = '<a href="#" title="Popover" data-bs-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)).not.toBeNull()
|
||||
})
|
||||
|
||||
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)).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should not re create a popover', () => {
|
||||
fixtureEl.innerHTML = '<a href="#" title="Popover" data-bs-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-bs-content="https://twitter.com/getbootstrap">BS twitter</a>'
|
||||
|
||||
const popoverEl = fixtureEl.querySelector('a')
|
||||
const action = 'undefinedMethod'
|
||||
|
||||
jQueryMock.fn.popover = Popover.jQueryInterface
|
||||
jQueryMock.elements = [popoverEl]
|
||||
|
||||
expect(() => {
|
||||
jQueryMock.fn.popover.call(jQueryMock, action)
|
||||
}).toThrowError(TypeError, `No method named "${action}"`)
|
||||
})
|
||||
|
||||
it('should should call show method', () => {
|
||||
fixtureEl.innerHTML = '<a href="#" title="Popover" data-bs-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]
|
||||
|
||||
const spy = spyOn(popover, 'show')
|
||||
|
||||
jQueryMock.fn.popover.call(jQueryMock, 'show')
|
||||
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getInstance', () => {
|
||||
it('should return popover instance', () => {
|
||||
fixtureEl.innerHTML = '<a href="#" title="Popover" data-bs-content="https://twitter.com/getbootstrap">BS twitter</a>'
|
||||
|
||||
const popoverEl = fixtureEl.querySelector('a')
|
||||
const popover = new Popover(popoverEl)
|
||||
|
||||
expect(Popover.getInstance(popoverEl)).toEqual(popover)
|
||||
expect(Popover.getInstance(popoverEl)).toBeInstanceOf(Popover)
|
||||
})
|
||||
|
||||
it('should return null when there is no popover instance', () => {
|
||||
fixtureEl.innerHTML = '<a href="#" title="Popover" data-bs-content="https://twitter.com/getbootstrap">BS twitter</a>'
|
||||
|
||||
const popoverEl = fixtureEl.querySelector('a')
|
||||
|
||||
expect(Popover.getInstance(popoverEl)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getOrCreateInstance', () => {
|
||||
it('should return popover instance', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
const popover = new Popover(div)
|
||||
|
||||
expect(Popover.getOrCreateInstance(div)).toEqual(popover)
|
||||
expect(Popover.getInstance(div)).toEqual(Popover.getOrCreateInstance(div, {}))
|
||||
expect(Popover.getOrCreateInstance(div)).toBeInstanceOf(Popover)
|
||||
})
|
||||
|
||||
it('should return new instance when there is no popover instance', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Popover.getInstance(div)).toBeNull()
|
||||
expect(Popover.getOrCreateInstance(div)).toBeInstanceOf(Popover)
|
||||
})
|
||||
|
||||
it('should return new instance when there is no popover instance with given configuration', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Popover.getInstance(div)).toBeNull()
|
||||
const popover = Popover.getOrCreateInstance(div, {
|
||||
placement: 'top'
|
||||
})
|
||||
expect(popover).toBeInstanceOf(Popover)
|
||||
|
||||
expect(popover._config.placement).toEqual('top')
|
||||
})
|
||||
|
||||
it('should return the instance when exists without given configuration', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
const popover = new Popover(div, {
|
||||
placement: 'top'
|
||||
})
|
||||
expect(Popover.getInstance(div)).toEqual(popover)
|
||||
|
||||
const popover2 = Popover.getOrCreateInstance(div, {
|
||||
placement: 'bottom'
|
||||
})
|
||||
expect(popover).toBeInstanceOf(Popover)
|
||||
expect(popover2).toEqual(popover)
|
||||
|
||||
expect(popover2._config.placement).toEqual('top')
|
||||
})
|
||||
})
|
||||
})
|
946
js/tests/unit/scrollspy.spec.js
Normal file
946
js/tests/unit/scrollspy.spec.js
Normal file
|
@ -0,0 +1,946 @@
|
|||
import ScrollSpy from '../../src/scrollspy'
|
||||
|
||||
/** Test helpers */
|
||||
import { clearFixture, createEvent, getFixture, jQueryMock } from '../helpers/fixture'
|
||||
import EventHandler from '../../src/dom/event-handler'
|
||||
|
||||
describe('ScrollSpy', () => {
|
||||
let fixtureEl
|
||||
|
||||
const getElementScrollSpy = element => element.scrollTo ?
|
||||
spyOn(element, 'scrollTo').and.callThrough() :
|
||||
spyOnProperty(element, 'scrollTop', 'set').and.callThrough()
|
||||
|
||||
const scrollTo = (el, height) => {
|
||||
el.scrollTop = height
|
||||
}
|
||||
|
||||
const onScrollStop = (callback, element, timeout = 30) => {
|
||||
let handle = null
|
||||
const onScroll = function () {
|
||||
if (handle) {
|
||||
window.clearTimeout(handle)
|
||||
}
|
||||
|
||||
handle = setTimeout(() => {
|
||||
element.removeEventListener('scroll', onScroll)
|
||||
callback()
|
||||
}, timeout + 1)
|
||||
}
|
||||
|
||||
element.addEventListener('scroll', onScroll)
|
||||
}
|
||||
|
||||
const getDummyFixture = () => {
|
||||
return [
|
||||
'<nav id="navBar" 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>',
|
||||
' </ul>',
|
||||
'</nav>',
|
||||
'<div class="content" data-bs-target="#navBar" style="overflow-y: auto">',
|
||||
' <div id="div-jsm-1">div 1</div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
}
|
||||
|
||||
const testElementIsActiveAfterScroll = ({ elementSelector, targetSelector, contentEl, scrollSpy, cb }) => {
|
||||
const element = fixtureEl.querySelector(elementSelector)
|
||||
const target = fixtureEl.querySelector(targetSelector)
|
||||
// add top padding to fix Chrome on Android failures
|
||||
const paddingTop = 0
|
||||
const parentOffset = getComputedStyle(contentEl).getPropertyValue('position') === 'relative' ? 0 : contentEl.offsetTop
|
||||
const scrollHeight = (target.offsetTop - parentOffset) + paddingTop
|
||||
|
||||
contentEl.addEventListener('activate.bs.scrollspy', event => {
|
||||
if (scrollSpy._activeTarget !== element) {
|
||||
return
|
||||
}
|
||||
|
||||
expect(element).toHaveClass('active')
|
||||
expect(scrollSpy._activeTarget).toEqual(element)
|
||||
expect(event.relatedTarget).toEqual(element)
|
||||
cb()
|
||||
})
|
||||
|
||||
setTimeout(() => { // in case we scroll something before the test
|
||||
scrollTo(contentEl, scrollHeight)
|
||||
}, 100)
|
||||
}
|
||||
|
||||
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('DATA_KEY', () => {
|
||||
it('should return plugin data key', () => {
|
||||
expect(ScrollSpy.DATA_KEY).toEqual('bs.scrollspy')
|
||||
})
|
||||
})
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should take care of element either passed as a CSS selector or DOM element', () => {
|
||||
fixtureEl.innerHTML = getDummyFixture()
|
||||
|
||||
const sSpyEl = fixtureEl.querySelector('.content')
|
||||
const sSpyBySelector = new ScrollSpy('.content')
|
||||
const sSpyByElement = new ScrollSpy(sSpyEl)
|
||||
|
||||
expect(sSpyBySelector._element).toEqual(sSpyEl)
|
||||
expect(sSpyByElement._element).toEqual(sSpyEl)
|
||||
})
|
||||
|
||||
it('should null, if element is not scrollable', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<nav id="navigation" class="navbar">',
|
||||
' <ul class="navbar-nav">' +
|
||||
' <li class="nav-item"><a class="nav-link active" id="one-link" href="#">One</a></li>' +
|
||||
' </ul>',
|
||||
'</nav>',
|
||||
'<div id="content">',
|
||||
' <div id="1" style="height: 300px;">test</div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const scrollSpy = new ScrollSpy(fixtureEl.querySelector('#content'), {
|
||||
target: '#navigation'
|
||||
})
|
||||
|
||||
expect(scrollSpy._observer.root).toBeNull()
|
||||
expect(scrollSpy._rootElement).toBeNull()
|
||||
})
|
||||
|
||||
it('should respect threshold option', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<ul id="navigation" class="navbar">',
|
||||
' <a class="nav-link active" id="one-link" href="#">One</a>' +
|
||||
'</ul>',
|
||||
'<div id="content">',
|
||||
' <div id="one-link">test</div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const scrollSpy = new ScrollSpy('#content', {
|
||||
target: '#navigation',
|
||||
threshold: [1]
|
||||
})
|
||||
|
||||
expect(scrollSpy._observer.thresholds).toEqual([1])
|
||||
})
|
||||
|
||||
it('should respect threshold option markup', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<ul id="navigation" class="navbar">',
|
||||
' <a class="nav-link active" id="one-link" href="#">One</a>' +
|
||||
'</ul>',
|
||||
'<div id="content" data-bs-threshold="0,0.2,1">',
|
||||
' <div id="one-link">test</div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const scrollSpy = new ScrollSpy('#content', {
|
||||
target: '#navigation'
|
||||
})
|
||||
|
||||
// See https://stackoverflow.com/a/45592926
|
||||
const expectToBeCloseToArray = (actual, expected) => {
|
||||
expect(actual.length).toBe(expected.length)
|
||||
for (const x of actual) {
|
||||
const i = actual.indexOf(x)
|
||||
expect(x).withContext(`[${i}]`).toBeCloseTo(expected[i])
|
||||
}
|
||||
}
|
||||
|
||||
expectToBeCloseToArray(scrollSpy._observer.thresholds, [0, 0.2, 1])
|
||||
})
|
||||
|
||||
it('should not take count to not visible sections', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<nav id="navigation" class="navbar">',
|
||||
' <ul class="navbar-nav">',
|
||||
' <li class="nav-item"><a class="nav-link active" 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: 300px;">test</div>',
|
||||
' <div id="two" hidden style="height: 300px;">test</div>',
|
||||
' <div id="three" style="display: none;">test</div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const scrollSpy = new ScrollSpy(fixtureEl.querySelector('#content'), {
|
||||
target: '#navigation'
|
||||
})
|
||||
|
||||
expect(scrollSpy._observableSections.size).toBe(1)
|
||||
expect(scrollSpy._targetLinks.size).toBe(1)
|
||||
})
|
||||
|
||||
it('should not process element without target', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<nav id="navigation" class="navbar">',
|
||||
' <ul class="navbar-nav">',
|
||||
' <li class="nav-item"><a class="nav-link active" 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;">test</div>',
|
||||
' <div id="three" style="height: 10px;">test2</div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const scrollSpy = new ScrollSpy(fixtureEl.querySelector('#content'), {
|
||||
target: '#navigation'
|
||||
})
|
||||
|
||||
expect(scrollSpy._targetLinks).toHaveSize(2)
|
||||
})
|
||||
|
||||
it('should only switch "active" class on current target', () => {
|
||||
return new Promise(resolve => {
|
||||
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;" id="masthead">Overview</div>',
|
||||
' <div style="height: 200px;" id="detail">Detail</div>',
|
||||
' </div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const scrollSpyEl = fixtureEl.querySelector('#scrollspy-example')
|
||||
const rootEl = fixtureEl.querySelector('#root')
|
||||
const scrollSpy = new ScrollSpy(scrollSpyEl, {
|
||||
target: 'ss-target'
|
||||
})
|
||||
|
||||
const spy = spyOn(scrollSpy, '_process').and.callThrough()
|
||||
|
||||
onScrollStop(() => {
|
||||
expect(rootEl).toHaveClass('active')
|
||||
expect(spy).toHaveBeenCalled()
|
||||
resolve()
|
||||
}, scrollSpyEl)
|
||||
|
||||
scrollTo(scrollSpyEl, 350)
|
||||
})
|
||||
})
|
||||
|
||||
it('should not process data if `activeTarget` is same as given target', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
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 triggerSpy = spyOn(EventHandler, 'trigger').and.callThrough()
|
||||
|
||||
scrollSpy._activeTarget = fixtureEl.querySelector('#a-1')
|
||||
testElementIsActiveAfterScroll({
|
||||
elementSelector: '#a-1',
|
||||
targetSelector: '#div-1',
|
||||
contentEl,
|
||||
scrollSpy,
|
||||
cb: reject
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
expect(triggerSpy).not.toHaveBeenCalled()
|
||||
resolve()
|
||||
}, 100)
|
||||
})
|
||||
})
|
||||
|
||||
it('should only switch "active" class on current target specified w element', () => {
|
||||
return new Promise(resolve => {
|
||||
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;" id="masthead">Overview</div>',
|
||||
' <div style="height: 200px;" id="detail">Detail</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')
|
||||
})
|
||||
|
||||
const spy = spyOn(scrollSpy, '_process').and.callThrough()
|
||||
|
||||
onScrollStop(() => {
|
||||
expect(rootEl).toHaveClass('active')
|
||||
expect(scrollSpy._activeTarget).toEqual(fixtureEl.querySelector('[href="#detail"]'))
|
||||
expect(spy).toHaveBeenCalled()
|
||||
resolve()
|
||||
}, scrollSpyEl)
|
||||
|
||||
scrollTo(scrollSpyEl, 350)
|
||||
})
|
||||
})
|
||||
|
||||
it('should add the active class to the correct element', () => {
|
||||
return new Promise(resolve => {
|
||||
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'
|
||||
})
|
||||
|
||||
testElementIsActiveAfterScroll({
|
||||
elementSelector: '#a-1',
|
||||
targetSelector: '#div-1',
|
||||
contentEl,
|
||||
scrollSpy,
|
||||
cb() {
|
||||
testElementIsActiveAfterScroll({
|
||||
elementSelector: '#a-2',
|
||||
targetSelector: '#div-2',
|
||||
contentEl,
|
||||
scrollSpy,
|
||||
cb: resolve
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should add to nav the active class to the correct element (nav markup)', () => {
|
||||
return new Promise(resolve => {
|
||||
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'
|
||||
})
|
||||
|
||||
testElementIsActiveAfterScroll({
|
||||
elementSelector: '#a-1',
|
||||
targetSelector: '#div-1',
|
||||
contentEl,
|
||||
scrollSpy,
|
||||
cb() {
|
||||
testElementIsActiveAfterScroll({
|
||||
elementSelector: '#a-2',
|
||||
targetSelector: '#div-2',
|
||||
contentEl,
|
||||
scrollSpy,
|
||||
cb: resolve
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should add to list-group, the active class to the correct element (list-group markup)', () => {
|
||||
return new Promise(resolve => {
|
||||
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'
|
||||
})
|
||||
|
||||
testElementIsActiveAfterScroll({
|
||||
elementSelector: '#a-1',
|
||||
targetSelector: '#div-1',
|
||||
contentEl,
|
||||
scrollSpy,
|
||||
cb() {
|
||||
testElementIsActiveAfterScroll({
|
||||
elementSelector: '#a-2',
|
||||
targetSelector: '#div-2',
|
||||
contentEl,
|
||||
scrollSpy,
|
||||
cb: resolve
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should clear selection if above the first section', () => {
|
||||
return new Promise(resolve => {
|
||||
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: 200px;"></div>',
|
||||
' <div id="one" style="height: 100px;">text</div>',
|
||||
' <div id="two" style="height: 100px;">text</div>',
|
||||
' <div id="three" style="height: 100px;">text</div>',
|
||||
' <div id="spacer" style="height: 100px;"></div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const contentEl = fixtureEl.querySelector('#content')
|
||||
const scrollSpy = new ScrollSpy(contentEl, {
|
||||
target: '#navigation',
|
||||
offset: contentEl.offsetTop
|
||||
})
|
||||
const spy = spyOn(scrollSpy, '_process').and.callThrough()
|
||||
|
||||
onScrollStop(() => {
|
||||
const active = () => fixtureEl.querySelector('.active')
|
||||
expect(spy).toHaveBeenCalled()
|
||||
|
||||
expect(fixtureEl.querySelectorAll('.active')).toHaveSize(1)
|
||||
expect(active().getAttribute('id')).toEqual('two-link')
|
||||
onScrollStop(() => {
|
||||
expect(active()).toBeNull()
|
||||
resolve()
|
||||
}, contentEl)
|
||||
scrollTo(contentEl, 0)
|
||||
}, contentEl)
|
||||
|
||||
scrollTo(contentEl, 200)
|
||||
})
|
||||
})
|
||||
|
||||
it('should not clear selection if above the first section and first section is at the top', () => {
|
||||
return new Promise(resolve => {
|
||||
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: 150px; overflow-y: auto;">',
|
||||
' <div id="one" style="height: 100px;">test</div>',
|
||||
' <div id="two" style="height: 100px;">test</div>',
|
||||
' <div id="three" style="height: 100px;">test</div>',
|
||||
' <div id="spacer" style="height: 100px;">test</div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const negativeHeight = 0
|
||||
const startOfSectionTwo = 101
|
||||
const contentEl = fixtureEl.querySelector('#content')
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const scrollSpy = new ScrollSpy(contentEl, {
|
||||
target: '#navigation',
|
||||
rootMargin: '0px 0px -50%'
|
||||
})
|
||||
|
||||
onScrollStop(() => {
|
||||
const activeId = () => fixtureEl.querySelector('.active').getAttribute('id')
|
||||
|
||||
expect(fixtureEl.querySelectorAll('.active')).toHaveSize(1)
|
||||
expect(activeId()).toEqual('two-link')
|
||||
scrollTo(contentEl, negativeHeight)
|
||||
|
||||
onScrollStop(() => {
|
||||
expect(fixtureEl.querySelectorAll('.active')).toHaveSize(1)
|
||||
expect(activeId()).toEqual('one-link')
|
||||
resolve()
|
||||
}, contentEl)
|
||||
|
||||
scrollTo(contentEl, 0)
|
||||
}, contentEl)
|
||||
|
||||
scrollTo(contentEl, startOfSectionTwo)
|
||||
})
|
||||
})
|
||||
|
||||
it('should correctly select navigation element on backward scrolling when each target section height is 100%', () => {
|
||||
return new Promise(resolve => {
|
||||
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'
|
||||
})
|
||||
|
||||
scrollTo(contentEl, 0)
|
||||
testElementIsActiveAfterScroll({
|
||||
elementSelector: '#li-100-5',
|
||||
targetSelector: '#div-100-5',
|
||||
contentEl,
|
||||
scrollSpy,
|
||||
cb() {
|
||||
scrollTo(contentEl, 0)
|
||||
testElementIsActiveAfterScroll({
|
||||
elementSelector: '#li-100-2',
|
||||
targetSelector: '#div-100-2',
|
||||
contentEl,
|
||||
scrollSpy,
|
||||
cb() {
|
||||
scrollTo(contentEl, 0)
|
||||
testElementIsActiveAfterScroll({
|
||||
elementSelector: '#li-100-3',
|
||||
targetSelector: '#div-100-3',
|
||||
contentEl,
|
||||
scrollSpy,
|
||||
cb() {
|
||||
scrollTo(contentEl, 0)
|
||||
testElementIsActiveAfterScroll({
|
||||
elementSelector: '#li-100-2',
|
||||
targetSelector: '#div-100-2',
|
||||
contentEl,
|
||||
scrollSpy,
|
||||
cb() {
|
||||
scrollTo(contentEl, 0)
|
||||
testElementIsActiveAfterScroll({
|
||||
elementSelector: '#li-100-1',
|
||||
targetSelector: '#div-100-1',
|
||||
contentEl,
|
||||
scrollSpy,
|
||||
cb: resolve
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('refresh', () => {
|
||||
it('should disconnect existing observer', () => {
|
||||
fixtureEl.innerHTML = getDummyFixture()
|
||||
|
||||
const el = fixtureEl.querySelector('.content')
|
||||
const scrollSpy = new ScrollSpy(el)
|
||||
|
||||
const spy = spyOn(scrollSpy._observer, 'disconnect')
|
||||
|
||||
scrollSpy.refresh()
|
||||
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('dispose', () => {
|
||||
it('should dispose a scrollspy', () => {
|
||||
fixtureEl.innerHTML = getDummyFixture()
|
||||
|
||||
const el = fixtureEl.querySelector('.content')
|
||||
const scrollSpy = new ScrollSpy(el)
|
||||
|
||||
expect(ScrollSpy.getInstance(el)).not.toBeNull()
|
||||
|
||||
scrollSpy.dispose()
|
||||
|
||||
expect(ScrollSpy.getInstance(el)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('jQueryInterface', () => {
|
||||
it('should create a scrollspy', () => {
|
||||
fixtureEl.innerHTML = getDummyFixture()
|
||||
|
||||
const div = fixtureEl.querySelector('.content')
|
||||
|
||||
jQueryMock.fn.scrollspy = ScrollSpy.jQueryInterface
|
||||
jQueryMock.elements = [div]
|
||||
|
||||
jQueryMock.fn.scrollspy.call(jQueryMock, { target: '#navBar' })
|
||||
|
||||
expect(ScrollSpy.getInstance(div)).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should create a scrollspy with given config', () => {
|
||||
fixtureEl.innerHTML = getDummyFixture()
|
||||
|
||||
const div = fixtureEl.querySelector('.content')
|
||||
|
||||
jQueryMock.fn.scrollspy = ScrollSpy.jQueryInterface
|
||||
jQueryMock.elements = [div]
|
||||
|
||||
jQueryMock.fn.scrollspy.call(jQueryMock, { rootMargin: '100px' })
|
||||
const spy = spyOn(ScrollSpy.prototype, 'constructor')
|
||||
expect(spy).not.toHaveBeenCalledWith(div, { rootMargin: '100px' })
|
||||
|
||||
const scrollspy = ScrollSpy.getInstance(div)
|
||||
expect(scrollspy).not.toBeNull()
|
||||
expect(scrollspy._config.rootMargin).toEqual('100px')
|
||||
})
|
||||
|
||||
it('should not re create a scrollspy', () => {
|
||||
fixtureEl.innerHTML = getDummyFixture()
|
||||
|
||||
const div = fixtureEl.querySelector('.content')
|
||||
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 = getDummyFixture()
|
||||
|
||||
const div = fixtureEl.querySelector('.content')
|
||||
const scrollSpy = new ScrollSpy(div)
|
||||
|
||||
const spy = spyOn(scrollSpy, 'refresh')
|
||||
|
||||
jQueryMock.fn.scrollspy = ScrollSpy.jQueryInterface
|
||||
jQueryMock.elements = [div]
|
||||
|
||||
jQueryMock.fn.scrollspy.call(jQueryMock, 'refresh')
|
||||
|
||||
expect(ScrollSpy.getInstance(div)).toEqual(scrollSpy)
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should throw error on undefined method', () => {
|
||||
fixtureEl.innerHTML = getDummyFixture()
|
||||
|
||||
const div = fixtureEl.querySelector('.content')
|
||||
const action = 'undefinedMethod'
|
||||
|
||||
jQueryMock.fn.scrollspy = ScrollSpy.jQueryInterface
|
||||
jQueryMock.elements = [div]
|
||||
|
||||
expect(() => {
|
||||
jQueryMock.fn.scrollspy.call(jQueryMock, action)
|
||||
}).toThrowError(TypeError, `No method named "${action}"`)
|
||||
})
|
||||
|
||||
it('should throw error on protected method', () => {
|
||||
fixtureEl.innerHTML = getDummyFixture()
|
||||
|
||||
const div = fixtureEl.querySelector('.content')
|
||||
const action = '_getConfig'
|
||||
|
||||
jQueryMock.fn.scrollspy = ScrollSpy.jQueryInterface
|
||||
jQueryMock.elements = [div]
|
||||
|
||||
expect(() => {
|
||||
jQueryMock.fn.scrollspy.call(jQueryMock, action)
|
||||
}).toThrowError(TypeError, `No method named "${action}"`)
|
||||
})
|
||||
|
||||
it('should throw error if method "constructor" is being called', () => {
|
||||
fixtureEl.innerHTML = getDummyFixture()
|
||||
|
||||
const div = fixtureEl.querySelector('.content')
|
||||
const action = 'constructor'
|
||||
|
||||
jQueryMock.fn.scrollspy = ScrollSpy.jQueryInterface
|
||||
jQueryMock.elements = [div]
|
||||
|
||||
expect(() => {
|
||||
jQueryMock.fn.scrollspy.call(jQueryMock, action)
|
||||
}).toThrowError(TypeError, `No method named "${action}"`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getInstance', () => {
|
||||
it('should return scrollspy instance', () => {
|
||||
fixtureEl.innerHTML = getDummyFixture()
|
||||
|
||||
const div = fixtureEl.querySelector('.content')
|
||||
const scrollSpy = new ScrollSpy(div, { target: fixtureEl.querySelector('#navBar') })
|
||||
|
||||
expect(ScrollSpy.getInstance(div)).toEqual(scrollSpy)
|
||||
expect(ScrollSpy.getInstance(div)).toBeInstanceOf(ScrollSpy)
|
||||
})
|
||||
|
||||
it('should return null if there is no instance', () => {
|
||||
fixtureEl.innerHTML = getDummyFixture()
|
||||
|
||||
const div = fixtureEl.querySelector('.content')
|
||||
expect(ScrollSpy.getInstance(div)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getOrCreateInstance', () => {
|
||||
it('should return scrollspy instance', () => {
|
||||
fixtureEl.innerHTML = getDummyFixture()
|
||||
|
||||
const div = fixtureEl.querySelector('.content')
|
||||
const scrollspy = new ScrollSpy(div)
|
||||
|
||||
expect(ScrollSpy.getOrCreateInstance(div)).toEqual(scrollspy)
|
||||
expect(ScrollSpy.getInstance(div)).toEqual(ScrollSpy.getOrCreateInstance(div, {}))
|
||||
expect(ScrollSpy.getOrCreateInstance(div)).toBeInstanceOf(ScrollSpy)
|
||||
})
|
||||
|
||||
it('should return new instance when there is no scrollspy instance', () => {
|
||||
fixtureEl.innerHTML = getDummyFixture()
|
||||
|
||||
const div = fixtureEl.querySelector('.content')
|
||||
|
||||
expect(ScrollSpy.getInstance(div)).toBeNull()
|
||||
expect(ScrollSpy.getOrCreateInstance(div)).toBeInstanceOf(ScrollSpy)
|
||||
})
|
||||
|
||||
it('should return new instance when there is no scrollspy instance with given configuration', () => {
|
||||
fixtureEl.innerHTML = getDummyFixture()
|
||||
|
||||
const div = fixtureEl.querySelector('.content')
|
||||
|
||||
expect(ScrollSpy.getInstance(div)).toBeNull()
|
||||
const scrollspy = ScrollSpy.getOrCreateInstance(div, {
|
||||
offset: 1
|
||||
})
|
||||
expect(scrollspy).toBeInstanceOf(ScrollSpy)
|
||||
|
||||
expect(scrollspy._config.offset).toEqual(1)
|
||||
})
|
||||
|
||||
it('should return the instance when exists without given configuration', () => {
|
||||
fixtureEl.innerHTML = getDummyFixture()
|
||||
|
||||
const div = fixtureEl.querySelector('.content')
|
||||
const scrollspy = new ScrollSpy(div, {
|
||||
offset: 1
|
||||
})
|
||||
expect(ScrollSpy.getInstance(div)).toEqual(scrollspy)
|
||||
|
||||
const scrollspy2 = ScrollSpy.getOrCreateInstance(div, {
|
||||
offset: 2
|
||||
})
|
||||
expect(scrollspy).toBeInstanceOf(ScrollSpy)
|
||||
expect(scrollspy2).toEqual(scrollspy)
|
||||
|
||||
expect(scrollspy2._config.offset).toEqual(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('event handler', () => {
|
||||
it('should create scrollspy on window load event', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div id="nav"></div>' +
|
||||
'<div id="wrapper" data-bs-spy="scroll" data-bs-target="#nav" style="overflow-y: auto"></div>'
|
||||
].join('')
|
||||
|
||||
const scrollSpyEl = fixtureEl.querySelector('#wrapper')
|
||||
|
||||
window.dispatchEvent(createEvent('load'))
|
||||
|
||||
expect(ScrollSpy.getInstance(scrollSpyEl)).not.toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('SmoothScroll', () => {
|
||||
it('should not enable smoothScroll', () => {
|
||||
fixtureEl.innerHTML = getDummyFixture()
|
||||
const offSpy = spyOn(EventHandler, 'off').and.callThrough()
|
||||
const onSpy = spyOn(EventHandler, 'on').and.callThrough()
|
||||
|
||||
const div = fixtureEl.querySelector('.content')
|
||||
const target = fixtureEl.querySelector('#navBar')
|
||||
// eslint-disable-next-line no-new
|
||||
new ScrollSpy(div, {
|
||||
offset: 1
|
||||
})
|
||||
|
||||
expect(offSpy).not.toHaveBeenCalledWith(target, 'click.bs.scrollspy')
|
||||
expect(onSpy).not.toHaveBeenCalledWith(target, 'click.bs.scrollspy')
|
||||
})
|
||||
|
||||
it('should enable smoothScroll', () => {
|
||||
fixtureEl.innerHTML = getDummyFixture()
|
||||
const offSpy = spyOn(EventHandler, 'off').and.callThrough()
|
||||
const onSpy = spyOn(EventHandler, 'on').and.callThrough()
|
||||
|
||||
const div = fixtureEl.querySelector('.content')
|
||||
const target = fixtureEl.querySelector('#navBar')
|
||||
// eslint-disable-next-line no-new
|
||||
new ScrollSpy(div, {
|
||||
offset: 1,
|
||||
smoothScroll: true
|
||||
})
|
||||
|
||||
expect(offSpy).toHaveBeenCalledWith(target, 'click.bs.scrollspy')
|
||||
expect(onSpy).toHaveBeenCalledWith(target, 'click.bs.scrollspy', '[href]', jasmine.any(Function))
|
||||
})
|
||||
|
||||
it('should not smoothScroll to element if it not handles a scrollspy section', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<nav id="navBar" class="navbar">',
|
||||
' <ul class="nav">',
|
||||
' <a id="anchor-1" href="#div-jsm-1">div 1</a></li>',
|
||||
' <a id="anchor-2" href="#foo">div 2</a></li>',
|
||||
' </ul>',
|
||||
'</nav>',
|
||||
'<div class="content" data-bs-target="#navBar" style="overflow-y: auto">',
|
||||
' <div id="div-jsm-1">div 1</div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const div = fixtureEl.querySelector('.content')
|
||||
// eslint-disable-next-line no-new
|
||||
new ScrollSpy(div, {
|
||||
offset: 1,
|
||||
smoothScroll: true
|
||||
})
|
||||
|
||||
const clickSpy = getElementScrollSpy(div)
|
||||
|
||||
fixtureEl.querySelector('#anchor-2').click()
|
||||
expect(clickSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should call `scrollTop` if element doesn\'t not support `scrollTo`', () => {
|
||||
fixtureEl.innerHTML = getDummyFixture()
|
||||
|
||||
const div = fixtureEl.querySelector('.content')
|
||||
const link = fixtureEl.querySelector('[href="#div-jsm-1"]')
|
||||
delete div.scrollTo
|
||||
const clickSpy = getElementScrollSpy(div)
|
||||
// eslint-disable-next-line no-new
|
||||
new ScrollSpy(div, {
|
||||
offset: 1,
|
||||
smoothScroll: true
|
||||
})
|
||||
|
||||
link.click()
|
||||
expect(clickSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should smoothScroll to the proper observable element on anchor click', done => {
|
||||
fixtureEl.innerHTML = getDummyFixture()
|
||||
|
||||
const div = fixtureEl.querySelector('.content')
|
||||
const link = fixtureEl.querySelector('[href="#div-jsm-1"]')
|
||||
const observable = fixtureEl.querySelector('#div-jsm-1')
|
||||
const clickSpy = getElementScrollSpy(div)
|
||||
// eslint-disable-next-line no-new
|
||||
new ScrollSpy(div, {
|
||||
offset: 1,
|
||||
smoothScroll: true
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
if (div.scrollTo) {
|
||||
expect(clickSpy).toHaveBeenCalledWith({ top: observable.offsetTop - div.offsetTop, behavior: 'smooth' })
|
||||
} else {
|
||||
expect(clickSpy).toHaveBeenCalledWith(observable.offsetTop - div.offsetTop)
|
||||
}
|
||||
|
||||
done()
|
||||
}, 100)
|
||||
link.click()
|
||||
})
|
||||
})
|
||||
})
|
1101
js/tests/unit/tab.spec.js
Normal file
1101
js/tests/unit/tab.spec.js
Normal file
File diff suppressed because it is too large
Load diff
670
js/tests/unit/toast.spec.js
Normal file
670
js/tests/unit/toast.spec.js
Normal file
|
@ -0,0 +1,670 @@
|
|||
import Toast from '../../src/toast'
|
||||
import { clearFixture, createEvent, getFixture, jQueryMock } from '../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('DATA_KEY', () => {
|
||||
it('should return plugin data key', () => {
|
||||
expect(Toast.DATA_KEY).toEqual('bs.toast')
|
||||
})
|
||||
})
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should take care of element either passed as a CSS selector or DOM element', () => {
|
||||
fixtureEl.innerHTML = '<div class="toast"></div>'
|
||||
|
||||
const toastEl = fixtureEl.querySelector('.toast')
|
||||
const toastBySelector = new Toast('.toast')
|
||||
const toastByElement = new Toast(toastEl)
|
||||
|
||||
expect(toastBySelector._element).toEqual(toastEl)
|
||||
expect(toastByElement._element).toEqual(toastEl)
|
||||
})
|
||||
|
||||
it('should allow to config in js', () => {
|
||||
return new Promise(resolve => {
|
||||
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).toHaveClass('show')
|
||||
resolve()
|
||||
})
|
||||
|
||||
toast.show()
|
||||
})
|
||||
})
|
||||
|
||||
it('should close toast when close element with data-bs-dismiss attribute is set', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div class="toast" data-bs-delay="1" data-bs-autohide="false" data-bs-animation="false">',
|
||||
' <button type="button" class="ms-2 mb-1 btn-close" data-bs-dismiss="toast" aria-label="Close"></button>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const toastEl = fixtureEl.querySelector('div')
|
||||
const toast = new Toast(toastEl)
|
||||
|
||||
toastEl.addEventListener('shown.bs.toast', () => {
|
||||
expect(toastEl).toHaveClass('show')
|
||||
|
||||
const button = toastEl.querySelector('.btn-close')
|
||||
|
||||
button.click()
|
||||
})
|
||||
|
||||
toastEl.addEventListener('hidden.bs.toast', () => {
|
||||
expect(toastEl).not.toHaveClass('show')
|
||||
resolve()
|
||||
})
|
||||
|
||||
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-bs-autohide="false" data-bs-animation="false">',
|
||||
' <button type="button" class="ms-2 mb-1 btn-close" data-bs-dismiss="toast" aria-label="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', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div class="toast" data-bs-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).not.toHaveClass('show')
|
||||
resolve()
|
||||
})
|
||||
|
||||
toast.show()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not add fade class', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div class="toast" data-bs-delay="1" data-bs-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).not.toHaveClass('fade')
|
||||
resolve()
|
||||
})
|
||||
|
||||
toast.show()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not trigger shown if show is prevented', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div class="toast" data-bs-delay="1" data-bs-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).not.toHaveClass('show')
|
||||
resolve()
|
||||
}, 20)
|
||||
}
|
||||
|
||||
toastEl.addEventListener('show.bs.toast', event => {
|
||||
event.preventDefault()
|
||||
assertDone()
|
||||
})
|
||||
|
||||
toastEl.addEventListener('shown.bs.toast', () => {
|
||||
reject(new Error('shown event should not be triggered if show is prevented'))
|
||||
})
|
||||
|
||||
toast.show()
|
||||
})
|
||||
})
|
||||
|
||||
it('should clear timeout if toast is shown again before it is hidden', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div class="toast">',
|
||||
' <div class="toast-body">',
|
||||
' a simple toast',
|
||||
' </div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const toastEl = fixtureEl.querySelector('.toast')
|
||||
const toast = new Toast(toastEl)
|
||||
|
||||
setTimeout(() => {
|
||||
toast._config.autohide = false
|
||||
toastEl.addEventListener('shown.bs.toast', () => {
|
||||
expect(spy).toHaveBeenCalled()
|
||||
expect(toast._timeout).toBeNull()
|
||||
resolve()
|
||||
})
|
||||
toast.show()
|
||||
}, toast._config.delay / 2)
|
||||
|
||||
const spy = spyOn(toast, '_clearTimeout').and.callThrough()
|
||||
|
||||
toast.show()
|
||||
})
|
||||
})
|
||||
|
||||
it('should clear timeout if toast is interacted with mouse', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div class="toast">',
|
||||
' <div class="toast-body">',
|
||||
' a simple toast',
|
||||
' </div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const toastEl = fixtureEl.querySelector('.toast')
|
||||
const toast = new Toast(toastEl)
|
||||
const spy = spyOn(toast, '_clearTimeout').and.callThrough()
|
||||
|
||||
setTimeout(() => {
|
||||
spy.calls.reset()
|
||||
|
||||
toastEl.addEventListener('mouseover', () => {
|
||||
expect(toast._clearTimeout).toHaveBeenCalledTimes(1)
|
||||
expect(toast._timeout).toBeNull()
|
||||
resolve()
|
||||
})
|
||||
|
||||
const mouseOverEvent = createEvent('mouseover')
|
||||
toastEl.dispatchEvent(mouseOverEvent)
|
||||
}, toast._config.delay / 2)
|
||||
|
||||
toast.show()
|
||||
})
|
||||
})
|
||||
|
||||
it('should clear timeout if toast is interacted with keyboard', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<button id="outside-focusable">outside focusable</button>',
|
||||
'<div class="toast">',
|
||||
' <div class="toast-body">',
|
||||
' a simple toast',
|
||||
' <button>with a button</button>',
|
||||
' </div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const toastEl = fixtureEl.querySelector('.toast')
|
||||
const toast = new Toast(toastEl)
|
||||
const spy = spyOn(toast, '_clearTimeout').and.callThrough()
|
||||
|
||||
setTimeout(() => {
|
||||
spy.calls.reset()
|
||||
|
||||
toastEl.addEventListener('focusin', () => {
|
||||
expect(toast._clearTimeout).toHaveBeenCalledTimes(1)
|
||||
expect(toast._timeout).toBeNull()
|
||||
resolve()
|
||||
})
|
||||
|
||||
const insideFocusable = toastEl.querySelector('button')
|
||||
insideFocusable.focus()
|
||||
}, toast._config.delay / 2)
|
||||
|
||||
toast.show()
|
||||
})
|
||||
})
|
||||
|
||||
it('should still auto hide after being interacted with mouse and keyboard', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<button id="outside-focusable">outside focusable</button>',
|
||||
'<div class="toast">',
|
||||
' <div class="toast-body">',
|
||||
' a simple toast',
|
||||
' <button>with a button</button>',
|
||||
' </div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const toastEl = fixtureEl.querySelector('.toast')
|
||||
const toast = new Toast(toastEl)
|
||||
|
||||
setTimeout(() => {
|
||||
toastEl.addEventListener('mouseover', () => {
|
||||
const insideFocusable = toastEl.querySelector('button')
|
||||
insideFocusable.focus()
|
||||
})
|
||||
|
||||
toastEl.addEventListener('focusin', () => {
|
||||
const mouseOutEvent = createEvent('mouseout')
|
||||
toastEl.dispatchEvent(mouseOutEvent)
|
||||
})
|
||||
|
||||
toastEl.addEventListener('mouseout', () => {
|
||||
const outsideFocusable = document.getElementById('outside-focusable')
|
||||
outsideFocusable.focus()
|
||||
})
|
||||
|
||||
toastEl.addEventListener('focusout', () => {
|
||||
expect(toast._timeout).not.toBeNull()
|
||||
resolve()
|
||||
})
|
||||
|
||||
const mouseOverEvent = createEvent('mouseover')
|
||||
toastEl.dispatchEvent(mouseOverEvent)
|
||||
}, toast._config.delay / 2)
|
||||
|
||||
toast.show()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not auto hide if focus leaves but mouse pointer remains inside', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<button id="outside-focusable">outside focusable</button>',
|
||||
'<div class="toast">',
|
||||
' <div class="toast-body">',
|
||||
' a simple toast',
|
||||
' <button>with a button</button>',
|
||||
' </div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const toastEl = fixtureEl.querySelector('.toast')
|
||||
const toast = new Toast(toastEl)
|
||||
|
||||
setTimeout(() => {
|
||||
toastEl.addEventListener('mouseover', () => {
|
||||
const insideFocusable = toastEl.querySelector('button')
|
||||
insideFocusable.focus()
|
||||
})
|
||||
|
||||
toastEl.addEventListener('focusin', () => {
|
||||
const outsideFocusable = document.getElementById('outside-focusable')
|
||||
outsideFocusable.focus()
|
||||
})
|
||||
|
||||
toastEl.addEventListener('focusout', () => {
|
||||
expect(toast._timeout).toBeNull()
|
||||
resolve()
|
||||
})
|
||||
|
||||
const mouseOverEvent = createEvent('mouseover')
|
||||
toastEl.dispatchEvent(mouseOverEvent)
|
||||
}, toast._config.delay / 2)
|
||||
|
||||
toast.show()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not auto hide if mouse pointer leaves but focus remains inside', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<button id="outside-focusable">outside focusable</button>',
|
||||
'<div class="toast">',
|
||||
' <div class="toast-body">',
|
||||
' a simple toast',
|
||||
' <button>with a button</button>',
|
||||
' </div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const toastEl = fixtureEl.querySelector('.toast')
|
||||
const toast = new Toast(toastEl)
|
||||
|
||||
setTimeout(() => {
|
||||
toastEl.addEventListener('mouseover', () => {
|
||||
const insideFocusable = toastEl.querySelector('button')
|
||||
insideFocusable.focus()
|
||||
})
|
||||
|
||||
toastEl.addEventListener('focusin', () => {
|
||||
const mouseOutEvent = createEvent('mouseout')
|
||||
toastEl.dispatchEvent(mouseOutEvent)
|
||||
})
|
||||
|
||||
toastEl.addEventListener('mouseout', () => {
|
||||
expect(toast._timeout).toBeNull()
|
||||
resolve()
|
||||
})
|
||||
|
||||
const mouseOverEvent = createEvent('mouseover')
|
||||
toastEl.dispatchEvent(mouseOverEvent)
|
||||
}, toast._config.delay / 2)
|
||||
|
||||
toast.show()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('hide', () => {
|
||||
it('should allow to hide toast manually', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div class="toast" data-bs-delay="1" data-bs-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).not.toHaveClass('show')
|
||||
resolve()
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
const spy = spyOn(toastEl.classList, 'contains')
|
||||
|
||||
toast.hide()
|
||||
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not trigger hidden if hide is prevented', () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div class="toast" data-bs-delay="1" data-bs-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).toHaveClass('show')
|
||||
resolve()
|
||||
}, 20)
|
||||
}
|
||||
|
||||
toastEl.addEventListener('shown.bs.toast', () => {
|
||||
toast.hide()
|
||||
})
|
||||
|
||||
toastEl.addEventListener('hide.bs.toast', event => {
|
||||
event.preventDefault()
|
||||
assertDone()
|
||||
})
|
||||
|
||||
toastEl.addEventListener('hidden.bs.toast', () => {
|
||||
reject(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)).not.toBeNull()
|
||||
|
||||
toast.dispose()
|
||||
|
||||
expect(Toast.getInstance(toastEl)).toBeNull()
|
||||
})
|
||||
|
||||
it('should allow to destroy toast and hide it before that', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div class="toast" data-bs-delay="0" data-bs-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).toHaveClass('show')
|
||||
expect(Toast.getInstance(toastEl)).not.toBeNull()
|
||||
|
||||
toast.dispose()
|
||||
|
||||
expect(Toast.getInstance(toastEl)).toBeNull()
|
||||
expect(toastEl).not.toHaveClass('show')
|
||||
|
||||
resolve()
|
||||
}
|
||||
|
||||
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)).not.toBeNull()
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
const spy = spyOn(toast, 'show')
|
||||
|
||||
jQueryMock.fn.toast = Toast.jQueryInterface
|
||||
jQueryMock.elements = [div]
|
||||
|
||||
jQueryMock.fn.toast.call(jQueryMock, 'show')
|
||||
|
||||
expect(Toast.getInstance(div)).toEqual(toast)
|
||||
expect(spy).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]
|
||||
|
||||
expect(() => {
|
||||
jQueryMock.fn.toast.call(jQueryMock, action)
|
||||
}).toThrowError(TypeError, `No method named "${action}"`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getInstance', () => {
|
||||
it('should return a toast instance', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
const toast = new Toast(div)
|
||||
|
||||
expect(Toast.getInstance(div)).toEqual(toast)
|
||||
expect(Toast.getInstance(div)).toBeInstanceOf(Toast)
|
||||
})
|
||||
|
||||
it('should return null when there is no toast instance', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Toast.getInstance(div)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getOrCreateInstance', () => {
|
||||
it('should return toast instance', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
const toast = new Toast(div)
|
||||
|
||||
expect(Toast.getOrCreateInstance(div)).toEqual(toast)
|
||||
expect(Toast.getInstance(div)).toEqual(Toast.getOrCreateInstance(div, {}))
|
||||
expect(Toast.getOrCreateInstance(div)).toBeInstanceOf(Toast)
|
||||
})
|
||||
|
||||
it('should return new instance when there is no toast instance', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Toast.getInstance(div)).toBeNull()
|
||||
expect(Toast.getOrCreateInstance(div)).toBeInstanceOf(Toast)
|
||||
})
|
||||
|
||||
it('should return new instance when there is no toast instance with given configuration', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Toast.getInstance(div)).toBeNull()
|
||||
const toast = Toast.getOrCreateInstance(div, {
|
||||
delay: 1
|
||||
})
|
||||
expect(toast).toBeInstanceOf(Toast)
|
||||
|
||||
expect(toast._config.delay).toEqual(1)
|
||||
})
|
||||
|
||||
it('should return the instance when exists without given configuration', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
const toast = new Toast(div, {
|
||||
delay: 1
|
||||
})
|
||||
expect(Toast.getInstance(div)).toEqual(toast)
|
||||
|
||||
const toast2 = Toast.getOrCreateInstance(div, {
|
||||
delay: 2
|
||||
})
|
||||
expect(toast).toBeInstanceOf(Toast)
|
||||
expect(toast2).toEqual(toast)
|
||||
|
||||
expect(toast2._config.delay).toEqual(1)
|
||||
})
|
||||
})
|
||||
})
|
1551
js/tests/unit/tooltip.spec.js
Normal file
1551
js/tests/unit/tooltip.spec.js
Normal file
File diff suppressed because it is too large
Load diff
321
js/tests/unit/util/backdrop.spec.js
Normal file
321
js/tests/unit/util/backdrop.spec.js
Normal file
|
@ -0,0 +1,321 @@
|
|||
import Backdrop from '../../../src/util/backdrop'
|
||||
import { getTransitionDurationFromElement } from '../../../src/util/index'
|
||||
import { clearFixture, getFixture } from '../../helpers/fixture'
|
||||
|
||||
const CLASS_BACKDROP = '.modal-backdrop'
|
||||
const CLASS_NAME_FADE = 'fade'
|
||||
const CLASS_NAME_SHOW = 'show'
|
||||
|
||||
describe('Backdrop', () => {
|
||||
let fixtureEl
|
||||
|
||||
beforeAll(() => {
|
||||
fixtureEl = getFixture()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
clearFixture()
|
||||
const list = document.querySelectorAll(CLASS_BACKDROP)
|
||||
|
||||
for (const el of list) {
|
||||
el.remove()
|
||||
}
|
||||
})
|
||||
|
||||
describe('show', () => {
|
||||
it('should append the backdrop html once on show and include the "show" class if it is "shown"', () => {
|
||||
return new Promise(resolve => {
|
||||
const instance = new Backdrop({
|
||||
isVisible: true,
|
||||
isAnimated: false
|
||||
})
|
||||
const getElements = () => document.querySelectorAll(CLASS_BACKDROP)
|
||||
|
||||
expect(getElements()).toHaveSize(0)
|
||||
|
||||
instance.show()
|
||||
instance.show(() => {
|
||||
expect(getElements()).toHaveSize(1)
|
||||
for (const el of getElements()) {
|
||||
expect(el).toHaveClass(CLASS_NAME_SHOW)
|
||||
}
|
||||
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should not append the backdrop html if it is not "shown"', () => {
|
||||
return new Promise(resolve => {
|
||||
const instance = new Backdrop({
|
||||
isVisible: false,
|
||||
isAnimated: true
|
||||
})
|
||||
const getElements = () => document.querySelectorAll(CLASS_BACKDROP)
|
||||
|
||||
expect(getElements()).toHaveSize(0)
|
||||
instance.show(() => {
|
||||
expect(getElements()).toHaveSize(0)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should append the backdrop html once and include the "fade" class if it is "shown" and "animated"', () => {
|
||||
return new Promise(resolve => {
|
||||
const instance = new Backdrop({
|
||||
isVisible: true,
|
||||
isAnimated: true
|
||||
})
|
||||
const getElements = () => document.querySelectorAll(CLASS_BACKDROP)
|
||||
|
||||
expect(getElements()).toHaveSize(0)
|
||||
|
||||
instance.show(() => {
|
||||
expect(getElements()).toHaveSize(1)
|
||||
for (const el of getElements()) {
|
||||
expect(el).toHaveClass(CLASS_NAME_FADE)
|
||||
}
|
||||
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('hide', () => {
|
||||
it('should remove the backdrop html', () => {
|
||||
return new Promise(resolve => {
|
||||
const instance = new Backdrop({
|
||||
isVisible: true,
|
||||
isAnimated: true
|
||||
})
|
||||
|
||||
const getElements = () => document.body.querySelectorAll(CLASS_BACKDROP)
|
||||
|
||||
expect(getElements()).toHaveSize(0)
|
||||
instance.show(() => {
|
||||
expect(getElements()).toHaveSize(1)
|
||||
instance.hide(() => {
|
||||
expect(getElements()).toHaveSize(0)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should remove the "show" class', () => {
|
||||
return new Promise(resolve => {
|
||||
const instance = new Backdrop({
|
||||
isVisible: true,
|
||||
isAnimated: true
|
||||
})
|
||||
const elem = instance._getElement()
|
||||
|
||||
instance.show()
|
||||
instance.hide(() => {
|
||||
expect(elem).not.toHaveClass(CLASS_NAME_SHOW)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should not try to remove Node on remove method if it is not "shown"', () => {
|
||||
return new Promise(resolve => {
|
||||
const instance = new Backdrop({
|
||||
isVisible: false,
|
||||
isAnimated: true
|
||||
})
|
||||
const getElements = () => document.querySelectorAll(CLASS_BACKDROP)
|
||||
const spy = spyOn(instance, 'dispose').and.callThrough()
|
||||
|
||||
expect(getElements()).toHaveSize(0)
|
||||
expect(instance._isAppended).toBeFalse()
|
||||
instance.show(() => {
|
||||
instance.hide(() => {
|
||||
expect(getElements()).toHaveSize(0)
|
||||
expect(spy).not.toHaveBeenCalled()
|
||||
expect(instance._isAppended).toBeFalse()
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should not error if the backdrop no longer has a parent', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div id="wrapper"></div>'
|
||||
|
||||
const wrapper = fixtureEl.querySelector('#wrapper')
|
||||
const instance = new Backdrop({
|
||||
isVisible: true,
|
||||
isAnimated: true,
|
||||
rootElement: wrapper
|
||||
})
|
||||
|
||||
const getElements = () => document.querySelectorAll(CLASS_BACKDROP)
|
||||
|
||||
instance.show(() => {
|
||||
wrapper.remove()
|
||||
instance.hide(() => {
|
||||
expect(getElements()).toHaveSize(0)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('click callback', () => {
|
||||
it('should execute callback on click', () => {
|
||||
return new Promise(resolve => {
|
||||
const spy = jasmine.createSpy('spy')
|
||||
|
||||
const instance = new Backdrop({
|
||||
isVisible: true,
|
||||
isAnimated: false,
|
||||
clickCallback: () => spy()
|
||||
})
|
||||
const endTest = () => {
|
||||
setTimeout(() => {
|
||||
expect(spy).toHaveBeenCalled()
|
||||
resolve()
|
||||
}, 10)
|
||||
}
|
||||
|
||||
instance.show(() => {
|
||||
const clickEvent = new Event('mousedown', { bubbles: true, cancelable: true })
|
||||
document.querySelector(CLASS_BACKDROP).dispatchEvent(clickEvent)
|
||||
endTest()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('animation callbacks', () => {
|
||||
it('should show and hide backdrop after counting transition duration if it is animated', () => {
|
||||
return new Promise(resolve => {
|
||||
const instance = new Backdrop({
|
||||
isVisible: true,
|
||||
isAnimated: true
|
||||
})
|
||||
const spy2 = jasmine.createSpy('spy2')
|
||||
|
||||
const execDone = () => {
|
||||
setTimeout(() => {
|
||||
expect(spy2).toHaveBeenCalledTimes(2)
|
||||
resolve()
|
||||
}, 10)
|
||||
}
|
||||
|
||||
instance.show(spy2)
|
||||
instance.hide(() => {
|
||||
spy2()
|
||||
execDone()
|
||||
})
|
||||
expect(spy2).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
it('should show and hide backdrop without a delay if it is not animated', () => {
|
||||
return new Promise(resolve => {
|
||||
const spy = jasmine.createSpy('spy', getTransitionDurationFromElement)
|
||||
const instance = new Backdrop({
|
||||
isVisible: true,
|
||||
isAnimated: false
|
||||
})
|
||||
const spy2 = jasmine.createSpy('spy2')
|
||||
|
||||
instance.show(spy2)
|
||||
instance.hide(spy2)
|
||||
|
||||
setTimeout(() => {
|
||||
expect(spy2).toHaveBeenCalled()
|
||||
expect(spy).not.toHaveBeenCalled()
|
||||
resolve()
|
||||
}, 10)
|
||||
})
|
||||
})
|
||||
|
||||
it('should not call delay callbacks if it is not "shown"', () => {
|
||||
return new Promise(resolve => {
|
||||
const instance = new Backdrop({
|
||||
isVisible: false,
|
||||
isAnimated: true
|
||||
})
|
||||
const spy = jasmine.createSpy('spy', getTransitionDurationFromElement)
|
||||
|
||||
instance.show()
|
||||
instance.hide(() => {
|
||||
expect(spy).not.toHaveBeenCalled()
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Config', () => {
|
||||
describe('rootElement initialization', () => {
|
||||
it('should be appended on "document.body" by default', () => {
|
||||
return new Promise(resolve => {
|
||||
const instance = new Backdrop({
|
||||
isVisible: true
|
||||
})
|
||||
const getElement = () => document.querySelector(CLASS_BACKDROP)
|
||||
instance.show(() => {
|
||||
expect(getElement().parentElement).toEqual(document.body)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should find the rootElement if passed as a string', () => {
|
||||
return new Promise(resolve => {
|
||||
const instance = new Backdrop({
|
||||
isVisible: true,
|
||||
rootElement: 'body'
|
||||
})
|
||||
const getElement = () => document.querySelector(CLASS_BACKDROP)
|
||||
instance.show(() => {
|
||||
expect(getElement().parentElement).toEqual(document.body)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should be appended on any element given by the proper config', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div id="wrapper"></div>'
|
||||
|
||||
const wrapper = fixtureEl.querySelector('#wrapper')
|
||||
const instance = new Backdrop({
|
||||
isVisible: true,
|
||||
rootElement: wrapper
|
||||
})
|
||||
const getElement = () => document.querySelector(CLASS_BACKDROP)
|
||||
instance.show(() => {
|
||||
expect(getElement().parentElement).toEqual(wrapper)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('ClassName', () => {
|
||||
it('should allow configuring className', () => {
|
||||
return new Promise(resolve => {
|
||||
const instance = new Backdrop({
|
||||
isVisible: true,
|
||||
className: 'foo'
|
||||
})
|
||||
const getElement = () => document.querySelector('.foo')
|
||||
instance.show(() => {
|
||||
expect(getElement()).toEqual(instance._getElement())
|
||||
instance.dispose()
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
108
js/tests/unit/util/component-functions.spec.js
Normal file
108
js/tests/unit/util/component-functions.spec.js
Normal file
|
@ -0,0 +1,108 @@
|
|||
/* Test helpers */
|
||||
|
||||
import { clearFixture, createEvent, getFixture } from '../../helpers/fixture'
|
||||
import { enableDismissTrigger } from '../../../src/util/component-functions'
|
||||
import BaseComponent from '../../../src/base-component'
|
||||
|
||||
class DummyClass2 extends BaseComponent {
|
||||
static get NAME() {
|
||||
return 'test'
|
||||
}
|
||||
|
||||
hide() {
|
||||
return true
|
||||
}
|
||||
|
||||
testMethod() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
describe('Plugin functions', () => {
|
||||
let fixtureEl
|
||||
|
||||
beforeAll(() => {
|
||||
fixtureEl = getFixture()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
clearFixture()
|
||||
})
|
||||
|
||||
describe('data-bs-dismiss functionality', () => {
|
||||
it('should get Plugin and execute the given method, when a click occurred on data-bs-dismiss="PluginName"', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div id="foo" class="test">',
|
||||
' <button type="button" data-bs-dismiss="test" data-bs-target="#foo"></button>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const spyGet = spyOn(DummyClass2, 'getOrCreateInstance').and.callThrough()
|
||||
const spyTest = spyOn(DummyClass2.prototype, 'testMethod')
|
||||
const componentWrapper = fixtureEl.querySelector('#foo')
|
||||
const btnClose = fixtureEl.querySelector('[data-bs-dismiss="test"]')
|
||||
const event = createEvent('click')
|
||||
|
||||
enableDismissTrigger(DummyClass2, 'testMethod')
|
||||
btnClose.dispatchEvent(event)
|
||||
|
||||
expect(spyGet).toHaveBeenCalledWith(componentWrapper)
|
||||
expect(spyTest).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('if data-bs-dismiss="PluginName" hasn\'t got "data-bs-target", "getOrCreateInstance" has to be initialized by closest "plugin.Name" class', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div id="foo" class="test">',
|
||||
' <button type="button" data-bs-dismiss="test"></button>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const spyGet = spyOn(DummyClass2, 'getOrCreateInstance').and.callThrough()
|
||||
const spyHide = spyOn(DummyClass2.prototype, 'hide')
|
||||
const componentWrapper = fixtureEl.querySelector('#foo')
|
||||
const btnClose = fixtureEl.querySelector('[data-bs-dismiss="test"]')
|
||||
const event = createEvent('click')
|
||||
|
||||
enableDismissTrigger(DummyClass2)
|
||||
btnClose.dispatchEvent(event)
|
||||
|
||||
expect(spyGet).toHaveBeenCalledWith(componentWrapper)
|
||||
expect(spyHide).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('if data-bs-dismiss="PluginName" is disabled, must not trigger function', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div id="foo" class="test">',
|
||||
' <button type="button" disabled data-bs-dismiss="test"></button>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const spy = spyOn(DummyClass2, 'getOrCreateInstance').and.callThrough()
|
||||
const btnClose = fixtureEl.querySelector('[data-bs-dismiss="test"]')
|
||||
const event = createEvent('click')
|
||||
|
||||
enableDismissTrigger(DummyClass2)
|
||||
btnClose.dispatchEvent(event)
|
||||
|
||||
expect(spy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should prevent default when the trigger is <a> or <area>', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div id="foo" class="test">',
|
||||
' <a type="button" data-bs-dismiss="test"></a>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const btnClose = fixtureEl.querySelector('[data-bs-dismiss="test"]')
|
||||
const event = createEvent('click')
|
||||
|
||||
enableDismissTrigger(DummyClass2)
|
||||
const spy = spyOn(Event.prototype, 'preventDefault').and.callThrough()
|
||||
|
||||
btnClose.dispatchEvent(event)
|
||||
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
166
js/tests/unit/util/config.spec.js
Normal file
166
js/tests/unit/util/config.spec.js
Normal file
|
@ -0,0 +1,166 @@
|
|||
import Config from '../../../src/util/config'
|
||||
import { clearFixture, getFixture } from '../../helpers/fixture'
|
||||
|
||||
class DummyConfigClass extends Config {
|
||||
static get NAME() {
|
||||
return 'dummy'
|
||||
}
|
||||
}
|
||||
|
||||
describe('Config', () => {
|
||||
let fixtureEl
|
||||
const name = 'dummy'
|
||||
|
||||
beforeAll(() => {
|
||||
fixtureEl = getFixture()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
clearFixture()
|
||||
})
|
||||
|
||||
describe('NAME', () => {
|
||||
it('should return plugin NAME', () => {
|
||||
expect(DummyConfigClass.NAME).toEqual(name)
|
||||
})
|
||||
})
|
||||
|
||||
describe('DefaultType', () => {
|
||||
it('should return plugin default type', () => {
|
||||
expect(DummyConfigClass.DefaultType).toEqual(jasmine.any(Object))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Default', () => {
|
||||
it('should return plugin defaults', () => {
|
||||
expect(DummyConfigClass.Default).toEqual(jasmine.any(Object))
|
||||
})
|
||||
})
|
||||
|
||||
describe('mergeConfigObj', () => {
|
||||
it('should parse element\'s data attributes and merge it with default config. Element\'s data attributes must excel Defaults', () => {
|
||||
fixtureEl.innerHTML = '<div id="test" data-bs-test-bool="false" data-bs-test-int="8" data-bs-test-string1="bar"></div>'
|
||||
|
||||
spyOnProperty(DummyConfigClass, 'Default', 'get').and.returnValue({
|
||||
testBool: true,
|
||||
testString: 'foo',
|
||||
testString1: 'foo',
|
||||
testInt: 7
|
||||
})
|
||||
const instance = new DummyConfigClass()
|
||||
const configResult = instance._mergeConfigObj({}, fixtureEl.querySelector('#test'))
|
||||
|
||||
expect(configResult.testBool).toEqual(false)
|
||||
expect(configResult.testString).toEqual('foo')
|
||||
expect(configResult.testString1).toEqual('bar')
|
||||
expect(configResult.testInt).toEqual(8)
|
||||
})
|
||||
|
||||
it('should parse element\'s data attributes and merge it with default config, plug these given during method call. The programmatically given should excel all', () => {
|
||||
fixtureEl.innerHTML = '<div id="test" data-bs-test-bool="false" data-bs-test-int="8" data-bs-test-string-1="bar"></div>'
|
||||
|
||||
spyOnProperty(DummyConfigClass, 'Default', 'get').and.returnValue({
|
||||
testBool: true,
|
||||
testString: 'foo',
|
||||
testString1: 'foo',
|
||||
testInt: 7
|
||||
})
|
||||
const instance = new DummyConfigClass()
|
||||
const configResult = instance._mergeConfigObj({
|
||||
testString1: 'test',
|
||||
testInt: 3
|
||||
}, fixtureEl.querySelector('#test'))
|
||||
|
||||
expect(configResult.testBool).toEqual(false)
|
||||
expect(configResult.testString).toEqual('foo')
|
||||
expect(configResult.testString1).toEqual('test')
|
||||
expect(configResult.testInt).toEqual(3)
|
||||
})
|
||||
|
||||
it('should parse element\'s data attribute `config` and any rest attributes. The programmatically given should excel all. Data attribute `config` should excel only Defaults', () => {
|
||||
fixtureEl.innerHTML = '<div id="test" data-bs-config=\'{"testBool":false,"testInt":50,"testInt2":100}\' data-bs-test-int="8" data-bs-test-string-1="bar"></div>'
|
||||
|
||||
spyOnProperty(DummyConfigClass, 'Default', 'get').and.returnValue({
|
||||
testBool: true,
|
||||
testString: 'foo',
|
||||
testString1: 'foo',
|
||||
testInt: 7,
|
||||
testInt2: 600
|
||||
})
|
||||
const instance = new DummyConfigClass()
|
||||
const configResult = instance._mergeConfigObj({
|
||||
testString1: 'test'
|
||||
}, fixtureEl.querySelector('#test'))
|
||||
|
||||
expect(configResult.testBool).toEqual(false)
|
||||
expect(configResult.testString).toEqual('foo')
|
||||
expect(configResult.testString1).toEqual('test')
|
||||
expect(configResult.testInt).toEqual(8)
|
||||
expect(configResult.testInt2).toEqual(100)
|
||||
})
|
||||
|
||||
it('should omit element\'s data attribute `config` if is not an object', () => {
|
||||
fixtureEl.innerHTML = '<div id="test" data-bs-config="foo" data-bs-test-int="8"></div>'
|
||||
|
||||
spyOnProperty(DummyConfigClass, 'Default', 'get').and.returnValue({
|
||||
testInt: 7,
|
||||
testInt2: 79
|
||||
})
|
||||
const instance = new DummyConfigClass()
|
||||
const configResult = instance._mergeConfigObj({}, fixtureEl.querySelector('#test'))
|
||||
|
||||
expect(configResult.testInt).toEqual(8)
|
||||
expect(configResult.testInt2).toEqual(79)
|
||||
})
|
||||
})
|
||||
|
||||
describe('typeCheckConfig', () => {
|
||||
it('should check type of the config object', () => {
|
||||
spyOnProperty(DummyConfigClass, 'DefaultType', 'get').and.returnValue({
|
||||
toggle: 'boolean',
|
||||
parent: '(string|element)'
|
||||
})
|
||||
const config = {
|
||||
toggle: true,
|
||||
parent: 777
|
||||
}
|
||||
|
||||
const obj = new DummyConfigClass()
|
||||
expect(() => {
|
||||
obj._typeCheckConfig(config)
|
||||
}).toThrowError(TypeError, obj.constructor.NAME.toUpperCase() + ': Option "parent" provided type "number" but expected type "(string|element)".')
|
||||
})
|
||||
|
||||
it('should return null stringified when null is passed', () => {
|
||||
spyOnProperty(DummyConfigClass, 'DefaultType', 'get').and.returnValue({
|
||||
toggle: 'boolean',
|
||||
parent: '(null|element)'
|
||||
})
|
||||
|
||||
const obj = new DummyConfigClass()
|
||||
const config = {
|
||||
toggle: true,
|
||||
parent: null
|
||||
}
|
||||
|
||||
obj._typeCheckConfig(config)
|
||||
expect().nothing()
|
||||
})
|
||||
|
||||
it('should return undefined stringified when undefined is passed', () => {
|
||||
spyOnProperty(DummyConfigClass, 'DefaultType', 'get').and.returnValue({
|
||||
toggle: 'boolean',
|
||||
parent: '(undefined|element)'
|
||||
})
|
||||
|
||||
const obj = new DummyConfigClass()
|
||||
const config = {
|
||||
toggle: true,
|
||||
parent: undefined
|
||||
}
|
||||
|
||||
obj._typeCheckConfig(config)
|
||||
expect().nothing()
|
||||
})
|
||||
})
|
||||
})
|
218
js/tests/unit/util/focustrap.spec.js
Normal file
218
js/tests/unit/util/focustrap.spec.js
Normal file
|
@ -0,0 +1,218 @@
|
|||
import FocusTrap from '../../../src/util/focustrap'
|
||||
import EventHandler from '../../../src/dom/event-handler'
|
||||
import SelectorEngine from '../../../src/dom/selector-engine'
|
||||
import { clearFixture, createEvent, getFixture } from '../../helpers/fixture'
|
||||
|
||||
describe('FocusTrap', () => {
|
||||
let fixtureEl
|
||||
|
||||
beforeAll(() => {
|
||||
fixtureEl = getFixture()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
clearFixture()
|
||||
})
|
||||
|
||||
describe('activate', () => {
|
||||
it('should autofocus itself by default', () => {
|
||||
fixtureEl.innerHTML = '<div id="focustrap" tabindex="-1"></div>'
|
||||
|
||||
const trapElement = fixtureEl.querySelector('div')
|
||||
|
||||
const spy = spyOn(trapElement, 'focus')
|
||||
|
||||
const focustrap = new FocusTrap({ trapElement })
|
||||
focustrap.activate()
|
||||
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('if configured not to autofocus, should not autofocus itself', () => {
|
||||
fixtureEl.innerHTML = '<div id="focustrap" tabindex="-1"></div>'
|
||||
|
||||
const trapElement = fixtureEl.querySelector('div')
|
||||
|
||||
const spy = spyOn(trapElement, 'focus')
|
||||
|
||||
const focustrap = new FocusTrap({ trapElement, autofocus: false })
|
||||
focustrap.activate()
|
||||
|
||||
expect(spy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should force focus inside focus trap if it can', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a href="#" id="outside">outside</a>',
|
||||
'<div id="focustrap" tabindex="-1">',
|
||||
' <a href="#" id="inside">inside</a>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const trapElement = fixtureEl.querySelector('div')
|
||||
const focustrap = new FocusTrap({ trapElement })
|
||||
focustrap.activate()
|
||||
|
||||
const inside = document.getElementById('inside')
|
||||
|
||||
const focusInListener = () => {
|
||||
expect(spy).toHaveBeenCalled()
|
||||
document.removeEventListener('focusin', focusInListener)
|
||||
resolve()
|
||||
}
|
||||
|
||||
const spy = spyOn(inside, 'focus')
|
||||
spyOn(SelectorEngine, 'focusableChildren').and.callFake(() => [inside])
|
||||
|
||||
document.addEventListener('focusin', focusInListener)
|
||||
|
||||
const focusInEvent = createEvent('focusin', { bubbles: true })
|
||||
Object.defineProperty(focusInEvent, 'target', {
|
||||
value: document.getElementById('outside')
|
||||
})
|
||||
|
||||
document.dispatchEvent(focusInEvent)
|
||||
})
|
||||
})
|
||||
|
||||
it('should wrap focus around forward on tab', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a href="#" id="outside">outside</a>',
|
||||
'<div id="focustrap" tabindex="-1">',
|
||||
' <a href="#" id="first">first</a>',
|
||||
' <a href="#" id="inside">inside</a>',
|
||||
' <a href="#" id="last">last</a>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const trapElement = fixtureEl.querySelector('div')
|
||||
const focustrap = new FocusTrap({ trapElement })
|
||||
focustrap.activate()
|
||||
|
||||
const first = document.getElementById('first')
|
||||
const inside = document.getElementById('inside')
|
||||
const last = document.getElementById('last')
|
||||
const outside = document.getElementById('outside')
|
||||
|
||||
spyOn(SelectorEngine, 'focusableChildren').and.callFake(() => [first, inside, last])
|
||||
const spy = spyOn(first, 'focus').and.callThrough()
|
||||
|
||||
const focusInListener = () => {
|
||||
expect(spy).toHaveBeenCalled()
|
||||
first.removeEventListener('focusin', focusInListener)
|
||||
resolve()
|
||||
}
|
||||
|
||||
first.addEventListener('focusin', focusInListener)
|
||||
|
||||
const keydown = createEvent('keydown')
|
||||
keydown.key = 'Tab'
|
||||
|
||||
document.dispatchEvent(keydown)
|
||||
outside.focus()
|
||||
})
|
||||
})
|
||||
|
||||
it('should wrap focus around backwards on shift-tab', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a href="#" id="outside">outside</a>',
|
||||
'<div id="focustrap" tabindex="-1">',
|
||||
' <a href="#" id="first">first</a>',
|
||||
' <a href="#" id="inside">inside</a>',
|
||||
' <a href="#" id="last">last</a>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const trapElement = fixtureEl.querySelector('div')
|
||||
const focustrap = new FocusTrap({ trapElement })
|
||||
focustrap.activate()
|
||||
|
||||
const first = document.getElementById('first')
|
||||
const inside = document.getElementById('inside')
|
||||
const last = document.getElementById('last')
|
||||
const outside = document.getElementById('outside')
|
||||
|
||||
spyOn(SelectorEngine, 'focusableChildren').and.callFake(() => [first, inside, last])
|
||||
const spy = spyOn(last, 'focus').and.callThrough()
|
||||
|
||||
const focusInListener = () => {
|
||||
expect(spy).toHaveBeenCalled()
|
||||
last.removeEventListener('focusin', focusInListener)
|
||||
resolve()
|
||||
}
|
||||
|
||||
last.addEventListener('focusin', focusInListener)
|
||||
|
||||
const keydown = createEvent('keydown')
|
||||
keydown.key = 'Tab'
|
||||
keydown.shiftKey = true
|
||||
|
||||
document.dispatchEvent(keydown)
|
||||
outside.focus()
|
||||
})
|
||||
})
|
||||
|
||||
it('should force focus on itself if there is no focusable content', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a href="#" id="outside">outside</a>',
|
||||
'<div id="focustrap" tabindex="-1"></div>'
|
||||
].join('')
|
||||
|
||||
const trapElement = fixtureEl.querySelector('div')
|
||||
const focustrap = new FocusTrap({ trapElement })
|
||||
focustrap.activate()
|
||||
|
||||
const focusInListener = () => {
|
||||
expect(spy).toHaveBeenCalled()
|
||||
document.removeEventListener('focusin', focusInListener)
|
||||
resolve()
|
||||
}
|
||||
|
||||
const spy = spyOn(focustrap._config.trapElement, 'focus')
|
||||
|
||||
document.addEventListener('focusin', focusInListener)
|
||||
|
||||
const focusInEvent = createEvent('focusin', { bubbles: true })
|
||||
Object.defineProperty(focusInEvent, 'target', {
|
||||
value: document.getElementById('outside')
|
||||
})
|
||||
|
||||
document.dispatchEvent(focusInEvent)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('deactivate', () => {
|
||||
it('should flag itself as no longer active', () => {
|
||||
const focustrap = new FocusTrap({ trapElement: fixtureEl })
|
||||
focustrap.activate()
|
||||
expect(focustrap._isActive).toBeTrue()
|
||||
|
||||
focustrap.deactivate()
|
||||
expect(focustrap._isActive).toBeFalse()
|
||||
})
|
||||
|
||||
it('should remove all event listeners', () => {
|
||||
const focustrap = new FocusTrap({ trapElement: fixtureEl })
|
||||
focustrap.activate()
|
||||
|
||||
const spy = spyOn(EventHandler, 'off')
|
||||
focustrap.deactivate()
|
||||
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('doesn\'t try removing event listeners unless it needs to (in case it hasn\'t been activated)', () => {
|
||||
const focustrap = new FocusTrap({ trapElement: fixtureEl })
|
||||
|
||||
const spy = spyOn(EventHandler, 'off')
|
||||
focustrap.deactivate()
|
||||
|
||||
expect(spy).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
814
js/tests/unit/util/index.spec.js
Normal file
814
js/tests/unit/util/index.spec.js
Normal file
|
@ -0,0 +1,814 @@
|
|||
import * as Util from '../../../src/util/index'
|
||||
import { clearFixture, getFixture } from '../../helpers/fixture'
|
||||
import { noop } from '../../../src/util/index'
|
||||
|
||||
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-bs-target', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div id="test" data-bs-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-bs-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-bs-target equal to #', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" data-bs-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 a selector from a href is a url without an anchor', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" data-bs-target="#" href="foo/bar.html"></a>',
|
||||
'<div class="target"></div>'
|
||||
].join('')
|
||||
|
||||
const testEl = fixtureEl.querySelector('#test')
|
||||
|
||||
expect(Util.getSelectorFromElement(testEl)).toBeNull()
|
||||
})
|
||||
|
||||
it('should return the anchor if a selector from a href is a url', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<a id="test" data-bs-target="#" href="foo/bar.html#target"></a>',
|
||||
'<div id="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-bs-target', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div id="test" data-bs-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-bs-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', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const el = fixtureEl.querySelector('div')
|
||||
const spy = spyOn(el, 'dispatchEvent').and.callThrough()
|
||||
|
||||
el.addEventListener('transitionend', () => {
|
||||
expect(spy).toHaveBeenCalled()
|
||||
resolve()
|
||||
})
|
||||
|
||||
Util.triggerTransitionEnd(el)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('isElement', () => {
|
||||
it('should detect if the parameter is an element or not and return Boolean', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div id="foo" class="test"></div>',
|
||||
'<div id="bar" class="test"></div>'
|
||||
].join('')
|
||||
|
||||
const el = fixtureEl.querySelector('#foo')
|
||||
|
||||
expect(Util.isElement(el)).toBeTrue()
|
||||
expect(Util.isElement({})).toBeFalse()
|
||||
expect(Util.isElement(fixtureEl.querySelectorAll('.test'))).toBeFalse()
|
||||
})
|
||||
|
||||
it('should detect jQuery element', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const el = fixtureEl.querySelector('div')
|
||||
const fakejQuery = {
|
||||
0: el,
|
||||
jquery: 'foo'
|
||||
}
|
||||
|
||||
expect(Util.isElement(fakejQuery)).toBeTrue()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getElement', () => {
|
||||
it('should try to parse element', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div id="foo" class="test"></div>',
|
||||
'<div id="bar" class="test"></div>'
|
||||
].join('')
|
||||
|
||||
const el = fixtureEl.querySelector('div')
|
||||
|
||||
expect(Util.getElement(el)).toEqual(el)
|
||||
expect(Util.getElement('#foo')).toEqual(el)
|
||||
expect(Util.getElement('#fail')).toBeNull()
|
||||
expect(Util.getElement({})).toBeNull()
|
||||
expect(Util.getElement([])).toBeNull()
|
||||
expect(Util.getElement()).toBeNull()
|
||||
expect(Util.getElement(null)).toBeNull()
|
||||
expect(Util.getElement(fixtureEl.querySelectorAll('.test'))).toBeNull()
|
||||
|
||||
const fakejQueryObject = {
|
||||
0: el,
|
||||
jquery: 'foo'
|
||||
}
|
||||
|
||||
expect(Util.getElement(fakejQueryObject)).toEqual(el)
|
||||
})
|
||||
})
|
||||
|
||||
describe('isVisible', () => {
|
||||
it('should return false if the element is not defined', () => {
|
||||
expect(Util.isVisible(null)).toBeFalse()
|
||||
expect(Util.isVisible(undefined)).toBeFalse()
|
||||
})
|
||||
|
||||
it('should return false if the element provided is not a dom element', () => {
|
||||
expect(Util.isVisible({})).toBeFalse()
|
||||
})
|
||||
|
||||
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)).toBeFalse()
|
||||
})
|
||||
|
||||
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)).toBeFalse()
|
||||
})
|
||||
|
||||
it('should return false if an ancestor element is display none', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div style="display: none;">',
|
||||
' <div>',
|
||||
' <div>',
|
||||
' <div class="content"></div>',
|
||||
' </div>',
|
||||
' </div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const div = fixtureEl.querySelector('.content')
|
||||
|
||||
expect(Util.isVisible(div)).toBeFalse()
|
||||
})
|
||||
|
||||
it('should return false if an ancestor element is visibility hidden', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div style="visibility: hidden;">',
|
||||
' <div>',
|
||||
' <div>',
|
||||
' <div class="content"></div>',
|
||||
' </div>',
|
||||
' </div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const div = fixtureEl.querySelector('.content')
|
||||
|
||||
expect(Util.isVisible(div)).toBeFalse()
|
||||
})
|
||||
|
||||
it('should return true if an ancestor element is visibility hidden, but reverted', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div style="visibility: hidden;">',
|
||||
' <div style="visibility: visible;">',
|
||||
' <div>',
|
||||
' <div class="content"></div>',
|
||||
' </div>',
|
||||
' </div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const div = fixtureEl.querySelector('.content')
|
||||
|
||||
expect(Util.isVisible(div)).toBeTrue()
|
||||
})
|
||||
|
||||
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)).toBeTrue()
|
||||
})
|
||||
|
||||
it('should return false if the element is hidden, but not via display or visibility', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<details>',
|
||||
' <div id="element"></div>',
|
||||
'</details>'
|
||||
].join('')
|
||||
|
||||
const div = fixtureEl.querySelector('#element')
|
||||
|
||||
expect(Util.isVisible(div)).toBeFalse()
|
||||
})
|
||||
|
||||
it('should return true if its a closed details element', () => {
|
||||
fixtureEl.innerHTML = '<details id="element"></details>'
|
||||
|
||||
const div = fixtureEl.querySelector('#element')
|
||||
|
||||
expect(Util.isVisible(div)).toBeTrue()
|
||||
})
|
||||
|
||||
it('should return true if the element is visible inside an open details element', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<details open>',
|
||||
' <div id="element"></div>',
|
||||
'</details>'
|
||||
].join('')
|
||||
|
||||
const div = fixtureEl.querySelector('#element')
|
||||
|
||||
expect(Util.isVisible(div)).toBeTrue()
|
||||
})
|
||||
|
||||
it('should return true if the element is a visible summary in a closed details element', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<details>',
|
||||
' <summary id="element-1">',
|
||||
' <span id="element-2"></span>',
|
||||
' </summary>',
|
||||
'</details>'
|
||||
].join('')
|
||||
|
||||
const element1 = fixtureEl.querySelector('#element-1')
|
||||
const element2 = fixtureEl.querySelector('#element-2')
|
||||
|
||||
expect(Util.isVisible(element1)).toBeTrue()
|
||||
expect(Util.isVisible(element2)).toBeTrue()
|
||||
})
|
||||
})
|
||||
|
||||
describe('isDisabled', () => {
|
||||
it('should return true if the element is not defined', () => {
|
||||
expect(Util.isDisabled(null)).toBeTrue()
|
||||
expect(Util.isDisabled(undefined)).toBeTrue()
|
||||
expect(Util.isDisabled()).toBeTrue()
|
||||
})
|
||||
|
||||
it('should return true if the element provided is not a dom element', () => {
|
||||
expect(Util.isDisabled({})).toBeTrue()
|
||||
expect(Util.isDisabled('test')).toBeTrue()
|
||||
})
|
||||
|
||||
it('should return true if the element has disabled attribute', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div>',
|
||||
' <div id="element" disabled="disabled"></div>',
|
||||
' <div id="element1" disabled="true"></div>',
|
||||
' <div id="element2" disabled></div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const div = fixtureEl.querySelector('#element')
|
||||
const div1 = fixtureEl.querySelector('#element1')
|
||||
const div2 = fixtureEl.querySelector('#element2')
|
||||
|
||||
expect(Util.isDisabled(div)).toBeTrue()
|
||||
expect(Util.isDisabled(div1)).toBeTrue()
|
||||
expect(Util.isDisabled(div2)).toBeTrue()
|
||||
})
|
||||
|
||||
it('should return false if the element has disabled attribute with "false" value, or doesn\'t have attribute', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div>',
|
||||
' <div id="element" disabled="false"></div>',
|
||||
' <div id="element1" ></div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const div = fixtureEl.querySelector('#element')
|
||||
const div1 = fixtureEl.querySelector('#element1')
|
||||
|
||||
expect(Util.isDisabled(div)).toBeFalse()
|
||||
expect(Util.isDisabled(div1)).toBeFalse()
|
||||
})
|
||||
|
||||
it('should return false if the element is not disabled ', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div>',
|
||||
' <button id="button"></button>',
|
||||
' <select id="select"></select>',
|
||||
' <select id="input"></select>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const el = selector => fixtureEl.querySelector(selector)
|
||||
|
||||
expect(Util.isDisabled(el('#button'))).toBeFalse()
|
||||
expect(Util.isDisabled(el('#select'))).toBeFalse()
|
||||
expect(Util.isDisabled(el('#input'))).toBeFalse()
|
||||
})
|
||||
|
||||
it('should return true if the element has disabled attribute', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div>',
|
||||
' <input id="input" disabled="disabled">',
|
||||
' <input id="input1" disabled="disabled">',
|
||||
' <button id="button" disabled="true"></button>',
|
||||
' <button id="button1" disabled="disabled"></button>',
|
||||
' <button id="button2" disabled></button>',
|
||||
' <select id="select" disabled></select>',
|
||||
' <select id="input" disabled></select>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const el = selector => fixtureEl.querySelector(selector)
|
||||
|
||||
expect(Util.isDisabled(el('#input'))).toBeTrue()
|
||||
expect(Util.isDisabled(el('#input1'))).toBeTrue()
|
||||
expect(Util.isDisabled(el('#button'))).toBeTrue()
|
||||
expect(Util.isDisabled(el('#button1'))).toBeTrue()
|
||||
expect(Util.isDisabled(el('#button2'))).toBeTrue()
|
||||
expect(Util.isDisabled(el('#input'))).toBeTrue()
|
||||
})
|
||||
|
||||
it('should return true if the element has class "disabled"', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div>',
|
||||
' <div id="element" class="disabled"></div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const div = fixtureEl.querySelector('#element')
|
||||
|
||||
expect(Util.isDisabled(div)).toBeTrue()
|
||||
})
|
||||
|
||||
it('should return true if the element has class "disabled" but disabled attribute is false', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div>',
|
||||
' <input id="input" class="disabled" disabled="false">',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const div = fixtureEl.querySelector('#input')
|
||||
|
||||
expect(Util.isDisabled(div)).toBeTrue()
|
||||
})
|
||||
})
|
||||
|
||||
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)).toBeNull()
|
||||
})
|
||||
|
||||
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)).toBeNull()
|
||||
})
|
||||
|
||||
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 be a function', () => {
|
||||
expect(Util.noop).toEqual(jasmine.any(Function))
|
||||
})
|
||||
})
|
||||
|
||||
describe('reflow', () => {
|
||||
it('should return element offset height to force the reflow', () => {
|
||||
fixtureEl.innerHTML = '<div></div>'
|
||||
|
||||
const div = fixtureEl.querySelector('div')
|
||||
const spy = spyOnProperty(div, 'offsetHeight')
|
||||
Util.reflow(div)
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
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-bs-no-jquery', () => {
|
||||
document.body.setAttribute('data-bs-no-jquery', '')
|
||||
|
||||
expect(window.jQuery).toEqual(fakejQuery)
|
||||
expect(Util.getjQuery()).toBeNull()
|
||||
|
||||
document.body.removeAttribute('data-bs-no-jquery')
|
||||
})
|
||||
|
||||
it('should not return jQuery if not present', () => {
|
||||
window.jQuery = undefined
|
||||
expect(Util.getjQuery()).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('onDOMContentLoaded', () => {
|
||||
it('should execute callbacks when DOMContentLoaded is fired and should not add more than one listener', () => {
|
||||
const spy = jasmine.createSpy()
|
||||
const spy2 = jasmine.createSpy()
|
||||
|
||||
const spyAdd = spyOn(document, 'addEventListener').and.callThrough()
|
||||
spyOnProperty(document, 'readyState').and.returnValue('loading')
|
||||
|
||||
Util.onDOMContentLoaded(spy)
|
||||
Util.onDOMContentLoaded(spy2)
|
||||
|
||||
document.dispatchEvent(new Event('DOMContentLoaded', {
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
}))
|
||||
|
||||
expect(spy).toHaveBeenCalled()
|
||||
expect(spy2).toHaveBeenCalled()
|
||||
expect(spyAdd).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should execute callback if readyState is not "loading"', () => {
|
||||
const spy = jasmine.createSpy()
|
||||
Util.onDOMContentLoaded(spy)
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('defineJQueryPlugin', () => {
|
||||
const fakejQuery = { fn: {} }
|
||||
|
||||
beforeEach(() => {
|
||||
Object.defineProperty(window, 'jQuery', {
|
||||
value: fakejQuery,
|
||||
writable: true
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
window.jQuery = undefined
|
||||
})
|
||||
|
||||
it('should define a plugin on the jQuery instance', () => {
|
||||
const pluginMock = Util.noop
|
||||
pluginMock.NAME = 'test'
|
||||
pluginMock.jQueryInterface = Util.noop
|
||||
|
||||
Util.defineJQueryPlugin(pluginMock)
|
||||
expect(fakejQuery.fn.test).toEqual(pluginMock.jQueryInterface)
|
||||
expect(fakejQuery.fn.test.Constructor).toEqual(pluginMock)
|
||||
expect(fakejQuery.fn.test.noConflict).toEqual(jasmine.any(Function))
|
||||
})
|
||||
})
|
||||
|
||||
describe('execute', () => {
|
||||
it('should execute if arg is function', () => {
|
||||
const spy = jasmine.createSpy('spy')
|
||||
Util.execute(spy)
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('executeAfterTransition', () => {
|
||||
it('should immediately execute a function when waitForTransition parameter is false', () => {
|
||||
const el = document.createElement('div')
|
||||
const callbackSpy = jasmine.createSpy('callback spy')
|
||||
const eventListenerSpy = spyOn(el, 'addEventListener')
|
||||
|
||||
Util.executeAfterTransition(callbackSpy, el, false)
|
||||
|
||||
expect(callbackSpy).toHaveBeenCalled()
|
||||
expect(eventListenerSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should execute a function when a transitionend event is dispatched', () => {
|
||||
const el = document.createElement('div')
|
||||
const callbackSpy = jasmine.createSpy('callback spy')
|
||||
|
||||
spyOn(window, 'getComputedStyle').and.returnValue({
|
||||
transitionDuration: '0.05s',
|
||||
transitionDelay: '0s'
|
||||
})
|
||||
|
||||
Util.executeAfterTransition(callbackSpy, el)
|
||||
|
||||
el.dispatchEvent(new TransitionEvent('transitionend'))
|
||||
|
||||
expect(callbackSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should execute a function after a computed CSS transition duration and there was no transitionend event dispatched', () => {
|
||||
return new Promise(resolve => {
|
||||
const el = document.createElement('div')
|
||||
const callbackSpy = jasmine.createSpy('callback spy')
|
||||
|
||||
spyOn(window, 'getComputedStyle').and.returnValue({
|
||||
transitionDuration: '0.05s',
|
||||
transitionDelay: '0s'
|
||||
})
|
||||
|
||||
Util.executeAfterTransition(callbackSpy, el)
|
||||
|
||||
setTimeout(() => {
|
||||
expect(callbackSpy).toHaveBeenCalled()
|
||||
resolve()
|
||||
}, 70)
|
||||
})
|
||||
})
|
||||
|
||||
it('should not execute a function a second time after a computed CSS transition duration and if a transitionend event has already been dispatched', () => {
|
||||
return new Promise(resolve => {
|
||||
const el = document.createElement('div')
|
||||
const callbackSpy = jasmine.createSpy('callback spy')
|
||||
|
||||
spyOn(window, 'getComputedStyle').and.returnValue({
|
||||
transitionDuration: '0.05s',
|
||||
transitionDelay: '0s'
|
||||
})
|
||||
|
||||
Util.executeAfterTransition(callbackSpy, el)
|
||||
|
||||
setTimeout(() => {
|
||||
el.dispatchEvent(new TransitionEvent('transitionend'))
|
||||
}, 50)
|
||||
|
||||
setTimeout(() => {
|
||||
expect(callbackSpy).toHaveBeenCalledTimes(1)
|
||||
resolve()
|
||||
}, 70)
|
||||
})
|
||||
})
|
||||
|
||||
it('should not trigger a transitionend event if another transitionend event had already happened', () => {
|
||||
return new Promise(resolve => {
|
||||
const el = document.createElement('div')
|
||||
|
||||
spyOn(window, 'getComputedStyle').and.returnValue({
|
||||
transitionDuration: '0.05s',
|
||||
transitionDelay: '0s'
|
||||
})
|
||||
|
||||
Util.executeAfterTransition(noop, el)
|
||||
|
||||
// simulate a event dispatched by the browser
|
||||
el.dispatchEvent(new TransitionEvent('transitionend'))
|
||||
|
||||
const dispatchSpy = spyOn(el, 'dispatchEvent').and.callThrough()
|
||||
|
||||
setTimeout(() => {
|
||||
// setTimeout should not have triggered another transitionend event.
|
||||
expect(dispatchSpy).not.toHaveBeenCalled()
|
||||
resolve()
|
||||
}, 70)
|
||||
})
|
||||
})
|
||||
|
||||
it('should ignore transitionend events from nested elements', () => {
|
||||
return new Promise(resolve => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div class="outer">',
|
||||
' <div class="nested"></div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const outer = fixtureEl.querySelector('.outer')
|
||||
const nested = fixtureEl.querySelector('.nested')
|
||||
const callbackSpy = jasmine.createSpy('callback spy')
|
||||
|
||||
spyOn(window, 'getComputedStyle').and.returnValue({
|
||||
transitionDuration: '0.05s',
|
||||
transitionDelay: '0s'
|
||||
})
|
||||
|
||||
Util.executeAfterTransition(callbackSpy, outer)
|
||||
|
||||
nested.dispatchEvent(new TransitionEvent('transitionend', {
|
||||
bubbles: true
|
||||
}))
|
||||
|
||||
setTimeout(() => {
|
||||
expect(callbackSpy).not.toHaveBeenCalled()
|
||||
}, 20)
|
||||
|
||||
setTimeout(() => {
|
||||
expect(callbackSpy).toHaveBeenCalled()
|
||||
resolve()
|
||||
}, 70)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getNextActiveElement', () => {
|
||||
it('should return first element if active not exists or not given and shouldGetNext is either true, or false with cycling being disabled', () => {
|
||||
const array = ['a', 'b', 'c', 'd']
|
||||
|
||||
expect(Util.getNextActiveElement(array, '', true, true)).toEqual('a')
|
||||
expect(Util.getNextActiveElement(array, 'g', true, true)).toEqual('a')
|
||||
expect(Util.getNextActiveElement(array, '', true, false)).toEqual('a')
|
||||
expect(Util.getNextActiveElement(array, 'g', true, false)).toEqual('a')
|
||||
expect(Util.getNextActiveElement(array, '', false, false)).toEqual('a')
|
||||
expect(Util.getNextActiveElement(array, 'g', false, false)).toEqual('a')
|
||||
})
|
||||
|
||||
it('should return last element if active not exists or not given and shouldGetNext is false but cycling is enabled', () => {
|
||||
const array = ['a', 'b', 'c', 'd']
|
||||
|
||||
expect(Util.getNextActiveElement(array, '', false, true)).toEqual('d')
|
||||
expect(Util.getNextActiveElement(array, 'g', false, true)).toEqual('d')
|
||||
})
|
||||
|
||||
it('should return next element or same if is last', () => {
|
||||
const array = ['a', 'b', 'c', 'd']
|
||||
|
||||
expect(Util.getNextActiveElement(array, 'a', true, true)).toEqual('b')
|
||||
expect(Util.getNextActiveElement(array, 'b', true, true)).toEqual('c')
|
||||
expect(Util.getNextActiveElement(array, 'd', true, false)).toEqual('d')
|
||||
})
|
||||
|
||||
it('should return next element or first, if is last and "isCycleAllowed = true"', () => {
|
||||
const array = ['a', 'b', 'c', 'd']
|
||||
|
||||
expect(Util.getNextActiveElement(array, 'c', true, true)).toEqual('d')
|
||||
expect(Util.getNextActiveElement(array, 'd', true, true)).toEqual('a')
|
||||
})
|
||||
|
||||
it('should return previous element or same if is first', () => {
|
||||
const array = ['a', 'b', 'c', 'd']
|
||||
|
||||
expect(Util.getNextActiveElement(array, 'b', false, true)).toEqual('a')
|
||||
expect(Util.getNextActiveElement(array, 'd', false, true)).toEqual('c')
|
||||
expect(Util.getNextActiveElement(array, 'a', false, false)).toEqual('a')
|
||||
})
|
||||
|
||||
it('should return next element or first, if is last and "isCycleAllowed = true"', () => {
|
||||
const array = ['a', 'b', 'c', 'd']
|
||||
|
||||
expect(Util.getNextActiveElement(array, 'd', false, true)).toEqual('c')
|
||||
expect(Util.getNextActiveElement(array, 'a', false, true)).toEqual('d')
|
||||
})
|
||||
})
|
||||
})
|
105
js/tests/unit/util/sanitizer.spec.js
Normal file
105
js/tests/unit/util/sanitizer.spec.js
Normal file
|
@ -0,0 +1,105 @@
|
|||
import { DefaultAllowlist, sanitizeHtml } from '../../../src/util/sanitizer'
|
||||
|
||||
describe('Sanitizer', () => {
|
||||
describe('sanitizeHtml', () => {
|
||||
it('should return the same on empty string', () => {
|
||||
const empty = ''
|
||||
|
||||
const result = sanitizeHtml(empty, DefaultAllowlist, 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, DefaultAllowlist, null)
|
||||
|
||||
expect(result).not.toContain('href="javascript:alert(7)')
|
||||
})
|
||||
|
||||
it('should sanitize template and work with multiple regex', () => {
|
||||
const template = [
|
||||
'<div>',
|
||||
' <a href="javascript:alert(7)" aria-label="This is a link" data-foo="bar">Click me</a>',
|
||||
' <span>Some content</span>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const myDefaultAllowList = DefaultAllowlist
|
||||
// With the default allow list
|
||||
let result = sanitizeHtml(template, myDefaultAllowList, null)
|
||||
|
||||
// `data-foo` won't be present
|
||||
expect(result).not.toContain('data-foo="bar"')
|
||||
|
||||
// Add the following regex too
|
||||
myDefaultAllowList['*'].push(/^data-foo/)
|
||||
|
||||
result = sanitizeHtml(template, myDefaultAllowList, null)
|
||||
|
||||
expect(result).not.toContain('href="javascript:alert(7)') // This is in the default list
|
||||
expect(result).toContain('aria-label="This is a link"') // This is in the default list
|
||||
expect(result).toContain('data-foo="bar"') // We explicitly allow this
|
||||
})
|
||||
|
||||
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, DefaultAllowlist, null)
|
||||
|
||||
expect(result).toContain('aria-pressed')
|
||||
expect(result).toContain('class="test"')
|
||||
})
|
||||
|
||||
it('should remove tags not in allowlist', () => {
|
||||
const template = [
|
||||
'<div>',
|
||||
' <script>alert(7)</script>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const result = sanitizeHtml(template, DefaultAllowlist, null)
|
||||
|
||||
expect(result).not.toContain('<script>')
|
||||
})
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
const spy = spyOn(DOMParser.prototype, 'parseFromString')
|
||||
|
||||
const result = sanitizeHtml(template, DefaultAllowlist, mySanitize)
|
||||
|
||||
expect(result).toEqual(template)
|
||||
expect(spy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should allow multiple sanitation passes of the same template', () => {
|
||||
const template = '<img src="test.jpg">'
|
||||
|
||||
const firstResult = sanitizeHtml(template, DefaultAllowlist, null)
|
||||
const secondResult = sanitizeHtml(template, DefaultAllowlist, null)
|
||||
|
||||
expect(firstResult).toContain('src')
|
||||
expect(secondResult).toContain('src')
|
||||
})
|
||||
})
|
||||
})
|
363
js/tests/unit/util/scrollbar.spec.js
Normal file
363
js/tests/unit/util/scrollbar.spec.js
Normal file
|
@ -0,0 +1,363 @@
|
|||
import { clearBodyAndDocument, clearFixture, getFixture } from '../../helpers/fixture'
|
||||
import Manipulator from '../../../src/dom/manipulator'
|
||||
import ScrollBarHelper from '../../../src/util/scrollbar'
|
||||
|
||||
describe('ScrollBar', () => {
|
||||
let fixtureEl
|
||||
const doc = document.documentElement
|
||||
const parseIntDecimal = arg => Number.parseInt(arg, 10)
|
||||
const getPaddingX = el => parseIntDecimal(window.getComputedStyle(el).paddingRight)
|
||||
const getMarginX = el => parseIntDecimal(window.getComputedStyle(el).marginRight)
|
||||
const getOverFlow = el => el.style.overflow
|
||||
const getPaddingAttr = el => Manipulator.getDataAttribute(el, 'padding-right')
|
||||
const getMarginAttr = el => Manipulator.getDataAttribute(el, 'margin-right')
|
||||
const getOverFlowAttr = el => Manipulator.getDataAttribute(el, 'overflow')
|
||||
const windowCalculations = () => {
|
||||
return {
|
||||
htmlClient: document.documentElement.clientWidth,
|
||||
htmlOffset: document.documentElement.offsetWidth,
|
||||
docClient: document.body.clientWidth,
|
||||
htmlBound: document.documentElement.getBoundingClientRect().width,
|
||||
bodyBound: document.body.getBoundingClientRect().width,
|
||||
window: window.innerWidth,
|
||||
width: Math.abs(window.innerWidth - document.documentElement.clientWidth)
|
||||
}
|
||||
}
|
||||
|
||||
// iOS, Android devices and macOS browsers hide scrollbar by default and show it only while scrolling.
|
||||
// So the tests for scrollbar would fail
|
||||
const isScrollBarHidden = () => {
|
||||
const calc = windowCalculations()
|
||||
return calc.htmlClient === calc.htmlOffset && calc.htmlClient === calc.window
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
fixtureEl = getFixture()
|
||||
// custom fixture to avoid extreme style values
|
||||
fixtureEl.removeAttribute('style')
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
fixtureEl.remove()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
clearFixture()
|
||||
clearBodyAndDocument()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
clearBodyAndDocument()
|
||||
})
|
||||
|
||||
describe('isBodyOverflowing', () => {
|
||||
it('should return true if body is overflowing', () => {
|
||||
document.documentElement.style.overflowY = 'scroll'
|
||||
document.body.style.overflowY = 'scroll'
|
||||
fixtureEl.innerHTML = '<div style="height: 110vh; width: 100%"></div>'
|
||||
const result = new ScrollBarHelper().isOverflowing()
|
||||
|
||||
if (isScrollBarHidden()) {
|
||||
expect(result).toBeFalse()
|
||||
} else {
|
||||
expect(result).toBeTrue()
|
||||
}
|
||||
})
|
||||
|
||||
it('should return false if body is not overflowing', () => {
|
||||
doc.style.overflowY = 'hidden'
|
||||
document.body.style.overflowY = 'hidden'
|
||||
fixtureEl.innerHTML = '<div style="height: 110vh; width: 100%"></div>'
|
||||
const scrollBar = new ScrollBarHelper()
|
||||
const result = scrollBar.isOverflowing()
|
||||
|
||||
expect(result).toBeFalse()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getWidth', () => {
|
||||
it('should return an integer greater than zero, if body is overflowing', () => {
|
||||
doc.style.overflowY = 'scroll'
|
||||
document.body.style.overflowY = 'scroll'
|
||||
fixtureEl.innerHTML = '<div style="height: 110vh; width: 100%"></div>'
|
||||
const result = new ScrollBarHelper().getWidth()
|
||||
|
||||
if (isScrollBarHidden()) {
|
||||
expect(result).toEqual(0)
|
||||
} else {
|
||||
expect(result).toBeGreaterThan(1)
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 0 if body is not overflowing', () => {
|
||||
document.documentElement.style.overflowY = 'hidden'
|
||||
document.body.style.overflowY = 'hidden'
|
||||
fixtureEl.innerHTML = '<div style="height: 110vh; width: 100%"></div>'
|
||||
|
||||
const result = new ScrollBarHelper().getWidth()
|
||||
|
||||
expect(result).toEqual(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('hide - reset', () => {
|
||||
it('should adjust the inline padding of fixed elements which are full-width', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div style="height: 110vh; width: 100%">',
|
||||
' <div class="fixed-top" id="fixed1" style="padding-right: 0px; width: 100vw"></div>',
|
||||
' <div class="fixed-top" id="fixed2" style="padding-right: 5px; width: 100vw"></div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
doc.style.overflowY = 'scroll'
|
||||
|
||||
const fixedEl = fixtureEl.querySelector('#fixed1')
|
||||
const fixedEl2 = fixtureEl.querySelector('#fixed2')
|
||||
const originalPadding = getPaddingX(fixedEl)
|
||||
const originalPadding2 = getPaddingX(fixedEl2)
|
||||
const scrollBar = new ScrollBarHelper()
|
||||
const expectedPadding = originalPadding + scrollBar.getWidth()
|
||||
const expectedPadding2 = originalPadding2 + scrollBar.getWidth()
|
||||
|
||||
scrollBar.hide()
|
||||
|
||||
let currentPadding = getPaddingX(fixedEl)
|
||||
let currentPadding2 = getPaddingX(fixedEl2)
|
||||
expect(getPaddingAttr(fixedEl)).toEqual(`${originalPadding}px`)
|
||||
expect(getPaddingAttr(fixedEl2)).toEqual(`${originalPadding2}px`)
|
||||
expect(currentPadding).toEqual(expectedPadding)
|
||||
expect(currentPadding2).toEqual(expectedPadding2)
|
||||
|
||||
scrollBar.reset()
|
||||
currentPadding = getPaddingX(fixedEl)
|
||||
currentPadding2 = getPaddingX(fixedEl2)
|
||||
expect(getPaddingAttr(fixedEl)).toBeNull()
|
||||
expect(getPaddingAttr(fixedEl2)).toBeNull()
|
||||
expect(currentPadding).toEqual(originalPadding)
|
||||
expect(currentPadding2).toEqual(originalPadding2)
|
||||
})
|
||||
|
||||
it('should remove padding & margin if not existed before adjustment', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div style="height: 110vh; width: 100%">',
|
||||
' <div class="fixed" id="fixed" style="width: 100vw;"></div>',
|
||||
' <div class="sticky-top" id="sticky" style=" width: 100vw;"></div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
doc.style.overflowY = 'scroll'
|
||||
|
||||
const fixedEl = fixtureEl.querySelector('#fixed')
|
||||
const stickyEl = fixtureEl.querySelector('#sticky')
|
||||
const scrollBar = new ScrollBarHelper()
|
||||
|
||||
scrollBar.hide()
|
||||
scrollBar.reset()
|
||||
|
||||
expect(fixedEl.getAttribute('style').includes('padding-right')).toBeFalse()
|
||||
expect(stickyEl.getAttribute('style').includes('margin-right')).toBeFalse()
|
||||
})
|
||||
|
||||
it('should adjust the inline margin and padding of sticky elements', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div style="height: 110vh">',
|
||||
' <div class="sticky-top" style="margin-right: 10px; padding-right: 20px; width: 100vw; height: 10px"></div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
doc.style.overflowY = 'scroll'
|
||||
|
||||
const stickyTopEl = fixtureEl.querySelector('.sticky-top')
|
||||
const originalMargin = getMarginX(stickyTopEl)
|
||||
const originalPadding = getPaddingX(stickyTopEl)
|
||||
const scrollBar = new ScrollBarHelper()
|
||||
const expectedMargin = originalMargin - scrollBar.getWidth()
|
||||
const expectedPadding = originalPadding + scrollBar.getWidth()
|
||||
scrollBar.hide()
|
||||
|
||||
expect(getMarginAttr(stickyTopEl)).toEqual(`${originalMargin}px`)
|
||||
expect(getMarginX(stickyTopEl)).toEqual(expectedMargin)
|
||||
expect(getPaddingAttr(stickyTopEl)).toEqual(`${originalPadding}px`)
|
||||
expect(getPaddingX(stickyTopEl)).toEqual(expectedPadding)
|
||||
|
||||
scrollBar.reset()
|
||||
expect(getMarginAttr(stickyTopEl)).toBeNull()
|
||||
expect(getMarginX(stickyTopEl)).toEqual(originalMargin)
|
||||
expect(getPaddingAttr(stickyTopEl)).toBeNull()
|
||||
expect(getPaddingX(stickyTopEl)).toEqual(originalPadding)
|
||||
})
|
||||
|
||||
it('should not adjust the inline margin and padding of sticky and fixed elements when element do not have full width', () => {
|
||||
fixtureEl.innerHTML = '<div class="sticky-top" style="margin-right: 0px; padding-right: 0px; width: 50vw"></div>'
|
||||
|
||||
const stickyTopEl = fixtureEl.querySelector('.sticky-top')
|
||||
const originalMargin = getMarginX(stickyTopEl)
|
||||
const originalPadding = getPaddingX(stickyTopEl)
|
||||
|
||||
const scrollBar = new ScrollBarHelper()
|
||||
scrollBar.hide()
|
||||
|
||||
const currentMargin = getMarginX(stickyTopEl)
|
||||
const currentPadding = getPaddingX(stickyTopEl)
|
||||
|
||||
expect(currentMargin).toEqual(originalMargin)
|
||||
expect(currentPadding).toEqual(originalPadding)
|
||||
|
||||
scrollBar.reset()
|
||||
})
|
||||
|
||||
it('should not put data-attribute if element doesn\'t have the proper style property, should just remove style property if element didn\'t had one', () => {
|
||||
fixtureEl.innerHTML = [
|
||||
'<div style="height: 110vh; width: 100%">',
|
||||
' <div class="sticky-top" id="sticky" style="width: 100vw"></div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
document.body.style.overflowY = 'scroll'
|
||||
const scrollBar = new ScrollBarHelper()
|
||||
|
||||
const hasPaddingAttr = el => el.hasAttribute('data-bs-padding-right')
|
||||
const hasMarginAttr = el => el.hasAttribute('data-bs-margin-right')
|
||||
const stickyEl = fixtureEl.querySelector('#sticky')
|
||||
const originalPadding = getPaddingX(stickyEl)
|
||||
const originalMargin = getMarginX(stickyEl)
|
||||
const scrollBarWidth = scrollBar.getWidth()
|
||||
|
||||
scrollBar.hide()
|
||||
|
||||
expect(getPaddingX(stickyEl)).toEqual(scrollBarWidth + originalPadding)
|
||||
const expectedMargin = scrollBarWidth + originalMargin
|
||||
expect(getMarginX(stickyEl)).toEqual(expectedMargin === 0 ? expectedMargin : -expectedMargin)
|
||||
expect(hasMarginAttr(stickyEl)).toBeFalse() // We do not have to keep css margin
|
||||
expect(hasPaddingAttr(stickyEl)).toBeFalse() // We do not have to keep css padding
|
||||
|
||||
scrollBar.reset()
|
||||
|
||||
expect(getPaddingX(stickyEl)).toEqual(originalPadding)
|
||||
expect(getPaddingX(stickyEl)).toEqual(originalPadding)
|
||||
})
|
||||
|
||||
describe('Body Handling', () => {
|
||||
it('should ignore other inline styles when trying to restore body defaults ', () => {
|
||||
document.body.style.color = 'red'
|
||||
|
||||
const scrollBar = new ScrollBarHelper()
|
||||
const scrollBarWidth = scrollBar.getWidth()
|
||||
scrollBar.hide()
|
||||
|
||||
expect(getPaddingX(document.body)).toEqual(scrollBarWidth)
|
||||
expect(document.body.style.color).toEqual('red')
|
||||
|
||||
scrollBar.reset()
|
||||
})
|
||||
|
||||
it('should hide scrollbar and reset it to its initial value', () => {
|
||||
const styleSheetPadding = '7px'
|
||||
fixtureEl.innerHTML = [
|
||||
'<style>',
|
||||
' body {',
|
||||
` padding-right: ${styleSheetPadding}`,
|
||||
' }',
|
||||
'</style>'
|
||||
].join('')
|
||||
|
||||
const el = document.body
|
||||
const inlineStylePadding = '10px'
|
||||
el.style.paddingRight = inlineStylePadding
|
||||
|
||||
const originalPadding = getPaddingX(el)
|
||||
expect(originalPadding).toEqual(parseIntDecimal(inlineStylePadding)) // Respect only the inline style as it has prevails this of css
|
||||
const originalOverFlow = 'auto'
|
||||
el.style.overflow = originalOverFlow
|
||||
const scrollBar = new ScrollBarHelper()
|
||||
const scrollBarWidth = scrollBar.getWidth()
|
||||
|
||||
scrollBar.hide()
|
||||
|
||||
const currentPadding = getPaddingX(el)
|
||||
|
||||
expect(currentPadding).toEqual(scrollBarWidth + originalPadding)
|
||||
expect(currentPadding).toEqual(scrollBarWidth + parseIntDecimal(inlineStylePadding))
|
||||
expect(getPaddingAttr(el)).toEqual(inlineStylePadding)
|
||||
expect(getOverFlow(el)).toEqual('hidden')
|
||||
expect(getOverFlowAttr(el)).toEqual(originalOverFlow)
|
||||
|
||||
scrollBar.reset()
|
||||
|
||||
const currentPadding1 = getPaddingX(el)
|
||||
expect(currentPadding1).toEqual(originalPadding)
|
||||
expect(getPaddingAttr(el)).toBeNull()
|
||||
expect(getOverFlow(el)).toEqual(originalOverFlow)
|
||||
expect(getOverFlowAttr(el)).toBeNull()
|
||||
})
|
||||
|
||||
it('should hide scrollbar and reset it to its initial value - respecting css rules', () => {
|
||||
const styleSheetPadding = '7px'
|
||||
fixtureEl.innerHTML = [
|
||||
'<style>',
|
||||
' body {',
|
||||
` padding-right: ${styleSheetPadding}`,
|
||||
' }',
|
||||
'</style>'
|
||||
].join('')
|
||||
const el = document.body
|
||||
const originalPadding = getPaddingX(el)
|
||||
const originalOverFlow = 'scroll'
|
||||
el.style.overflow = originalOverFlow
|
||||
const scrollBar = new ScrollBarHelper()
|
||||
const scrollBarWidth = scrollBar.getWidth()
|
||||
|
||||
scrollBar.hide()
|
||||
|
||||
const currentPadding = getPaddingX(el)
|
||||
|
||||
expect(currentPadding).toEqual(scrollBarWidth + originalPadding)
|
||||
expect(currentPadding).toEqual(scrollBarWidth + parseIntDecimal(styleSheetPadding))
|
||||
expect(getPaddingAttr(el)).toBeNull() // We do not have to keep css padding
|
||||
expect(getOverFlow(el)).toEqual('hidden')
|
||||
expect(getOverFlowAttr(el)).toEqual(originalOverFlow)
|
||||
|
||||
scrollBar.reset()
|
||||
|
||||
const currentPadding1 = getPaddingX(el)
|
||||
expect(currentPadding1).toEqual(originalPadding)
|
||||
expect(getPaddingAttr(el)).toBeNull()
|
||||
expect(getOverFlow(el)).toEqual(originalOverFlow)
|
||||
expect(getOverFlowAttr(el)).toBeNull()
|
||||
})
|
||||
|
||||
it('should not adjust the inline body padding when it does not overflow', () => {
|
||||
const originalPadding = getPaddingX(document.body)
|
||||
const scrollBar = new ScrollBarHelper()
|
||||
|
||||
// Hide scrollbars to prevent the body overflowing
|
||||
doc.style.overflowY = 'hidden'
|
||||
doc.style.paddingRight = '0px'
|
||||
|
||||
scrollBar.hide()
|
||||
const currentPadding = getPaddingX(document.body)
|
||||
|
||||
expect(currentPadding).toEqual(originalPadding)
|
||||
scrollBar.reset()
|
||||
})
|
||||
|
||||
it('should not adjust the inline body padding when it does not overflow, even on a scaled display', () => {
|
||||
const originalPadding = getPaddingX(document.body)
|
||||
const scrollBar = new ScrollBarHelper()
|
||||
// Remove body margins as would be done by Bootstrap css
|
||||
document.body.style.margin = '0'
|
||||
|
||||
// Hide scrollbars to prevent the body overflowing
|
||||
doc.style.overflowY = 'hidden'
|
||||
|
||||
// Simulate a discrepancy between exact, i.e. floating point body width, and rounded body width
|
||||
// as it can occur when zooming or scaling the display to something else than 100%
|
||||
doc.style.paddingRight = '.48px'
|
||||
scrollBar.hide()
|
||||
|
||||
const currentPadding = getPaddingX(document.body)
|
||||
|
||||
expect(currentPadding).toEqual(originalPadding)
|
||||
|
||||
scrollBar.reset()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
291
js/tests/unit/util/swipe.spec.js
Normal file
291
js/tests/unit/util/swipe.spec.js
Normal file
|
@ -0,0 +1,291 @@
|
|||
import { clearFixture, getFixture } from '../../helpers/fixture'
|
||||
import EventHandler from '../../../src/dom/event-handler'
|
||||
import Swipe from '../../../src/util/swipe'
|
||||
import { noop } from '../../../src/util'
|
||||
|
||||
describe('Swipe', () => {
|
||||
const { Simulator, PointerEvent } = window
|
||||
const originWinPointerEvent = PointerEvent
|
||||
const supportPointerEvent = Boolean(PointerEvent)
|
||||
|
||||
let fixtureEl
|
||||
let swipeEl
|
||||
const clearPointerEvents = () => {
|
||||
window.PointerEvent = null
|
||||
}
|
||||
|
||||
const restorePointerEvents = () => {
|
||||
window.PointerEvent = originWinPointerEvent
|
||||
}
|
||||
|
||||
// The headless browser does not support touch events, so we need to fake it
|
||||
// in order to test that touch events are added properly
|
||||
const defineDocumentElementOntouchstart = () => {
|
||||
document.documentElement.ontouchstart = noop
|
||||
}
|
||||
|
||||
const deleteDocumentElementOntouchstart = () => {
|
||||
delete document.documentElement.ontouchstart
|
||||
}
|
||||
|
||||
const mockSwipeGesture = (element, options = {}, type = 'touch') => {
|
||||
Simulator.setType(type)
|
||||
const _options = { deltaX: 0, deltaY: 0, ...options }
|
||||
|
||||
Simulator.gestures.swipe(element, _options)
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
fixtureEl = getFixture()
|
||||
const cssStyle = [
|
||||
'<style>',
|
||||
' #fixture .pointer-event {',
|
||||
' touch-action: pan-y;',
|
||||
' }',
|
||||
' #fixture div {',
|
||||
' width: 300px;',
|
||||
' height: 300px;',
|
||||
' }',
|
||||
'</style>'
|
||||
].join('')
|
||||
|
||||
fixtureEl.innerHTML = `<div id="swipeEl"></div>${cssStyle}`
|
||||
swipeEl = fixtureEl.querySelector('div')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
clearFixture()
|
||||
deleteDocumentElementOntouchstart()
|
||||
})
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should add touch event listeners by default', () => {
|
||||
defineDocumentElementOntouchstart()
|
||||
|
||||
spyOn(Swipe.prototype, '_initEvents').and.callThrough()
|
||||
const swipe = new Swipe(swipeEl)
|
||||
expect(swipe._initEvents).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not add touch event listeners if touch is not supported', () => {
|
||||
spyOn(Swipe, 'isSupported').and.returnValue(false)
|
||||
|
||||
spyOn(Swipe.prototype, '_initEvents').and.callThrough()
|
||||
const swipe = new Swipe(swipeEl)
|
||||
|
||||
expect(swipe._initEvents).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Config', () => {
|
||||
it('Test leftCallback', () => {
|
||||
return new Promise(resolve => {
|
||||
const spyRight = jasmine.createSpy('spy')
|
||||
clearPointerEvents()
|
||||
defineDocumentElementOntouchstart()
|
||||
// eslint-disable-next-line no-new
|
||||
new Swipe(swipeEl, {
|
||||
leftCallback() {
|
||||
expect(spyRight).not.toHaveBeenCalled()
|
||||
restorePointerEvents()
|
||||
resolve()
|
||||
},
|
||||
rightCallback: spyRight
|
||||
})
|
||||
|
||||
mockSwipeGesture(swipeEl, {
|
||||
pos: [300, 10],
|
||||
deltaX: -300
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('Test rightCallback', () => {
|
||||
return new Promise(resolve => {
|
||||
const spyLeft = jasmine.createSpy('spy')
|
||||
clearPointerEvents()
|
||||
defineDocumentElementOntouchstart()
|
||||
// eslint-disable-next-line no-new
|
||||
new Swipe(swipeEl, {
|
||||
rightCallback() {
|
||||
expect(spyLeft).not.toHaveBeenCalled()
|
||||
restorePointerEvents()
|
||||
resolve()
|
||||
},
|
||||
leftCallback: spyLeft
|
||||
})
|
||||
|
||||
mockSwipeGesture(swipeEl, {
|
||||
pos: [10, 10],
|
||||
deltaX: 300
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('Test endCallback', () => {
|
||||
return new Promise(resolve => {
|
||||
clearPointerEvents()
|
||||
defineDocumentElementOntouchstart()
|
||||
let isFirstTime = true
|
||||
|
||||
const callback = () => {
|
||||
if (isFirstTime) {
|
||||
isFirstTime = false
|
||||
return
|
||||
}
|
||||
|
||||
expect().nothing()
|
||||
restorePointerEvents()
|
||||
resolve()
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Swipe(swipeEl, {
|
||||
endCallback: callback
|
||||
})
|
||||
mockSwipeGesture(swipeEl, {
|
||||
pos: [10, 10],
|
||||
deltaX: 300
|
||||
})
|
||||
|
||||
mockSwipeGesture(swipeEl, {
|
||||
pos: [300, 10],
|
||||
deltaX: -300
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Functionality on PointerEvents', () => {
|
||||
it('should not allow pinch with touch events', () => {
|
||||
Simulator.setType('touch')
|
||||
clearPointerEvents()
|
||||
deleteDocumentElementOntouchstart()
|
||||
|
||||
const swipe = new Swipe(swipeEl)
|
||||
const spy = spyOn(swipe, '_handleSwipe')
|
||||
|
||||
mockSwipeGesture(swipeEl, {
|
||||
pos: [300, 10],
|
||||
deltaX: -300,
|
||||
deltaY: 0,
|
||||
touches: 2
|
||||
})
|
||||
|
||||
restorePointerEvents()
|
||||
expect(spy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should allow swipeRight and call "rightCallback" with pointer events', () => {
|
||||
return new Promise(resolve => {
|
||||
if (!supportPointerEvent) {
|
||||
expect().nothing()
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
|
||||
const style = '#fixture .pointer-event { touch-action: none !important; }'
|
||||
fixtureEl.innerHTML += style
|
||||
|
||||
defineDocumentElementOntouchstart()
|
||||
// eslint-disable-next-line no-new
|
||||
new Swipe(swipeEl, {
|
||||
rightCallback() {
|
||||
deleteDocumentElementOntouchstart()
|
||||
expect().nothing()
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
|
||||
mockSwipeGesture(swipeEl, { deltaX: 300 }, 'pointer')
|
||||
})
|
||||
})
|
||||
|
||||
it('should allow swipeLeft and call "leftCallback" with pointer events', () => {
|
||||
return new Promise(resolve => {
|
||||
if (!supportPointerEvent) {
|
||||
expect().nothing()
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
|
||||
const style = '#fixture .pointer-event { touch-action: none !important; }'
|
||||
fixtureEl.innerHTML += style
|
||||
|
||||
defineDocumentElementOntouchstart()
|
||||
// eslint-disable-next-line no-new
|
||||
new Swipe(swipeEl, {
|
||||
leftCallback() {
|
||||
expect().nothing()
|
||||
deleteDocumentElementOntouchstart()
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
|
||||
mockSwipeGesture(swipeEl, {
|
||||
pos: [300, 10],
|
||||
deltaX: -300
|
||||
}, 'pointer')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Dispose', () => {
|
||||
it('should call EventHandler.off', () => {
|
||||
defineDocumentElementOntouchstart()
|
||||
spyOn(EventHandler, 'off').and.callThrough()
|
||||
const swipe = new Swipe(swipeEl)
|
||||
|
||||
swipe.dispose()
|
||||
expect(EventHandler.off).toHaveBeenCalledWith(swipeEl, '.bs.swipe')
|
||||
})
|
||||
|
||||
it('should destroy', () => {
|
||||
const addEventSpy = spyOn(fixtureEl, 'addEventListener').and.callThrough()
|
||||
const removeEventSpy = spyOn(EventHandler, 'off').and.callThrough()
|
||||
defineDocumentElementOntouchstart()
|
||||
|
||||
const swipe = new Swipe(fixtureEl)
|
||||
|
||||
const expectedArgs =
|
||||
swipe._supportPointerEvents ?
|
||||
[
|
||||
['pointerdown', jasmine.any(Function), jasmine.any(Boolean)],
|
||||
['pointerup', jasmine.any(Function), jasmine.any(Boolean)]
|
||||
] :
|
||||
[
|
||||
['touchstart', jasmine.any(Function), jasmine.any(Boolean)],
|
||||
['touchmove', jasmine.any(Function), jasmine.any(Boolean)],
|
||||
['touchend', jasmine.any(Function), jasmine.any(Boolean)]
|
||||
]
|
||||
|
||||
expect(addEventSpy.calls.allArgs()).toEqual(expectedArgs)
|
||||
|
||||
swipe.dispose()
|
||||
|
||||
expect(removeEventSpy).toHaveBeenCalledWith(fixtureEl, '.bs.swipe')
|
||||
deleteDocumentElementOntouchstart()
|
||||
})
|
||||
})
|
||||
|
||||
describe('"isSupported" static', () => {
|
||||
it('should return "true" if "touchstart" exists in document element)', () => {
|
||||
Object.defineProperty(window.navigator, 'maxTouchPoints', () => 0)
|
||||
defineDocumentElementOntouchstart()
|
||||
|
||||
expect(Swipe.isSupported()).toBeTrue()
|
||||
})
|
||||
|
||||
it('should return "false" if "touchstart" not exists in document element and "navigator.maxTouchPoints" are zero (0)', () => {
|
||||
Object.defineProperty(window.navigator, 'maxTouchPoints', () => 0)
|
||||
deleteDocumentElementOntouchstart()
|
||||
|
||||
if ('ontouchstart' in document.documentElement) {
|
||||
expect().nothing()
|
||||
return
|
||||
}
|
||||
|
||||
expect(Swipe.isSupported()).toBeFalse()
|
||||
})
|
||||
})
|
||||
})
|
306
js/tests/unit/util/template-factory.spec.js
Normal file
306
js/tests/unit/util/template-factory.spec.js
Normal file
|
@ -0,0 +1,306 @@
|
|||
import { clearFixture, getFixture } from '../../helpers/fixture'
|
||||
import TemplateFactory from '../../../src/util/template-factory'
|
||||
|
||||
describe('TemplateFactory', () => {
|
||||
let fixtureEl
|
||||
|
||||
beforeAll(() => {
|
||||
fixtureEl = getFixture()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
clearFixture()
|
||||
})
|
||||
|
||||
describe('NAME', () => {
|
||||
it('should return plugin NAME', () => {
|
||||
expect(TemplateFactory.NAME).toEqual('TemplateFactory')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Default', () => {
|
||||
it('should return plugin default config', () => {
|
||||
expect(TemplateFactory.Default).toEqual(jasmine.any(Object))
|
||||
})
|
||||
})
|
||||
|
||||
describe('toHtml', () => {
|
||||
describe('Sanitization', () => {
|
||||
it('should use "sanitizeHtml" to sanitize template', () => {
|
||||
const factory = new TemplateFactory({
|
||||
sanitize: true,
|
||||
template: '<div><a href="javascript:alert(7)">Click me</a></div>'
|
||||
})
|
||||
const spy = spyOn(factory, '_maybeSanitize').and.callThrough()
|
||||
|
||||
expect(factory.toHtml().innerHTML).not.toContain('href="javascript:alert(7)')
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not sanitize template', () => {
|
||||
const factory = new TemplateFactory({
|
||||
sanitize: false,
|
||||
template: '<div><a href="javascript:alert(7)">Click me</a></div>'
|
||||
})
|
||||
const spy = spyOn(factory, '_maybeSanitize').and.callThrough()
|
||||
|
||||
expect(factory.toHtml().innerHTML).toContain('href="javascript:alert(7)')
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should use "sanitizeHtml" to sanitize content', () => {
|
||||
const factory = new TemplateFactory({
|
||||
sanitize: true,
|
||||
html: true,
|
||||
template: '<div id="foo"></div>',
|
||||
content: { '#foo': '<a href="javascript:alert(7)">Click me</a>' }
|
||||
})
|
||||
expect(factory.toHtml().innerHTML).not.toContain('href="javascript:alert(7)')
|
||||
})
|
||||
|
||||
it('should not sanitize content', () => {
|
||||
const factory = new TemplateFactory({
|
||||
sanitize: false,
|
||||
html: true,
|
||||
template: '<div id="foo"></div>',
|
||||
content: { '#foo': '<a href="javascript:alert(7)">Click me</a>' }
|
||||
})
|
||||
expect(factory.toHtml().innerHTML).toContain('href="javascript:alert(7)')
|
||||
})
|
||||
|
||||
it('should sanitize content only if "config.html" is enabled', () => {
|
||||
const factory = new TemplateFactory({
|
||||
sanitize: true,
|
||||
html: false,
|
||||
template: '<div id="foo"></div>',
|
||||
content: { '#foo': '<a href="javascript:alert(7)">Click me</a>' }
|
||||
})
|
||||
const spy = spyOn(factory, '_maybeSanitize').and.callThrough()
|
||||
|
||||
expect(spy).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Extra Class', () => {
|
||||
it('should add extra class', () => {
|
||||
const factory = new TemplateFactory({
|
||||
extraClass: 'testClass'
|
||||
})
|
||||
expect(factory.toHtml()).toHaveClass('testClass')
|
||||
})
|
||||
|
||||
it('should add extra classes', () => {
|
||||
const factory = new TemplateFactory({
|
||||
extraClass: 'testClass testClass2'
|
||||
})
|
||||
expect(factory.toHtml()).toHaveClass('testClass')
|
||||
expect(factory.toHtml()).toHaveClass('testClass2')
|
||||
})
|
||||
|
||||
it('should resolve class if function is given', () => {
|
||||
const factory = new TemplateFactory({
|
||||
extraClass(arg) {
|
||||
expect(arg).toEqual(factory)
|
||||
return 'testClass'
|
||||
}
|
||||
})
|
||||
|
||||
expect(factory.toHtml()).toHaveClass('testClass')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Content', () => {
|
||||
it('add simple text content', () => {
|
||||
const template = [
|
||||
'<div>',
|
||||
' <div class="foo"></div>',
|
||||
' <div class="foo2"></div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const factory = new TemplateFactory({
|
||||
template,
|
||||
content: {
|
||||
'.foo': 'bar',
|
||||
'.foo2': 'bar2'
|
||||
}
|
||||
})
|
||||
|
||||
const html = factory.toHtml()
|
||||
expect(html.querySelector('.foo').textContent).toEqual('bar')
|
||||
expect(html.querySelector('.foo2').textContent).toEqual('bar2')
|
||||
})
|
||||
|
||||
it('should not fill template if selector not exists', () => {
|
||||
const factory = new TemplateFactory({
|
||||
sanitize: true,
|
||||
html: true,
|
||||
template: '<div id="foo"></div>',
|
||||
content: { '#bar': 'test' }
|
||||
})
|
||||
|
||||
expect(factory.toHtml().outerHTML).toEqual('<div id="foo"></div>')
|
||||
})
|
||||
|
||||
it('should remove template selector, if content is null', () => {
|
||||
const factory = new TemplateFactory({
|
||||
sanitize: true,
|
||||
html: true,
|
||||
template: '<div><div id="foo"></div></div>',
|
||||
content: { '#foo': null }
|
||||
})
|
||||
|
||||
expect(factory.toHtml().outerHTML).toEqual('<div></div>')
|
||||
})
|
||||
|
||||
it('should resolve content if is function', () => {
|
||||
const factory = new TemplateFactory({
|
||||
sanitize: true,
|
||||
html: true,
|
||||
template: '<div><div id="foo"></div></div>',
|
||||
content: { '#foo': () => null }
|
||||
})
|
||||
|
||||
expect(factory.toHtml().outerHTML).toEqual('<div></div>')
|
||||
})
|
||||
|
||||
it('if content is element and "config.html=false", should put content\'s textContent', () => {
|
||||
fixtureEl.innerHTML = '<div>foo<span>bar</span></div>'
|
||||
const contentElement = fixtureEl.querySelector('div')
|
||||
|
||||
const factory = new TemplateFactory({
|
||||
html: false,
|
||||
template: '<div><div id="foo"></div></div>',
|
||||
content: { '#foo': contentElement }
|
||||
})
|
||||
|
||||
const fooEl = factory.toHtml().querySelector('#foo')
|
||||
expect(fooEl.innerHTML).not.toEqual(contentElement.innerHTML)
|
||||
expect(fooEl.textContent).toEqual(contentElement.textContent)
|
||||
expect(fooEl.textContent).toEqual('foobar')
|
||||
})
|
||||
|
||||
it('if content is element and "config.html=true", should put content\'s outerHtml as child', () => {
|
||||
fixtureEl.innerHTML = '<div>foo<span>bar</span></div>'
|
||||
const contentElement = fixtureEl.querySelector('div')
|
||||
|
||||
const factory = new TemplateFactory({
|
||||
html: true,
|
||||
template: '<div><div id="foo"></div></div>',
|
||||
content: { '#foo': contentElement }
|
||||
})
|
||||
|
||||
const fooEl = factory.toHtml().querySelector('#foo')
|
||||
expect(fooEl.innerHTML).toEqual(contentElement.outerHTML)
|
||||
expect(fooEl.textContent).toEqual(contentElement.textContent)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getContent', () => {
|
||||
it('should get content as array', () => {
|
||||
const factory = new TemplateFactory({
|
||||
content: {
|
||||
'.foo': 'bar',
|
||||
'.foo2': 'bar2'
|
||||
}
|
||||
})
|
||||
expect(factory.getContent()).toEqual(['bar', 'bar2'])
|
||||
})
|
||||
|
||||
it('should filter empties', () => {
|
||||
const factory = new TemplateFactory({
|
||||
content: {
|
||||
'.foo': 'bar',
|
||||
'.foo2': '',
|
||||
'.foo3': null,
|
||||
'.foo4': () => 2,
|
||||
'.foo5': () => null
|
||||
}
|
||||
})
|
||||
expect(factory.getContent()).toEqual(['bar', 2])
|
||||
})
|
||||
})
|
||||
|
||||
describe('hasContent', () => {
|
||||
it('should return true, if it has', () => {
|
||||
const factory = new TemplateFactory({
|
||||
content: {
|
||||
'.foo': 'bar',
|
||||
'.foo2': 'bar2',
|
||||
'.foo3': ''
|
||||
}
|
||||
})
|
||||
expect(factory.hasContent()).toBeTrue()
|
||||
})
|
||||
|
||||
it('should return false, if filtered content is empty', () => {
|
||||
const factory = new TemplateFactory({
|
||||
content: {
|
||||
'.foo2': '',
|
||||
'.foo3': null,
|
||||
'.foo4': () => null
|
||||
}
|
||||
})
|
||||
expect(factory.hasContent()).toBeFalse()
|
||||
})
|
||||
})
|
||||
|
||||
describe('changeContent', () => {
|
||||
it('should change Content', () => {
|
||||
const template = [
|
||||
'<div>',
|
||||
' <div class="foo"></div>',
|
||||
' <div class="foo2"></div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const factory = new TemplateFactory({
|
||||
template,
|
||||
content: {
|
||||
'.foo': 'bar',
|
||||
'.foo2': 'bar2'
|
||||
}
|
||||
})
|
||||
|
||||
const html = selector => factory.toHtml().querySelector(selector).textContent
|
||||
expect(html('.foo')).toEqual('bar')
|
||||
expect(html('.foo2')).toEqual('bar2')
|
||||
factory.changeContent({
|
||||
'.foo': 'test',
|
||||
'.foo2': 'test2'
|
||||
})
|
||||
|
||||
expect(html('.foo')).toEqual('test')
|
||||
expect(html('.foo2')).toEqual('test2')
|
||||
})
|
||||
|
||||
it('should change only the given, content', () => {
|
||||
const template = [
|
||||
'<div>',
|
||||
' <div class="foo"></div>',
|
||||
' <div class="foo2"></div>',
|
||||
'</div>'
|
||||
].join('')
|
||||
|
||||
const factory = new TemplateFactory({
|
||||
template,
|
||||
content: {
|
||||
'.foo': 'bar',
|
||||
'.foo2': 'bar2'
|
||||
}
|
||||
})
|
||||
|
||||
const html = selector => factory.toHtml().querySelector(selector).textContent
|
||||
expect(html('.foo')).toEqual('bar')
|
||||
expect(html('.foo2')).toEqual('bar2')
|
||||
factory.changeContent({
|
||||
'.foo': 'test',
|
||||
'.wrong': 'wrong'
|
||||
})
|
||||
|
||||
expect(html('.foo')).toEqual('test')
|
||||
expect(html('.foo2')).toEqual('bar2')
|
||||
})
|
||||
})
|
||||
})
|
Loading…
Add table
Add a link
Reference in a new issue