'
+ ].join('')
+
+ const modalEl = fixtureEl.querySelector('.modal')
+ const trigger = fixtureEl.querySelector('[data-toggle="modal"]')
+
+ spyOn(trigger, 'focus')
+
+ const showListener = () => {
+ setTimeout(() => {
+ expect(trigger.focus).not.toHaveBeenCalled()
+ done()
+ }, 10)
+ }
+
+ modalEl.addEventListener('show.bs.modal', e => {
+ e.preventDefault()
+ showListener()
+ })
+
+ trigger.click()
+ })
+ })
+
+ describe('jQueryInterface', () => {
+ it('should create a modal', () => {
+ fixtureEl.innerHTML = '
'
+
+ const div = fixtureEl.querySelector('div')
+
+ jQueryMock.fn.modal = Modal.jQueryInterface
+ jQueryMock.elements = [div]
+
+ jQueryMock.fn.modal.call(jQueryMock)
+
+ expect(Modal.getInstance(div)).toBeDefined()
+ })
+
+ it('should not re create a modal', () => {
+ fixtureEl.innerHTML = '
'
+
+ const div = fixtureEl.querySelector('div')
+ const modal = new Modal(div)
+
+ jQueryMock.fn.modal = Modal.jQueryInterface
+ jQueryMock.elements = [div]
+
+ jQueryMock.fn.modal.call(jQueryMock)
+
+ expect(Modal.getInstance(div)).toEqual(modal)
+ })
+
+ it('should throw error on undefined method', () => {
+ fixtureEl.innerHTML = '
'
+
+ const div = fixtureEl.querySelector('div')
+ const action = 'undefinedMethod'
+
+ jQueryMock.fn.modal = Modal.jQueryInterface
+ jQueryMock.elements = [div]
+
+ try {
+ jQueryMock.fn.modal.call(jQueryMock, action)
+ } catch (error) {
+ expect(error.message).toEqual(`No method named "${action}"`)
+ }
+ })
+
+ it('should should call show method', () => {
+ fixtureEl.innerHTML = '
'
+
+ const div = fixtureEl.querySelector('div')
+ const modal = new Modal(div)
+
+ jQueryMock.fn.modal = Modal.jQueryInterface
+ jQueryMock.elements = [div]
+
+ spyOn(modal, 'show')
+
+ jQueryMock.fn.modal.call(jQueryMock, 'show')
+
+ expect(modal.show).toHaveBeenCalled()
+ })
+
+ it('should should not call show method', () => {
+ fixtureEl.innerHTML = '
'
+
+ const div = fixtureEl.querySelector('div')
+
+ jQueryMock.fn.modal = Modal.jQueryInterface
+ jQueryMock.elements = [div]
+
+ spyOn(Modal.prototype, 'show')
+
+ jQueryMock.fn.modal.call(jQueryMock)
+
+ expect(Modal.prototype.show).not.toHaveBeenCalled()
+ })
+ })
+
+ describe('getInstance', () => {
+ it('should return modal instance', () => {
+ fixtureEl.innerHTML = '
'
+
+ const div = fixtureEl.querySelector('div')
+ const modal = new Modal(div)
+
+ expect(Modal.getInstance(div)).toEqual(modal)
+ })
+
+ it('should return null when there is no modal instance', () => {
+ fixtureEl.innerHTML = '
'
+
+ const div = fixtureEl.querySelector('div')
+
+ expect(Modal.getInstance(div)).toEqual(null)
+ })
+ })
+})
diff --git a/js/tests/unit/popover.js b/js/tests/unit/popover.js
deleted file mode 100644
index f4b04ded57cb..000000000000
--- a/js/tests/unit/popover.js
+++ /dev/null
@@ -1,471 +0,0 @@
-$(function () {
- 'use strict'
-
- QUnit.module('popover plugin')
-
- QUnit.test('should be defined on jquery object', function (assert) {
- assert.expect(1)
- assert.ok($(document.body).popover, 'popover method is defined')
- })
-
- QUnit.module('popover', {
- beforeEach: function () {
- // Run all tests in noConflict mode -- it's the only way to ensure that the plugin works in noConflict mode
- $.fn.bootstrapPopover = $.fn.popover.noConflict()
- },
- afterEach: function () {
- $.fn.popover = $.fn.bootstrapPopover
- delete $.fn.bootstrapPopover
- $('.popover').remove()
- $('#qunit-fixture').html('')
- }
- })
-
- QUnit.test('should provide no conflict', function (assert) {
- assert.expect(1)
- assert.strictEqual(typeof $.fn.popover, 'undefined', 'popover was set back to undefined (org value)')
- })
-
- QUnit.test('should throw explicit error on undefined method', function (assert) {
- assert.expect(1)
- var $el = $('
')
- $el.bootstrapPopover()
- try {
- $el.bootstrapPopover('noMethod')
- } catch (err) {
- assert.strictEqual(err.message, 'No method named "noMethod"')
- }
- })
-
- QUnit.test('should return jquery collection containing the element', function (assert) {
- assert.expect(2)
- var $el = $('
')
- var $popover = $el.bootstrapPopover()
- assert.ok($popover instanceof $, 'returns jquery collection')
- assert.strictEqual($popover[0], $el[0], 'collection contains element')
- })
-
- QUnit.test('should render popover element', function (assert) {
- assert.expect(2)
- var done = assert.async()
- $('
@mdo')
- .appendTo('#qunit-fixture')
- .on('shown.bs.popover', function () {
- assert.notEqual($('.popover').length, 0, 'popover was inserted')
- $(this).bootstrapPopover('hide')
- })
- .on('hidden.bs.popover', function () {
- assert.strictEqual($('.popover').length, 0, 'popover removed')
- done()
- })
- .bootstrapPopover('show')
- })
-
- QUnit.test('should store popover instance in popover data object', function (assert) {
- assert.expect(1)
- var $popover = $('
@mdo').bootstrapPopover()
-
- assert.ok($popover.data('bs.popover'), 'popover instance exists')
- })
-
- QUnit.test('should store popover trigger in popover instance data object', function (assert) {
- assert.expect(1)
- var $popover = $('
@ResentedHook')
- .appendTo('#qunit-fixture')
- .bootstrapPopover()
-
- $popover.bootstrapPopover('show')
-
- assert.ok($('.popover').data('bs.popover'), 'popover trigger stored in instance data')
- })
-
- QUnit.test('should get title and content from options', function (assert) {
- assert.expect(4)
- var done = assert.async()
- var $popover = $('
@fat')
- .appendTo('#qunit-fixture')
- .bootstrapPopover({
- title: function () {
- return '@fat'
- },
- content: function () {
- return 'loves writing tests ๏ผโฏยฐโกยฐ๏ผโฏ๏ธต โปโโป'
- }
- })
-
- $popover
- .one('shown.bs.popover', function () {
- assert.notEqual($('.popover').length, 0, 'popover was inserted')
- assert.strictEqual($('.popover .popover-header').text(), '@fat', 'title correctly inserted')
- assert.strictEqual($('.popover .popover-body').text(), 'loves writing tests ๏ผโฏยฐโกยฐ๏ผโฏ๏ธต โปโโป', 'content correctly inserted')
- $popover.bootstrapPopover('hide')
- })
- .one('hidden.bs.popover', function () {
- assert.strictEqual($('.popover').length, 0, 'popover was removed')
- done()
- })
- .bootstrapPopover('show')
- })
-
- QUnit.test('should allow DOMElement title and content (html: true)', function (assert) {
- assert.expect(5)
- var title = document.createTextNode('@glebm <3 writing tests')
- var content = $('
ยฏ\\_(ใ)_/ยฏ').get(0)
- var $popover = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapPopover({
- html: true,
- title: title,
- content: content
- })
-
- $popover.bootstrapPopover('show')
-
- assert.notEqual($('.popover').length, 0, 'popover inserted')
- assert.strictEqual($('.popover .popover-header').text(), '@glebm <3 writing tests', 'title inserted')
- assert.ok($.contains($('.popover').get(0), title), 'title node moved, not copied')
- // toLowerCase because IE8 will return
...
- assert.strictEqual($('.popover .popover-body').html().toLowerCase(), '
ยฏ\\_(ใ)_/ยฏ', 'content inserted')
- assert.ok($.contains($('.popover').get(0), content), 'content node moved, not copied')
- })
-
- QUnit.test('should allow DOMElement title and content (html: false)', function (assert) {
- assert.expect(5)
- var title = document.createTextNode('@glebm <3 writing tests')
- var content = $('
ยฏ\\_(ใ)_/ยฏ').get(0)
- var $popover = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapPopover({
- title: title,
- content: content
- })
-
- $popover.bootstrapPopover('show')
-
- assert.notEqual($('.popover').length, 0, 'popover inserted')
- assert.strictEqual($('.popover .popover-header').text(), '@glebm <3 writing tests', 'title inserted')
- assert.ok(!$.contains($('.popover').get(0), title), 'title node copied, not moved')
- assert.strictEqual($('.popover .popover-body').html(), 'ยฏ\\_(ใ)_/ยฏ', 'content inserted')
- assert.ok(!$.contains($('.popover').get(0), content), 'content node copied, not moved')
- })
-
- QUnit.test('should not duplicate HTML object', function (assert) {
- assert.expect(6)
- var done = assert.async()
- var $div = $('
').html('loves writing tests ๏ผโฏยฐโกยฐ๏ผโฏ๏ธต โปโโป')
-
- var $popover = $('
@fat')
- .appendTo('#qunit-fixture')
- .bootstrapPopover({
- html: true,
- content: function () {
- return $div
- }
- })
-
- function popoverInserted() {
- assert.notEqual($('.popover').length, 0, 'popover was inserted')
- assert.equal($('.popover .popover-body').html(), $div[0].outerHTML, 'content correctly inserted')
- }
-
- $popover
- .one('shown.bs.popover', function () {
- popoverInserted()
-
- $popover.one('hidden.bs.popover', function () {
- assert.strictEqual($('.popover').length, 0, 'popover was removed')
-
- $popover.one('shown.bs.popover', function () {
- popoverInserted()
-
- $popover.one('hidden.bs.popover', function () {
- assert.strictEqual($('.popover').length, 0, 'popover was removed')
- done()
- }).bootstrapPopover('hide')
- }).bootstrapPopover('show')
- }).bootstrapPopover('hide')
- })
- .bootstrapPopover('show')
- })
-
- QUnit.test('should get title and content from attributes', function (assert) {
- assert.expect(4)
- var done = assert.async()
- var $popover = $('
@mdo')
- .appendTo('#qunit-fixture')
- .bootstrapPopover()
- .one('shown.bs.popover', function () {
- assert.notEqual($('.popover').length, 0, 'popover was inserted')
- assert.strictEqual($('.popover .popover-header').text(), '@mdo', 'title correctly inserted')
- assert.strictEqual($('.popover .popover-body').text(), 'loves data attributes (ใฅ๏ฝกโโฟโฟโ๏ฝก)ใฅ ๏ธต โปโโป', 'content correctly inserted')
- $popover.bootstrapPopover('hide')
- })
- .one('hidden.bs.popover', function () {
- assert.strictEqual($('.popover').length, 0, 'popover was removed')
- done()
- })
- .bootstrapPopover('show')
- })
-
- QUnit.test('should get title and content from attributes ignoring options passed via js', function (assert) {
- assert.expect(4)
- var done = assert.async()
- var $popover = $('
@mdo')
- .appendTo('#qunit-fixture')
- .bootstrapPopover({
- title: 'ignored title option',
- content: 'ignored content option'
- })
- .one('shown.bs.popover', function () {
- assert.notEqual($('.popover').length, 0, 'popover was inserted')
- assert.strictEqual($('.popover .popover-header').text(), '@mdo', 'title correctly inserted')
- assert.strictEqual($('.popover .popover-body').text(), 'loves data attributes (ใฅ๏ฝกโโฟโฟโ๏ฝก)ใฅ ๏ธต โปโโป', 'content correctly inserted')
- $popover.bootstrapPopover('hide')
- })
- .one('hidden.bs.popover', function () {
- assert.strictEqual($('.popover').length, 0, 'popover was removed')
- done()
- })
- .bootstrapPopover('show')
- })
-
- QUnit.test('should respect custom template', function (assert) {
- assert.expect(3)
- var done = assert.async()
- var $popover = $('
@fat')
- .appendTo('#qunit-fixture')
- .bootstrapPopover({
- title: 'Test',
- content: 'Test',
- template: '
'
- })
- .one('shown.bs.popover', function () {
- assert.notEqual($('.popover').length, 0, 'popover was inserted')
- assert.ok($('.popover').hasClass('foobar'), 'custom class is present')
- $popover.bootstrapPopover('hide')
- })
- .one('hidden.bs.popover', function () {
- assert.strictEqual($('.popover').length, 0, 'popover was removed')
- done()
- })
- .bootstrapPopover('show')
- })
-
- QUnit.test('should destroy popover', function (assert) {
- assert.expect(7)
- var $popover = $('
')
- .bootstrapPopover({
- trigger: 'hover'
- })
- .on('click.foo', $.noop)
-
- assert.ok($popover.data('bs.popover'), 'popover has data')
- assert.ok($._data($popover[0], 'events').mouseover && $._data($popover[0], 'events').mouseout, 'popover has hover event')
- assert.strictEqual($._data($popover[0], 'events').click[0].namespace, 'foo', 'popover has extra click.foo event')
-
- $popover.bootstrapPopover('show')
- $popover.bootstrapPopover('dispose')
-
- assert.ok(!$popover.hasClass('show'), 'popover is hidden')
- assert.ok(!$popover.data('popover'), 'popover does not have data')
- assert.strictEqual($._data($popover[0], 'events').click[0].namespace, 'foo', 'popover still has click.foo')
- assert.ok(!$._data($popover[0], 'events').mouseover && !$._data($popover[0], 'events').mouseout, 'popover does not have any events')
- })
-
- QUnit.test('should render popover element using delegated selector', function (assert) {
- assert.expect(2)
- var done = assert.async()
- var $div = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapPopover({
- selector: 'a',
- trigger: 'click'
- })
- .one('shown.bs.popover', function () {
- assert.notEqual($('.popover').length, 0, 'popover was inserted')
- $div.find('a').trigger('click')
- })
- .one('hidden.bs.popover', function () {
- assert.strictEqual($('.popover').length, 0, 'popover was removed')
- done()
- })
-
- $div.find('a').trigger('click')
- })
-
- QUnit.test('should detach popover content rather than removing it so that event handlers are left intact', function (assert) {
- assert.expect(1)
- var $content = $('
').appendTo('#qunit-fixture')
-
- var handlerCalled = false
- $('.content-with-handler .btn').on('click', function () {
- handlerCalled = true
- })
-
- var $div = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapPopover({
- html: true,
- trigger: 'manual',
- container: 'body',
- animation: false,
- content: function () {
- return $content
- }
- })
-
- var done = assert.async()
- $div
- .one('shown.bs.popover', function () {
- $div
- .one('hidden.bs.popover', function () {
- $div
- .one('shown.bs.popover', function () {
- $('.content-with-handler .btn').trigger('click')
- assert.ok(handlerCalled, 'content\'s event handler still present')
- $div.bootstrapPopover('dispose')
- done()
- })
- .bootstrapPopover('show')
- })
- .bootstrapPopover('hide')
- })
- .bootstrapPopover('show')
- })
-
- QUnit.test('should do nothing when an attempt is made to hide an uninitialized popover', function (assert) {
- assert.expect(1)
-
- var $popover = $('
some text')
- .appendTo('#qunit-fixture')
- .on('hidden.bs.popover shown.bs.popover', function () {
- assert.ok(false, 'should not fire any popover events')
- })
- .bootstrapPopover('hide')
- assert.strictEqual(typeof $popover.data('bs.popover'), 'undefined', 'should not initialize the popover')
- })
-
- QUnit.test('should fire inserted event', function (assert) {
- assert.expect(2)
- var done = assert.async()
-
- $('
@Johann-S')
- .appendTo('#qunit-fixture')
- .on('inserted.bs.popover', function () {
- assert.notEqual($('.popover').length, 0, 'popover was inserted')
- assert.ok(true, 'inserted event fired')
- done()
- })
- .bootstrapPopover({
- title: 'Test',
- content: 'Test'
- })
- .bootstrapPopover('show')
- })
-
- QUnit.test('should throw an error when show is called on hidden elements', function (assert) {
- assert.expect(1)
- var done = assert.async()
-
- try {
- $('
').bootstrapPopover('show')
- } catch (err) {
- assert.strictEqual(err.message, 'Please use show on visible elements')
- done()
- }
- })
-
- QUnit.test('should hide popovers when their containing modal is closed', function (assert) {
- assert.expect(1)
- var done = assert.async()
- var templateHTML = '
' +
- '
' +
- '
' +
- '
' +
- '' +
- '
' +
- '
' +
- '
' +
- '
'
-
- $(templateHTML).appendTo('#qunit-fixture')
- $('#popover-test')
- .on('shown.bs.popover', function () {
- $('#modal-test').modal('hide')
- })
- .on('hide.bs.popover', function () {
- assert.ok(true, 'popover hide')
- done()
- })
-
- $('#modal-test')
- .on('shown.bs.modal', function () {
- $('#popover-test').bootstrapPopover('show')
- })
- .modal('show')
- })
-
- QUnit.test('should convert number to string without error for content and title', function (assert) {
- assert.expect(2)
- var done = assert.async()
- var $popover = $('
@mdo')
- .appendTo('#qunit-fixture')
- .bootstrapPopover({
- title: 5,
- content: 7
- })
- .on('shown.bs.popover', function () {
- assert.strictEqual($('.popover .popover-header').text(), '5')
- assert.strictEqual($('.popover .popover-body').text(), '7')
- done()
- })
-
- $popover.bootstrapPopover('show')
- })
-
- QUnit.test('popover should be shown right away after the call of disable/enable', function (assert) {
- assert.expect(2)
- var done = assert.async()
- var $popover = $('
@mdo')
- .appendTo('#qunit-fixture')
- .bootstrapPopover({
- title: 'Test popover',
- content: 'with disable/enable'
- })
- .on('shown.bs.popover', function () {
- assert.strictEqual($('.popover').hasClass('show'), true)
- done()
- })
-
- $popover.bootstrapPopover('disable')
- $popover.trigger($.Event('click'))
- setTimeout(function () {
- assert.strictEqual($('.popover').length === 0, true)
- $popover.bootstrapPopover('enable')
- $popover.trigger($.Event('click'))
- }, 200)
- })
-
- QUnit.test('popover should call content function only once', function (assert) {
- assert.expect(1)
- var done = assert.async()
- var nbCall = 0
- $('
content
').appendTo('#qunit-fixture')
- var $popover = $('
@Johann-S')
- .appendTo('#qunit-fixture')
- .bootstrapPopover({
- content: function () {
- nbCall++
- return $('#popover').clone().show().get(0)
- }
- })
- .on('shown.bs.popover', function () {
- assert.strictEqual(nbCall, 1)
- done()
- })
-
- $popover.trigger($.Event('click'))
- })
-})
diff --git a/js/tests/unit/popover.spec.js b/js/tests/unit/popover.spec.js
new file mode 100644
index 000000000000..acaf0a48c22d
--- /dev/null
+++ b/js/tests/unit/popover.spec.js
@@ -0,0 +1,250 @@
+import Popover from '../../src/popover'
+
+/** Test helpers */
+import { getFixture, clearFixture, jQueryMock } from '../helpers/fixture'
+
+describe('Popover', () => {
+ let fixtureEl
+
+ beforeAll(() => {
+ fixtureEl = getFixture()
+ })
+
+ afterEach(() => {
+ clearFixture()
+
+ const popoverList = document.querySelectorAll('.popover')
+
+ popoverList.forEach(popoverEl => {
+ document.body.removeChild(popoverEl)
+ })
+ })
+
+ describe('VERSION', () => {
+ it('should return plugin version', () => {
+ expect(Popover.VERSION).toEqual(jasmine.any(String))
+ })
+ })
+
+ describe('Default', () => {
+ it('should return plugin default config', () => {
+ expect(Popover.Default).toEqual(jasmine.any(Object))
+ })
+ })
+
+ describe('NAME', () => {
+ it('should return plugin name', () => {
+ expect(Popover.NAME).toEqual(jasmine.any(String))
+ })
+ })
+
+ describe('DATA_KEY', () => {
+ it('should return plugin data key', () => {
+ expect(Popover.DATA_KEY).toEqual('bs.popover')
+ })
+ })
+
+ describe('Event', () => {
+ it('should return plugin events', () => {
+ expect(Popover.Event).toEqual(jasmine.any(Object))
+ })
+ })
+
+ describe('EVENT_KEY', () => {
+ it('should return plugin event key', () => {
+ expect(Popover.EVENT_KEY).toEqual('.bs.popover')
+ })
+ })
+
+ describe('DefaultType', () => {
+ it('should return plugin default type', () => {
+ expect(Popover.DefaultType).toEqual(jasmine.any(Object))
+ })
+ })
+
+ describe('show', () => {
+ it('should show a popover', done => {
+ fixtureEl.innerHTML = '
BS twitter'
+
+ const popoverEl = fixtureEl.querySelector('a')
+ const popover = new Popover(popoverEl)
+
+ popoverEl.addEventListener('shown.bs.popover', () => {
+ expect(document.querySelector('.popover')).toBeDefined()
+ done()
+ })
+
+ popover.show()
+ })
+
+ it('should set title and content from functions', done => {
+ fixtureEl.innerHTML = '
BS twitter'
+
+ const popoverEl = fixtureEl.querySelector('a')
+ const popover = new Popover(popoverEl, {
+ title: () => 'Bootstrap',
+ content: () => 'loves writing tests ๏ผโฏยฐโกยฐ๏ผโฏ๏ธต โปโโป'
+ })
+
+ popoverEl.addEventListener('shown.bs.popover', () => {
+ const popoverDisplayed = document.querySelector('.popover')
+
+ expect(popoverDisplayed).toBeDefined()
+ expect(popoverDisplayed.querySelector('.popover-header').textContent).toEqual('Bootstrap')
+ expect(popoverDisplayed.querySelector('.popover-body').textContent).toEqual('loves writing tests ๏ผโฏยฐโกยฐ๏ผโฏ๏ธต โปโโป')
+ done()
+ })
+
+ popover.show()
+ })
+
+ it('should show a popover with just content', done => {
+ fixtureEl.innerHTML = '
BS twitter'
+
+ const popoverEl = fixtureEl.querySelector('a')
+ const popover = new Popover(popoverEl, {
+ content: 'Popover content'
+ })
+
+ popoverEl.addEventListener('shown.bs.popover', () => {
+ const popoverDisplayed = document.querySelector('.popover')
+
+ expect(popoverDisplayed).toBeDefined()
+ expect(popoverDisplayed.querySelector('.popover-body').textContent).toEqual('Popover content')
+ done()
+ })
+
+ popover.show()
+ })
+ })
+
+ describe('hide', () => {
+ it('should hide a popover', done => {
+ fixtureEl.innerHTML = '
BS twitter'
+
+ const popoverEl = fixtureEl.querySelector('a')
+ const popover = new Popover(popoverEl)
+
+ popoverEl.addEventListener('shown.bs.popover', () => {
+ popover.hide()
+ })
+
+ popoverEl.addEventListener('hidden.bs.popover', () => {
+ expect(document.querySelector('.popover')).toBeNull()
+ done()
+ })
+
+ popover.show()
+ })
+ })
+
+ describe('jQueryInterface', () => {
+ it('should create a popover', () => {
+ fixtureEl.innerHTML = '
BS twitter'
+
+ const popoverEl = fixtureEl.querySelector('a')
+
+ jQueryMock.fn.popover = Popover.jQueryInterface
+ jQueryMock.elements = [popoverEl]
+
+ jQueryMock.fn.popover.call(jQueryMock)
+
+ expect(Popover.getInstance(popoverEl)).toBeDefined()
+ })
+
+ it('should create a popover with a config object', () => {
+ fixtureEl.innerHTML = '
BS twitter'
+
+ const popoverEl = fixtureEl.querySelector('a')
+
+ jQueryMock.fn.popover = Popover.jQueryInterface
+ jQueryMock.elements = [popoverEl]
+
+ jQueryMock.fn.popover.call(jQueryMock, {
+ content: 'Popover content'
+ })
+
+ expect(Popover.getInstance(popoverEl)).toBeDefined()
+ })
+
+ it('should not re create a popover', () => {
+ fixtureEl.innerHTML = '
BS twitter'
+
+ 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 = '
BS twitter'
+
+ const popoverEl = fixtureEl.querySelector('a')
+ const action = 'undefinedMethod'
+
+ jQueryMock.fn.popover = Popover.jQueryInterface
+ jQueryMock.elements = [popoverEl]
+
+ try {
+ jQueryMock.fn.popover.call(jQueryMock, action)
+ } catch (error) {
+ expect(error.message).toEqual(`No method named "${action}"`)
+ }
+ })
+
+ it('should should call show method', () => {
+ fixtureEl.innerHTML = '
BS twitter'
+
+ const popoverEl = fixtureEl.querySelector('a')
+ const popover = new Popover(popoverEl)
+
+ jQueryMock.fn.popover = Popover.jQueryInterface
+ jQueryMock.elements = [popoverEl]
+
+ spyOn(popover, 'show')
+
+ jQueryMock.fn.popover.call(jQueryMock, 'show')
+
+ expect(popover.show).toHaveBeenCalled()
+ })
+
+ it('should do nothing if dipose is called when a popover do not exist', () => {
+ fixtureEl.innerHTML = '
BS twitter'
+
+ const popoverEl = fixtureEl.querySelector('a')
+
+ jQueryMock.fn.popover = Popover.jQueryInterface
+ jQueryMock.elements = [popoverEl]
+
+ spyOn(Popover.prototype, 'dispose')
+
+ jQueryMock.fn.popover.call(jQueryMock, 'dispose')
+
+ expect(Popover.prototype.dispose).not.toHaveBeenCalled()
+ })
+ })
+
+ describe('getInstance', () => {
+ it('should return popover instance', () => {
+ fixtureEl.innerHTML = '
BS twitter'
+
+ const popoverEl = fixtureEl.querySelector('a')
+ const popover = new Popover(popoverEl)
+
+ expect(Popover.getInstance(popoverEl)).toEqual(popover)
+ })
+
+ it('should return null when there is no popover instance', () => {
+ fixtureEl.innerHTML = '
BS twitter'
+
+ const popoverEl = fixtureEl.querySelector('a')
+
+ expect(Popover.getInstance(popoverEl)).toEqual(null)
+ })
+ })
+})
diff --git a/js/tests/unit/scrollspy.js b/js/tests/unit/scrollspy.js
deleted file mode 100644
index 1337e585d49d..000000000000
--- a/js/tests/unit/scrollspy.js
+++ /dev/null
@@ -1,728 +0,0 @@
-$(function () {
- 'use strict'
-
- QUnit.module('scrollspy plugin')
-
- QUnit.test('should be defined on jquery object', function (assert) {
- assert.expect(1)
- assert.ok($(document.body).scrollspy, 'scrollspy method is defined')
- })
-
- QUnit.module('scrollspy', {
- beforeEach: function () {
- // Run all tests in noConflict mode -- it's the only way to ensure that the plugin works in noConflict mode
- $.fn.bootstrapScrollspy = $.fn.scrollspy.noConflict()
- },
- afterEach: function () {
- $.fn.scrollspy = $.fn.bootstrapScrollspy
- delete $.fn.bootstrapScrollspy
- $('#qunit-fixture').html('')
- }
- })
-
- QUnit.test('should provide no conflict', function (assert) {
- assert.expect(1)
- assert.strictEqual(typeof $.fn.scrollspy, 'undefined', 'scrollspy was set back to undefined (org value)')
- })
-
- QUnit.test('should throw explicit error on undefined method', function (assert) {
- assert.expect(1)
- var $el = $('
').appendTo('#qunit-fixture')
- $el.bootstrapScrollspy()
- try {
- $el.bootstrapScrollspy('noMethod')
- } catch (err) {
- assert.strictEqual(err.message, 'No method named "noMethod"')
- }
- })
-
- QUnit.test('should return jquery collection containing the element', function (assert) {
- assert.expect(2)
- var $el = $('
').appendTo('#qunit-fixture')
- var $scrollspy = $el.bootstrapScrollspy()
- assert.ok($scrollspy instanceof $, 'returns jquery collection')
- assert.strictEqual($scrollspy[0], $el[0], 'collection contains element')
- })
-
- QUnit.test('should only switch "active" class on current target', function (assert) {
- assert.expect(1)
- var done = assert.async()
-
- var sectionHTML = '
'
- var $section = $(sectionHTML).appendTo('#qunit-fixture')
-
- var $scrollspy = $section
- .show()
- .find('#scrollspy-example')
- .bootstrapScrollspy({
- target: '#ss-target'
- })
-
- $scrollspy.one('scroll', function () {
- assert.ok($section.hasClass('active'), '"active" class still on root node')
- done()
- })
-
- $scrollspy.scrollTop(350)
- })
-
- QUnit.test('should only switch "active" class on current target specified w element', function (assert) {
- assert.expect(1)
- var done = assert.async()
-
- var sectionHTML = '
'
- var $section = $(sectionHTML).appendTo('#qunit-fixture')
-
- var $scrollspy = $section
- .show()
- .find('#scrollspy-example')
- .bootstrapScrollspy({
- target: document.getElementById('#ss-target')
- })
-
- $scrollspy.one('scroll', function () {
- assert.ok($section.hasClass('active'), '"active" class still on root node')
- done()
- })
-
- $scrollspy.scrollTop(350)
- })
-
- QUnit.test('should correctly select middle navigation option when large offset is used', function (assert) {
- assert.expect(3)
- var done = assert.async()
-
- var sectionHTML = '' +
- '
' +
- '
' +
- '
' +
- '
' +
- '
' +
- '
'
- var $section = $(sectionHTML).appendTo('#qunit-fixture')
- var $scrollspy = $section
- .show()
- .filter('#content')
-
- $scrollspy.bootstrapScrollspy({
- target: '#navigation',
- offset: $scrollspy.position().top
- })
-
- $scrollspy.one('scroll', function () {
- assert.ok(!$section.find('#one-link').hasClass('active'), '"active" class removed from first section')
- assert.ok($section.find('#two-link').hasClass('active'), '"active" class on middle section')
- assert.ok(!$section.find('#three-link').hasClass('active'), '"active" class not on last section')
- done()
- })
-
- $scrollspy.scrollTop(550)
- })
-
- QUnit.test('should add the active class to the correct element', function (assert) {
- assert.expect(2)
- var navbarHtml =
- '
'
- var contentHtml =
- '
' +
- '
div 1
' +
- '
div 2
' +
- '
'
-
- $(navbarHtml).appendTo('#qunit-fixture')
- var $content = $(contentHtml)
- .appendTo('#qunit-fixture')
- .bootstrapScrollspy({
- offset: 0,
- target: '.navbar'
- })
-
- var done = assert.async()
- var testElementIsActiveAfterScroll = function (element, target) {
- var deferred = $.Deferred()
- var scrollHeight = Math.ceil($content.scrollTop() + $(target).position().top)
- $content.one('scroll', function () {
- assert.ok($(element).hasClass('active'), 'target:' + target + ', element' + element)
- deferred.resolve()
- })
- $content.scrollTop(scrollHeight)
- return deferred.promise()
- }
-
- $.when(testElementIsActiveAfterScroll('#a-1', '#div-1'))
- .then(function () {
- return testElementIsActiveAfterScroll('#a-2', '#div-2')
- })
- .then(function () {
- done()
- })
- })
-
- QUnit.test('should add the active class to the correct element (nav markup)', function (assert) {
- assert.expect(2)
- var navbarHtml =
- '
'
- var contentHtml =
- '
' +
- '
div 1
' +
- '
div 2
' +
- '
'
-
- $(navbarHtml).appendTo('#qunit-fixture')
- var $content = $(contentHtml)
- .appendTo('#qunit-fixture')
- .bootstrapScrollspy({
- offset: 0,
- target: '.navbar'
- })
-
- var done = assert.async()
- var testElementIsActiveAfterScroll = function (element, target) {
- var deferred = $.Deferred()
- var scrollHeight = Math.ceil($content.scrollTop() + $(target).position().top)
- $content.one('scroll', function () {
- assert.ok($(element).hasClass('active'), 'target:' + target + ', element' + element)
- deferred.resolve()
- })
- $content.scrollTop(scrollHeight)
- return deferred.promise()
- }
-
- $.when(testElementIsActiveAfterScroll('#a-1', '#div-1'))
- .then(function () {
- return testElementIsActiveAfterScroll('#a-2', '#div-2')
- })
- .then(function () {
- done()
- })
- })
-
- QUnit.test('should add the active class to the correct element (list-group markup)', function (assert) {
- assert.expect(2)
- var navbarHtml =
- '
' +
- '' +
- ''
- var contentHtml =
- '
' +
- '
div 1
' +
- '
div 2
' +
- '
'
-
- $(navbarHtml).appendTo('#qunit-fixture')
- var $content = $(contentHtml)
- .appendTo('#qunit-fixture')
- .bootstrapScrollspy({
- offset: 0,
- target: '.navbar'
- })
-
- var done = assert.async()
- var testElementIsActiveAfterScroll = function (element, target) {
- var deferred = $.Deferred()
- var scrollHeight = Math.ceil($content.scrollTop() + $(target).position().top)
- $content.one('scroll', function () {
- assert.ok($(element).hasClass('active'), 'target:' + target + ', element' + element)
- deferred.resolve()
- })
- $content.scrollTop(scrollHeight)
- return deferred.promise()
- }
-
- $.when(testElementIsActiveAfterScroll('#a-1', '#div-1'))
- .then(function () {
- return testElementIsActiveAfterScroll('#a-2', '#div-2')
- })
- .then(function () {
- done()
- })
- })
-
- QUnit.test('should add the active class correctly when there are nested elements at 0 scroll offset', function (assert) {
- assert.expect(6)
- var times = 0
- var done = assert.async()
- var navbarHtml = '
' +
- '' +
- '- div 1' +
- '' +
- '
' +
- '
' +
- ''
-
- var contentHtml = '
'
-
- $(navbarHtml).appendTo('#qunit-fixture')
-
- var $content = $(contentHtml)
- .appendTo('#qunit-fixture')
- .bootstrapScrollspy({
- offset: 0,
- target: '#navigation'
- })
-
- function testActiveElements() {
- if (++times > 3) {
- return done()
- }
-
- $content.one('scroll', function () {
- assert.ok($('#a-1').hasClass('active'), 'nav item for outer element has "active" class')
- assert.ok($('#a-2').hasClass('active'), 'nav item for inner element has "active" class')
- testActiveElements()
- })
-
- $content.scrollTop($content.scrollTop() + 10)
- }
-
- testActiveElements()
- })
-
- QUnit.test('should add the active class correctly when there are nested elements (nav markup)', function (assert) {
- assert.expect(6)
- var times = 0
- var done = assert.async()
- var navbarHtml = '
' +
- '' +
- 'div 1' +
- '' +
- 'div 2' +
- '' +
- '' +
- ''
-
- var contentHtml = '
'
-
- $(navbarHtml).appendTo('#qunit-fixture')
-
- var $content = $(contentHtml)
- .appendTo('#qunit-fixture')
- .bootstrapScrollspy({
- offset: 0,
- target: '#navigation'
- })
-
- function testActiveElements() {
- if (++times > 3) {
- return done()
- }
-
- $content.one('scroll', function () {
- assert.ok($('#a-1').hasClass('active'), 'nav item for outer element has "active" class')
- assert.ok($('#a-2').hasClass('active'), 'nav item for inner element has "active" class')
- testActiveElements()
- })
-
- $content.scrollTop($content.scrollTop() + 10)
- }
-
- testActiveElements()
- })
-
- QUnit.test('should add the active class correctly when there are nested elements (nav nav-item markup)', function (assert) {
- assert.expect(6)
- var times = 0
- var done = assert.async()
- var navbarHtml = '
' +
- '' +
- '- div 1
' +
- '' +
- '
' +
- ''
-
- var contentHtml = '
'
-
- $(navbarHtml).appendTo('#qunit-fixture')
-
- var $content = $(contentHtml)
- .appendTo('#qunit-fixture')
- .bootstrapScrollspy({
- offset: 0,
- target: '#navigation'
- })
-
- function testActiveElements() {
- if (++times > 3) {
- return done()
- }
-
- $content.one('scroll', function () {
- assert.ok($('#a-1').hasClass('active'), 'nav item for outer element has "active" class')
- assert.ok($('#a-2').hasClass('active'), 'nav item for inner element has "active" class')
- testActiveElements()
- })
-
- $content.scrollTop($content.scrollTop() + 10)
- }
-
- testActiveElements()
- })
-
- QUnit.test('should add the active class correctly when there are nested elements (list-group markup)', function (assert) {
- assert.expect(6)
- var times = 0
- var done = assert.async()
- var navbarHtml = '
' +
- '' +
- '
div 1' +
- '
' +
- '
' +
- ''
-
- var contentHtml = '
'
-
- $(navbarHtml).appendTo('#qunit-fixture')
-
- var $content = $(contentHtml)
- .appendTo('#qunit-fixture')
- .bootstrapScrollspy({
- offset: 0,
- target: '#navigation'
- })
-
- function testActiveElements() {
- if (++times > 3) {
- return done()
- }
-
- $content.one('scroll', function () {
- assert.ok($('#a-1').hasClass('active'), 'nav item for outer element has "active" class')
- assert.ok($('#a-2').hasClass('active'), 'nav item for inner element has "active" class')
- testActiveElements()
- })
-
- $content.scrollTop($content.scrollTop() + 10)
- }
-
- testActiveElements()
- })
-
- QUnit.test('should clear selection if above the first section', function (assert) {
- assert.expect(3)
- var done = assert.async()
-
- var sectionHTML = '' +
- '
' +
- '' +
- ''
- $(sectionHTML).appendTo('#qunit-fixture')
-
- var scrollspyHTML = '
' +
- '
' +
- '
' +
- '
' +
- '
' +
- '
' +
- '
'
- var $scrollspy = $(scrollspyHTML).appendTo('#qunit-fixture')
-
- $scrollspy
- .bootstrapScrollspy({
- target: '#navigation',
- offset: $scrollspy.position().top
- })
- .one('scroll', function () {
- assert.strictEqual($('.active').length, 1, '"active" class on only one element present')
- assert.strictEqual($('.active').is('#two-link'), true, '"active" class on second section')
- $scrollspy
- .one('scroll', function () {
- assert.strictEqual($('.active').length, 0, 'selection cleared')
- done()
- })
- .scrollTop(0)
- })
- .scrollTop(201)
- })
-
- QUnit.test('should NOT clear selection if above the first section and first section is at the top', function (assert) {
- assert.expect(4)
- var done = assert.async()
-
- var sectionHTML = '' +
- '
' +
- '' +
- ''
- $(sectionHTML).appendTo('#qunit-fixture')
-
- var negativeHeight = -10
- var startOfSectionTwo = 101
-
- var scrollspyHTML = '
' +
- '
' +
- '
' +
- '
' +
- '
' +
- '
'
- var $scrollspy = $(scrollspyHTML).appendTo('#qunit-fixture')
-
- $scrollspy
- .bootstrapScrollspy({
- target: '#navigation',
- offset: $scrollspy.position().top
- })
- .one('scroll', function () {
- assert.strictEqual($('.active').length, 1, '"active" class on only one element present')
- assert.strictEqual($('.active').is('#two-link'), true, '"active" class on second section')
- $scrollspy
- .one('scroll', function () {
- assert.strictEqual($('.active').length, 1, '"active" class on only one element present')
- assert.strictEqual($('.active').is('#one-link'), true, '"active" class on first section')
- done()
- })
- .scrollTop(negativeHeight)
- })
- .scrollTop(startOfSectionTwo)
- })
-
- QUnit.test('should correctly select navigation element on backward scrolling when each target section height is 100%', function (assert) {
- assert.expect(5)
- var navbarHtml =
- '
' +
- '' +
- ''
- var contentHtml =
- '
' +
- '
div 1
' +
- '
div 2
' +
- '
div 3
' +
- '
div 4
' +
- '
div 5
' +
- '
'
-
- $(navbarHtml).appendTo('#qunit-fixture')
- var $content = $(contentHtml)
- .appendTo('#qunit-fixture')
- .bootstrapScrollspy({
- offset: 0,
- target: '.navbar'
- })
-
- var testElementIsActiveAfterScroll = function (element, target) {
- var deferred = $.Deferred()
- var scrollHeight = Math.ceil($content.scrollTop() + $(target).position().top)
- $content.one('scroll', function () {
- assert.ok($(element).hasClass('active'), 'target:' + target + ', element: ' + element)
- deferred.resolve()
- })
- $content.scrollTop(scrollHeight)
- return deferred.promise()
- }
-
- var done = assert.async()
- $.when(testElementIsActiveAfterScroll('#li-100-5', '#div-100-5'))
- .then(function () {
- return testElementIsActiveAfterScroll('#li-100-4', '#div-100-4')
- })
- .then(function () {
- return testElementIsActiveAfterScroll('#li-100-3', '#div-100-3')
- })
- .then(function () {
- return testElementIsActiveAfterScroll('#li-100-2', '#div-100-2')
- })
- .then(function () {
- return testElementIsActiveAfterScroll('#li-100-1', '#div-100-1')
- })
- .then(function () {
- done()
- })
- })
-
- QUnit.test('should allow passed in option offset method: offset', function (assert) {
- assert.expect(4)
-
- var testOffsetMethod = function (type) {
- var $navbar = $(
- ''
- )
- var $content = $(
- '
' +
- '
div 1
' +
- '
div 2
' +
- '
div 3
' +
- '
'
- )
-
- $navbar.appendTo('#qunit-fixture')
- $content.appendTo('#qunit-fixture')
-
- if (type === 'js') {
- $content.bootstrapScrollspy({
- target: '.navbar',
- offset: 0,
- method: 'offset'
- })
- } else if (type === 'data') {
- $(window).trigger('load')
- }
-
- var $target = $('#div-' + type + 'm-2')
- var scrollspy = $content.data('bs.scrollspy')
-
- assert.ok(scrollspy._offsets[1] === $target.offset().top, 'offset method with ' + type + ' option')
- assert.ok(scrollspy._offsets[1] !== $target.position().top, 'position method with ' + type + ' option')
- $navbar.remove()
- $content.remove()
- }
-
- testOffsetMethod('js')
- testOffsetMethod('data')
- })
-
- QUnit.test('should allow passed in option offset method: position', function (assert) {
- assert.expect(4)
-
- var testOffsetMethod = function (type) {
- var $navbar = $(
- ''
- )
- var $content = $(
- '
' +
- '
div 1
' +
- '
div 2
' +
- '
div 3
' +
- '
'
- )
-
- $navbar.appendTo('#qunit-fixture')
- $content.appendTo('#qunit-fixture')
-
- if (type === 'js') {
- $content.bootstrapScrollspy({
- target: '.navbar',
- offset: 0,
- method: 'position'
- })
- } else if (type === 'data') {
- $(window).trigger('load')
- }
-
- var $target = $('#div-' + type + 'm-2')
- var scrollspy = $content.data('bs.scrollspy')
-
- assert.ok(scrollspy._offsets[1] !== $target.offset().top, 'offset method with ' + type + ' option')
- assert.ok(scrollspy._offsets[1] === $target.position().top, 'position method with ' + type + ' option')
- $navbar.remove()
- $content.remove()
- }
-
- testOffsetMethod('js')
- testOffsetMethod('data')
- })
-})
diff --git a/js/tests/unit/scrollspy.spec.js b/js/tests/unit/scrollspy.spec.js
new file mode 100644
index 000000000000..9ac02ce99fb7
--- /dev/null
+++ b/js/tests/unit/scrollspy.spec.js
@@ -0,0 +1,653 @@
+import ScrollSpy from '../../src/scrollspy'
+import Manipulator from '../../src/dom/manipulator'
+import EventHandler from '../../src/dom/event-handler'
+
+/** Test helpers */
+import { getFixture, clearFixture, createEvent, jQueryMock } from '../helpers/fixture'
+
+describe('ScrollSpy', () => {
+ let fixtureEl
+
+ const testElementIsActiveAfterScroll = ({ elementSelector, targetSelector, contentEl, scrollSpy, spy, cb }) => {
+ const element = fixtureEl.querySelector(elementSelector)
+ const target = fixtureEl.querySelector(targetSelector)
+
+ // add top padding to fix Chrome on Android failures
+ const paddingTop = 5
+ const scrollHeight = Math.ceil(contentEl.scrollTop + Manipulator.position(target).top) + paddingTop
+
+ function listener() {
+ expect(element.classList.contains('active')).toEqual(true)
+ contentEl.removeEventListener('scroll', listener)
+ expect(scrollSpy._process).toHaveBeenCalled()
+ spy.calls.reset()
+ cb()
+ }
+
+ contentEl.addEventListener('scroll', listener)
+ contentEl.scrollTop = scrollHeight
+ }
+
+ beforeAll(() => {
+ fixtureEl = getFixture()
+ })
+
+ afterEach(() => {
+ clearFixture()
+ })
+
+ describe('VERSION', () => {
+ it('should return plugin version', () => {
+ expect(ScrollSpy.VERSION).toEqual(jasmine.any(String))
+ })
+ })
+
+ describe('Default', () => {
+ it('should return plugin default config', () => {
+ expect(ScrollSpy.Default).toEqual(jasmine.any(Object))
+ })
+ })
+
+ describe('constructor', () => {
+ it('should generate an id when there is not one', () => {
+ fixtureEl.innerHTML = [
+ '
',
+ '
'
+ ].join('')
+
+ const navEl = fixtureEl.querySelector('nav')
+ const scrollSpy = new ScrollSpy(fixtureEl.querySelector('.content'), {
+ target: navEl
+ })
+
+ expect(scrollSpy).toBeDefined()
+ expect(navEl.getAttribute('id')).not.toEqual(null)
+ })
+
+ it('should not process element without target', () => {
+ fixtureEl.innerHTML = [
+ '
',
+ ' ',
+ '',
+ '
'
+ ].join('')
+
+ const scrollSpy = new ScrollSpy(fixtureEl.querySelector('#content'), {
+ target: '#navigation'
+ })
+
+ expect(scrollSpy._targets.length).toEqual(2)
+ })
+
+ it('should only switch "active" class on current target', done => {
+ fixtureEl.innerHTML = [
+ '
'
+ ].join('')
+
+ const scrollSpyEl = fixtureEl.querySelector('#scrollspy-example')
+ const rootEl = fixtureEl.querySelector('#root')
+ const scrollSpy = new ScrollSpy(scrollSpyEl, {
+ target: 'ss-target'
+ })
+
+ spyOn(scrollSpy, '_process').and.callThrough()
+
+ scrollSpyEl.addEventListener('scroll', () => {
+ expect(rootEl.classList.contains('active')).toEqual(true)
+ expect(scrollSpy._process).toHaveBeenCalled()
+ done()
+ })
+
+ scrollSpyEl.scrollTop = 350
+ })
+
+ it('should only switch "active" class on current target specified w element', done => {
+ fixtureEl.innerHTML = [
+ '
'
+ ].join('')
+
+ const scrollSpyEl = fixtureEl.querySelector('#scrollspy-example')
+ const rootEl = fixtureEl.querySelector('#root')
+ const scrollSpy = new ScrollSpy(scrollSpyEl, {
+ target: fixtureEl.querySelector('#ss-target')
+ })
+
+ spyOn(scrollSpy, '_process').and.callThrough()
+
+ scrollSpyEl.addEventListener('scroll', () => {
+ expect(rootEl.classList.contains('active')).toEqual(true)
+ expect(scrollSpy._process).toHaveBeenCalled()
+ done()
+ })
+
+ scrollSpyEl.scrollTop = 350
+ })
+
+ it('should correctly select middle navigation option when large offset is used', done => {
+ fixtureEl.innerHTML = [
+ '',
+ '
',
+ ' ',
+ '',
+ '
',
+ '
',
+ '
',
+ '
',
+ '
'
+ ].join('')
+
+ const contentEl = fixtureEl.querySelector('#content')
+ const scrollSpy = new ScrollSpy(contentEl, {
+ target: '#navigation',
+ offset: Manipulator.position(contentEl).top
+ })
+
+ spyOn(scrollSpy, '_process').and.callThrough()
+
+ contentEl.addEventListener('scroll', () => {
+ expect(fixtureEl.querySelector('#one-link').classList.contains('active')).toEqual(false)
+ expect(fixtureEl.querySelector('#two-link').classList.contains('active')).toEqual(true)
+ expect(fixtureEl.querySelector('#three-link').classList.contains('active')).toEqual(false)
+ expect(scrollSpy._process).toHaveBeenCalled()
+ done()
+ })
+
+ contentEl.scrollTop = 550
+ })
+
+ it('should add the active class to the correct element', done => {
+ fixtureEl.innerHTML = [
+ '
',
+ ' ',
+ '',
+ '
',
+ '
div 1
',
+ '
div 2
',
+ '
'
+ ].join('')
+
+ const contentEl = fixtureEl.querySelector('.content')
+ const scrollSpy = new ScrollSpy(contentEl, {
+ offset: 0,
+ target: '.navbar'
+ })
+ const spy = spyOn(scrollSpy, '_process').and.callThrough()
+
+ testElementIsActiveAfterScroll({
+ elementSelector: '#a-1',
+ targetSelector: '#div-1',
+ contentEl,
+ scrollSpy,
+ spy,
+ cb: () => {
+ testElementIsActiveAfterScroll({
+ elementSelector: '#a-2',
+ targetSelector: '#div-2',
+ contentEl,
+ scrollSpy,
+ spy,
+ cb: () => done()
+ })
+ }
+ })
+ })
+
+ it('should add the active class to the correct element (nav markup)', done => {
+ fixtureEl.innerHTML = [
+ '
',
+ ' ',
+ ' div 1',
+ ' div 2',
+ ' ',
+ '',
+ '
',
+ '
div 1
',
+ '
div 2
',
+ '
'
+ ].join('')
+
+ const contentEl = fixtureEl.querySelector('.content')
+ const scrollSpy = new ScrollSpy(contentEl, {
+ offset: 0,
+ target: '.navbar'
+ })
+ const spy = spyOn(scrollSpy, '_process').and.callThrough()
+
+ testElementIsActiveAfterScroll({
+ elementSelector: '#a-1',
+ targetSelector: '#div-1',
+ contentEl,
+ scrollSpy,
+ spy,
+ cb: () => {
+ testElementIsActiveAfterScroll({
+ elementSelector: '#a-2',
+ targetSelector: '#div-2',
+ contentEl,
+ scrollSpy,
+ spy,
+ cb: () => done()
+ })
+ }
+ })
+ })
+
+ it('should add the active class to the correct element (list-group markup)', done => {
+ fixtureEl.innerHTML = [
+ '
',
+ ' ',
+ '',
+ '
',
+ '
div 1
',
+ '
div 2
',
+ '
'
+ ].join('')
+
+ const contentEl = fixtureEl.querySelector('.content')
+ const scrollSpy = new ScrollSpy(contentEl, {
+ offset: 0,
+ target: '.navbar'
+ })
+ const spy = spyOn(scrollSpy, '_process').and.callThrough()
+
+ testElementIsActiveAfterScroll({
+ elementSelector: '#a-1',
+ targetSelector: '#div-1',
+ contentEl,
+ scrollSpy,
+ spy,
+ cb: () => {
+ testElementIsActiveAfterScroll({
+ elementSelector: '#a-2',
+ targetSelector: '#div-2',
+ contentEl,
+ scrollSpy,
+ spy,
+ cb: () => done()
+ })
+ }
+ })
+ })
+
+ it('should clear selection if above the first section', done => {
+ fixtureEl.innerHTML = [
+ '',
+ '
',
+ ' ',
+ '',
+ '
',
+ '
',
+ '
',
+ '
',
+ '
',
+ '
',
+ '
'
+ ].join('')
+
+ const contentEl = fixtureEl.querySelector('#content')
+ const scrollSpy = new ScrollSpy(contentEl, {
+ target: '#navigation',
+ offset: Manipulator.position(contentEl).top
+ })
+ const spy = spyOn(scrollSpy, '_process').and.callThrough()
+
+ let firstTime = true
+
+ contentEl.addEventListener('scroll', () => {
+ const active = fixtureEl.querySelector('.active')
+
+ expect(spy).toHaveBeenCalled()
+ spy.calls.reset()
+ if (firstTime) {
+ expect(fixtureEl.querySelectorAll('.active').length).toEqual(1)
+ expect(active.getAttribute('id')).toEqual('two-link')
+ firstTime = false
+ contentEl.scrollTop = 0
+ } else {
+ expect(active).toBeNull()
+ done()
+ }
+ })
+
+ contentEl.scrollTop = 201
+ })
+
+ it('should not clear selection if above the first section and first section is at the top', done => {
+ fixtureEl.innerHTML = [
+ '',
+ '
',
+ ' ',
+ '',
+ '
',
+ '
',
+ '
',
+ '
',
+ '
',
+ '
'
+ ].join('')
+
+ const negativeHeight = -10
+ const startOfSectionTwo = 101
+ const contentEl = fixtureEl.querySelector('#content')
+ const scrollSpy = new ScrollSpy(contentEl, {
+ target: '#navigation',
+ offset: contentEl.offsetTop
+ })
+ const spy = spyOn(scrollSpy, '_process').and.callThrough()
+
+ let firstTime = true
+
+ contentEl.addEventListener('scroll', () => {
+ const active = fixtureEl.querySelector('.active')
+
+ expect(spy).toHaveBeenCalled()
+ spy.calls.reset()
+ if (firstTime) {
+ expect(fixtureEl.querySelectorAll('.active').length).toEqual(1)
+ expect(active.getAttribute('id')).toEqual('two-link')
+ firstTime = false
+ contentEl.scrollTop = negativeHeight
+ } else {
+ expect(fixtureEl.querySelectorAll('.active').length).toEqual(1)
+ expect(active.getAttribute('id')).toEqual('one-link')
+ done()
+ }
+ })
+
+ contentEl.scrollTop = startOfSectionTwo
+ })
+
+ it('should correctly select navigation element on backward scrolling when each target section height is 100%', done => {
+ fixtureEl.innerHTML = [
+ '
',
+ ' ',
+ '',
+ '
',
+ '
div 1
',
+ '
div 2
',
+ '
div 3
',
+ '
div 4
',
+ '
div 5
',
+ '
'
+ ].join('')
+
+ const contentEl = fixtureEl.querySelector('.content')
+ const scrollSpy = new ScrollSpy(contentEl, {
+ offset: 0,
+ target: '.navbar'
+ })
+ const spy = spyOn(scrollSpy, '_process').and.callThrough()
+
+ testElementIsActiveAfterScroll({
+ elementSelector: '#li-100-5',
+ targetSelector: '#div-100-5',
+ scrollSpy,
+ spy,
+ contentEl,
+ cb() {
+ contentEl.scrollTop = 0
+ testElementIsActiveAfterScroll({
+ elementSelector: '#li-100-4',
+ targetSelector: '#div-100-4',
+ scrollSpy,
+ spy,
+ contentEl,
+ cb() {
+ contentEl.scrollTop = 0
+ testElementIsActiveAfterScroll({
+ elementSelector: '#li-100-3',
+ targetSelector: '#div-100-3',
+ scrollSpy,
+ spy,
+ contentEl,
+ cb() {
+ contentEl.scrollTop = 0
+ testElementIsActiveAfterScroll({
+ elementSelector: '#li-100-2',
+ targetSelector: '#div-100-2',
+ scrollSpy,
+ spy,
+ contentEl,
+ cb() {
+ contentEl.scrollTop = 0
+ testElementIsActiveAfterScroll({
+ elementSelector: '#li-100-1',
+ targetSelector: '#div-100-1',
+ scrollSpy,
+ spy,
+ contentEl,
+ cb: done
+ })
+ }
+ })
+ }
+ })
+ }
+ })
+ }
+ })
+ })
+
+ it('should allow passed in option offset method: offset', () => {
+ fixtureEl.innerHTML = [
+ '
',
+ ' ',
+ '',
+ '
',
+ '
div 1
',
+ '
div 2
',
+ '
div 3
',
+ '
'
+ ].join('')
+
+ const contentEl = fixtureEl.querySelector('.content')
+ const targetEl = fixtureEl.querySelector('#div-jsm-2')
+ const scrollSpy = new ScrollSpy(contentEl, {
+ target: '.navbar',
+ offset: 0,
+ method: 'offset'
+ })
+
+ expect(scrollSpy._offsets[1]).toEqual(Manipulator.offset(targetEl).top)
+ expect(scrollSpy._offsets[1]).not.toEqual(Manipulator.position(targetEl).top)
+ })
+
+ it('should allow passed in option offset method: position', () => {
+ fixtureEl.innerHTML = [
+ '
',
+ ' ',
+ '',
+ '
',
+ '
div 1
',
+ '
div 2
',
+ '
div 3
',
+ '
'
+ ].join('')
+
+ const contentEl = fixtureEl.querySelector('.content')
+ const targetEl = fixtureEl.querySelector('#div-jsm-2')
+ const scrollSpy = new ScrollSpy(contentEl, {
+ target: '.navbar',
+ offset: 0,
+ method: 'position'
+ })
+
+ expect(scrollSpy._offsets[1]).not.toEqual(Manipulator.offset(targetEl).top)
+ expect(scrollSpy._offsets[1]).toEqual(Manipulator.position(targetEl).top)
+ })
+ })
+
+ describe('dispose', () => {
+ it('should dispose a scrollspy', () => {
+ spyOn(EventHandler, 'off')
+ fixtureEl.innerHTML = '
'
+
+ const divEl = fixtureEl.querySelector('div')
+ const scrollSpy = new ScrollSpy(divEl)
+
+ scrollSpy.dispose()
+ expect(EventHandler.off).toHaveBeenCalledWith(divEl, '.bs.scrollspy')
+ })
+ })
+
+ describe('jQueryInterface', () => {
+ it('should create a scrollspy', () => {
+ fixtureEl.innerHTML = '
'
+
+ const div = fixtureEl.querySelector('div')
+
+ jQueryMock.fn.scrollspy = ScrollSpy.jQueryInterface
+ jQueryMock.elements = [div]
+
+ jQueryMock.fn.scrollspy.call(jQueryMock)
+
+ expect(ScrollSpy.getInstance(div)).toBeDefined()
+ })
+
+ it('should not re create a scrollspy', () => {
+ fixtureEl.innerHTML = '
'
+
+ const div = fixtureEl.querySelector('div')
+ const scrollSpy = new ScrollSpy(div)
+
+ jQueryMock.fn.scrollspy = ScrollSpy.jQueryInterface
+ jQueryMock.elements = [div]
+
+ jQueryMock.fn.scrollspy.call(jQueryMock)
+
+ expect(ScrollSpy.getInstance(div)).toEqual(scrollSpy)
+ })
+
+ it('should call a scrollspy method', () => {
+ fixtureEl.innerHTML = '
'
+
+ const div = fixtureEl.querySelector('div')
+ const scrollSpy = new ScrollSpy(div)
+
+ spyOn(scrollSpy, 'refresh')
+
+ jQueryMock.fn.scrollspy = ScrollSpy.jQueryInterface
+ jQueryMock.elements = [div]
+
+ jQueryMock.fn.scrollspy.call(jQueryMock, 'refresh')
+
+ expect(ScrollSpy.getInstance(div)).toEqual(scrollSpy)
+ expect(scrollSpy.refresh).toHaveBeenCalled()
+ })
+
+ it('should throw error on undefined method', () => {
+ fixtureEl.innerHTML = '
'
+
+ const div = fixtureEl.querySelector('div')
+ const action = 'undefinedMethod'
+
+ jQueryMock.fn.scrollspy = ScrollSpy.jQueryInterface
+ jQueryMock.elements = [div]
+
+ try {
+ jQueryMock.fn.scrollspy.call(jQueryMock, action)
+ } catch (error) {
+ expect(error.message).toEqual(`No method named "${action}"`)
+ }
+ })
+ })
+
+ describe('getInstance', () => {
+ it('should return null if there is no instance', () => {
+ expect(ScrollSpy.getInstance(fixtureEl)).toEqual(null)
+ })
+ })
+
+ describe('event handler', () => {
+ it('should create scrollspy on window load event', () => {
+ fixtureEl.innerHTML = '
'
+
+ const scrollSpyEl = fixtureEl.querySelector('div')
+
+ window.dispatchEvent(createEvent('load'))
+
+ expect(ScrollSpy.getInstance(scrollSpyEl)).not.toBeNull()
+ })
+ })
+})
diff --git a/js/tests/unit/tab.js b/js/tests/unit/tab.js
deleted file mode 100644
index 4491e14948c4..000000000000
--- a/js/tests/unit/tab.js
+++ /dev/null
@@ -1,518 +0,0 @@
-$(function () {
- 'use strict'
-
- QUnit.module('tabs plugin')
-
- QUnit.test('should be defined on jquery object', function (assert) {
- assert.expect(1)
- assert.ok($(document.body).tab, 'tabs method is defined')
- })
-
- QUnit.module('tabs', {
- beforeEach: function () {
- // Run all tests in noConflict mode -- it's the only way to ensure that the plugin works in noConflict mode
- $.fn.bootstrapTab = $.fn.tab.noConflict()
- },
- afterEach: function () {
- $.fn.tab = $.fn.bootstrapTab
- delete $.fn.bootstrapTab
- $('#qunit-fixture').html('')
- }
- })
-
- QUnit.test('should provide no conflict', function (assert) {
- assert.expect(1)
- assert.strictEqual(typeof $.fn.tab, 'undefined', 'tab was set back to undefined (org value)')
- })
-
- QUnit.test('should throw explicit error on undefined method', function (assert) {
- assert.expect(1)
- var $el = $('
')
- $el.bootstrapTab()
- try {
- $el.bootstrapTab('noMethod')
- } catch (err) {
- assert.strictEqual(err.message, 'No method named "noMethod"')
- }
- })
-
- QUnit.test('should return jquery collection containing the element', function (assert) {
- assert.expect(2)
- var $el = $('
')
- var $tab = $el.bootstrapTab()
- assert.ok($tab instanceof $, 'returns jquery collection')
- assert.strictEqual($tab[0], $el[0], 'collection contains element')
- })
-
- QUnit.test('should activate element by tab id', function (assert) {
- assert.expect(2)
- var tabsHTML = '
'
-
- $('
').appendTo('#qunit-fixture')
-
- $(tabsHTML).find('li:last-child a').bootstrapTab('show')
- assert.strictEqual($('#qunit-fixture').find('.active').attr('id'), 'profile')
-
- $(tabsHTML).find('li:first-child a').bootstrapTab('show')
- assert.strictEqual($('#qunit-fixture').find('.active').attr('id'), 'home')
- })
-
- QUnit.test('should activate element by tab id', function (assert) {
- assert.expect(2)
- var pillsHTML = '
'
-
- $('
').appendTo('#qunit-fixture')
-
- $(pillsHTML).find('li:last-child a').bootstrapTab('show')
- assert.strictEqual($('#qunit-fixture').find('.active').attr('id'), 'profile')
-
- $(pillsHTML).find('li:first-child a').bootstrapTab('show')
- assert.strictEqual($('#qunit-fixture').find('.active').attr('id'), 'home')
- })
-
- QUnit.test('should activate element by tab id in ordered list', function (assert) {
- assert.expect(2)
- var pillsHTML = '
' +
- '- Home
' +
- '- Profile
' +
- '
'
-
- $('
').appendTo('#qunit-fixture')
-
- $(pillsHTML).find('li:last-child a').bootstrapTab('show')
- assert.strictEqual($('#qunit-fixture').find('.active').attr('id'), 'profile')
-
- $(pillsHTML).find('li:first-child a').bootstrapTab('show')
- assert.strictEqual($('#qunit-fixture').find('.active').attr('id'), 'home')
- })
-
- QUnit.test('should activate element by tab id in nav list', function (assert) {
- assert.expect(2)
- var tabsHTML = '
' +
- 'Home' +
- 'Profile' +
- ''
-
- $('
').appendTo('#qunit-fixture')
-
- $(tabsHTML).find('a:last-child').bootstrapTab('show')
- assert.strictEqual($('#qunit-fixture').find('.active').attr('id'), 'profile')
-
- $(tabsHTML).find('a:first-child').bootstrapTab('show')
- assert.strictEqual($('#qunit-fixture').find('.active').attr('id'), 'home')
- })
-
- QUnit.test('should activate element by tab id in list group', function (assert) {
- assert.expect(2)
- var tabsHTML = '
'
-
- $('
').appendTo('#qunit-fixture')
-
- $(tabsHTML).find('a:last-child').bootstrapTab('show')
- assert.strictEqual($('#qunit-fixture').find('.active').attr('id'), 'profile')
-
- $(tabsHTML).find('a:first-child').bootstrapTab('show')
- assert.strictEqual($('#qunit-fixture').find('.active').attr('id'), 'home')
- })
-
- QUnit.test('should not fire shown when show is prevented', function (assert) {
- assert.expect(1)
- var done = assert.async()
-
- $('
')
- .on('show.bs.tab', function (e) {
- e.preventDefault()
- assert.ok(true, 'show event fired')
- done()
- })
- .on('shown.bs.tab', function () {
- assert.ok(false, 'shown event fired')
- })
- .bootstrapTab('show')
- })
-
- QUnit.test('should not fire shown when tab is already active', function (assert) {
- assert.expect(0)
- var tabsHTML = '
' +
- '
'
-
- $(tabsHTML)
- .find('a.active')
- .on('shown.bs.tab', function () {
- assert.ok(true, 'shown event fired')
- })
- .bootstrapTab('show')
- })
-
- QUnit.test('should not fire shown when tab is disabled', function (assert) {
- assert.expect(0)
- var tabsHTML = '
' +
- '
'
-
- $(tabsHTML)
- .find('a.disabled')
- .on('shown.bs.tab', function () {
- assert.ok(true, 'shown event fired')
- })
- .bootstrapTab('show')
- })
-
- QUnit.test('show and shown events should reference correct relatedTarget', function (assert) {
- assert.expect(2)
- var done = assert.async()
-
- var dropHTML =
- '
' +
- ' - 1' +
- ' ' +
- '
' +
- '
'
-
- $(dropHTML)
- .find('ul > li:first-child a')
- .bootstrapTab('show')
- .end()
- .find('ul > li:last-child a')
- .on('show.bs.tab', function (e) {
- assert.strictEqual(e.relatedTarget.hash, '#a1-1', 'references correct element as relatedTarget')
- })
- .on('shown.bs.tab', function (e) {
- assert.strictEqual(e.relatedTarget.hash, '#a1-1', 'references correct element as relatedTarget')
- done()
- })
- .bootstrapTab('show')
- })
-
- QUnit.test('should fire hide and hidden events', function (assert) {
- assert.expect(2)
- var done = assert.async()
-
- var tabsHTML = '
'
-
- $(tabsHTML)
- .find('li:first-child a')
- .on('hide.bs.tab', function () {
- assert.ok(true, 'hide event fired')
- })
- .bootstrapTab('show')
- .end()
- .find('li:last-child a')
- .bootstrapTab('show')
-
- $(tabsHTML)
- .find('li:first-child a')
- .on('hidden.bs.tab', function () {
- assert.ok(true, 'hidden event fired')
- done()
- })
- .bootstrapTab('show')
- .end()
- .find('li:last-child a')
- .bootstrapTab('show')
- })
-
- QUnit.test('should not fire hidden when hide is prevented', function (assert) {
- assert.expect(1)
- var done = assert.async()
-
- var tabsHTML = '
'
-
- $(tabsHTML)
- .find('li:first-child a')
- .on('hide.bs.tab', function (e) {
- e.preventDefault()
- assert.ok(true, 'hide event fired')
- done()
- })
- .on('hidden.bs.tab', function () {
- assert.ok(false, 'hidden event fired')
- })
- .bootstrapTab('show')
- .end()
- .find('li:last-child a')
- .bootstrapTab('show')
- })
-
- QUnit.test('hide and hidden events contain correct relatedTarget', function (assert) {
- assert.expect(2)
- var done = assert.async()
-
- var tabsHTML = '
'
-
- $(tabsHTML)
- .find('li:first-child a')
- .on('hide.bs.tab', function (e) {
- assert.strictEqual(e.relatedTarget.hash, '#profile', 'references correct element as relatedTarget')
- })
- .on('hidden.bs.tab', function (e) {
- assert.strictEqual(e.relatedTarget.hash, '#profile', 'references correct element as relatedTarget')
- done()
- })
- .bootstrapTab('show')
- .end()
- .find('li:last-child a')
- .bootstrapTab('show')
- })
-
- QUnit.test('selected tab should have aria-selected', function (assert) {
- assert.expect(8)
- var tabsHTML = '
'
- var $tabs = $(tabsHTML).appendTo('#qunit-fixture')
-
- $tabs.find('li:first-child a').bootstrapTab('show')
- assert.strictEqual($tabs.find('.active').attr('aria-selected'), 'true', 'shown tab has aria-selected = true')
- assert.strictEqual($tabs.find('a:not(.active)').attr('aria-selected'), 'false', 'hidden tab has aria-selected = false')
-
- $tabs.find('li:last-child a').trigger('click')
- assert.strictEqual($tabs.find('.active').attr('aria-selected'), 'true', 'after click, shown tab has aria-selected = true')
- assert.strictEqual($tabs.find('a:not(.active)').attr('aria-selected'), 'false', 'after click, hidden tab has aria-selected = false')
-
- $tabs.find('li:first-child a').bootstrapTab('show')
- assert.strictEqual($tabs.find('.active').attr('aria-selected'), 'true', 'shown tab has aria-selected = true')
- assert.strictEqual($tabs.find('a:not(.active)').attr('aria-selected'), 'false', 'hidden tab has aria-selected = false')
-
- $tabs.find('li:first-child a').trigger('click')
- assert.strictEqual($tabs.find('.active').attr('aria-selected'), 'true', 'after second show event, shown tab still has aria-selected = true')
- assert.strictEqual($tabs.find('a:not(.active)').attr('aria-selected'), 'false', 'after second show event, hidden tab has aria-selected = false')
- })
-
- QUnit.test('selected tab should deactivate previous selected tab', function (assert) {
- assert.expect(2)
- var tabsHTML = '
'
- var $tabs = $(tabsHTML).appendTo('#qunit-fixture')
-
- $tabs.find('li:last-child a').trigger('click')
- assert.notOk($tabs.find('li:first-child a').hasClass('active'))
- assert.ok($tabs.find('li:last-child a').hasClass('active'))
- })
-
- QUnit.test('selected tab should deactivate previous selected link in dropdown', function (assert) {
- assert.expect(3)
- var tabsHTML = '
'
- var $tabs = $(tabsHTML).appendTo('#qunit-fixture')
-
- $tabs.find('li:first-child a').trigger('click')
- assert.ok($tabs.find('li:first-child a').hasClass('active'))
- assert.notOk($tabs.find('li:last-child a').hasClass('active'))
- assert.notOk($tabs.find('li:last-child .dropdown-menu a:first-child').hasClass('active'))
- })
-
- QUnit.test('Nested tabs', function (assert) {
- assert.expect(2)
- var done = assert.async()
- var tabsHTML =
- '
' +
- ' Tab 1' +
- ' Tab 2' +
- ' Tab 3' +
- '' +
- '
' +
- '
' +
- '
' +
- ' Nested Tab 1' +
- ' Nested Tab2' +
- ' ' +
- '
' +
- '
Nested Tab1 Content
' +
- '
Nested Tab2 Content
' +
- '
' +
- '
' +
- '
Tab2 Content
' +
- '
Tab3 Content
' +
- '
'
-
- $(tabsHTML).appendTo('#qunit-fixture')
-
- $('#tabNested2').on('shown.bs.tab', function () {
- assert.ok($('#x-tab1').hasClass('active'))
- done()
- })
-
- $('#tab1').on('shown.bs.tab', function () {
- assert.ok($('#x-tab1').hasClass('active'))
- $('#tabNested2').trigger($.Event('click'))
- })
- .trigger($.Event('click'))
- })
-
- QUnit.test('should not remove fade class if no active pane is present', function (assert) {
- assert.expect(6)
- var done = assert.async()
- var tabsHTML = '
' +
- '
'
-
- $(tabsHTML).appendTo('#qunit-fixture')
- $('#tab-profile')
- .on('shown.bs.tab', function () {
- assert.ok($('#profile').hasClass('fade'))
- assert.ok($('#profile').hasClass('show'))
-
- $('#tab-home')
- .on('shown.bs.tab', function () {
- assert.ok($('#profile').hasClass('fade'))
- assert.notOk($('#profile').hasClass('show'))
- assert.ok($('#home').hasClass('fade'))
- assert.ok($('#home').hasClass('show'))
-
- done()
- })
- .trigger($.Event('click'))
- })
- .trigger($.Event('click'))
- })
-
- QUnit.test('should handle removed tabs', function (assert) {
- assert.expect(1)
- var done = assert.async()
-
- var html = [
- '
',
- ' - ',
- ' ',
- ' ',
- ' ',
- '
',
- ' - ',
- ' ',
- ' ',
- ' ',
- '
',
- ' - ',
- ' ',
- ' ',
- ' ',
- '
',
- '
',
- '
',
- '
test 1
',
- '
test 2
',
- '
test 3
',
- '
'
- ].join('')
-
- $(html).appendTo('#qunit-fixture')
-
- $('#secondNav').on('shown.bs.tab', function () {
- assert.strictEqual($('.nav-tab').length, 2)
- done()
- })
-
- $('#btnClose').one('click', function () {
- var tabId = $(this).parents('a').attr('href')
- $(this).parents('li').remove()
- $(tabId).remove()
- $('.nav-tabs a:last').bootstrapTab('show')
- })
- .trigger($.Event('click'))
- })
-
- QUnit.test('should not add show class to tab panes if there is no `.fade` class', function (assert) {
- assert.expect(1)
- var done = assert.async()
-
- var html = [
- '
',
- ' - ',
- ' Home',
- '
',
- ' - ',
- ' Profile',
- '
',
- '
',
- '
',
- '
test 1
',
- '
test 2
',
- '
'
- ].join('')
-
- $(html).appendTo('#qunit-fixture')
-
- $('#secondNav').on('shown.bs.tab', function () {
- assert.strictEqual($('.show').length, 0)
- done()
- })
- .trigger($.Event('click'))
- })
-
- QUnit.test('should add show class to tab panes if there is a `.fade` class', function (assert) {
- assert.expect(1)
- var done = assert.async()
-
- var html = [
- '
',
- ' - ',
- ' Home',
- '
',
- ' - ',
- ' Profile',
- '
',
- '
',
- '
',
- '
test 1
',
- '
test 2
',
- '
'
- ].join('')
-
- $(html).appendTo('#qunit-fixture')
-
- $('#secondNav').on('shown.bs.tab', function () {
- assert.strictEqual($('.show').length, 1)
- done()
- })
- .trigger($.Event('click'))
- })
-})
diff --git a/js/tests/unit/tab.spec.js b/js/tests/unit/tab.spec.js
new file mode 100644
index 000000000000..a08fec20d6a6
--- /dev/null
+++ b/js/tests/unit/tab.spec.js
@@ -0,0 +1,599 @@
+import Tab from '../../src/tab'
+
+/** Test helpers */
+import { getFixture, clearFixture, jQueryMock } from '../helpers/fixture'
+
+describe('Tab', () => {
+ let fixtureEl
+
+ beforeAll(() => {
+ fixtureEl = getFixture()
+ })
+
+ afterEach(() => {
+ clearFixture()
+ })
+
+ describe('VERSION', () => {
+ it('should return plugin version', () => {
+ expect(Tab.VERSION).toEqual(jasmine.any(String))
+ })
+ })
+
+ describe('show', () => {
+ it('should activate element by tab id', done => {
+ fixtureEl.innerHTML = [
+ '
',
+ '
'
+ ].join('')
+
+ const profileTriggerEl = fixtureEl.querySelector('#triggerProfile')
+ const tab = new Tab(profileTriggerEl)
+
+ profileTriggerEl.addEventListener('shown.bs.tab', () => {
+ expect(fixtureEl.querySelector('#profile').classList.contains('active')).toEqual(true)
+ expect(profileTriggerEl.getAttribute('aria-selected')).toEqual('true')
+ done()
+ })
+
+ tab.show()
+ })
+
+ it('should activate element by tab id in ordered list', done => {
+ fixtureEl.innerHTML = [
+ '
',
+ ' - Home
',
+ ' - Profile
',
+ '
',
+ '
',
+ ' ',
+ ' ',
+ '
'
+ ].join('')
+
+ const profileTriggerEl = fixtureEl.querySelector('#triggerProfile')
+ const tab = new Tab(profileTriggerEl)
+
+ profileTriggerEl.addEventListener('shown.bs.tab', () => {
+ expect(fixtureEl.querySelector('#profile').classList.contains('active')).toEqual(true)
+ done()
+ })
+
+ tab.show()
+ })
+
+ it('should activate element by tab id in nav list', done => {
+ fixtureEl.innerHTML = [
+ '
',
+ ' Home',
+ ' Profile',
+ '',
+ '
'
+ ].join('')
+
+ const profileTriggerEl = fixtureEl.querySelector('#triggerProfile')
+ const tab = new Tab(profileTriggerEl)
+
+ profileTriggerEl.addEventListener('shown.bs.tab', () => {
+ expect(fixtureEl.querySelector('#profile').classList.contains('active')).toEqual(true)
+ done()
+ })
+
+ tab.show()
+ })
+
+ it('should activate element by tab id in list group', done => {
+ fixtureEl.innerHTML = [
+ '
',
+ '
'
+ ].join('')
+
+ const profileTriggerEl = fixtureEl.querySelector('#triggerProfile')
+ const tab = new Tab(profileTriggerEl)
+
+ profileTriggerEl.addEventListener('shown.bs.tab', () => {
+ expect(fixtureEl.querySelector('#profile').classList.contains('active')).toEqual(true)
+ done()
+ })
+
+ tab.show()
+ })
+
+ it('should not fire shown when show is prevented', done => {
+ fixtureEl.innerHTML = '
'
+
+ const navEl = fixtureEl.querySelector('div')
+ const tab = new Tab(navEl)
+ const expectDone = () => {
+ setTimeout(() => {
+ expect().nothing()
+ done()
+ }, 30)
+ }
+
+ navEl.addEventListener('show.bs.tab', ev => {
+ ev.preventDefault()
+ expectDone()
+ })
+
+ navEl.addEventListener('shown.bs.tab', () => {
+ throw new Error('should not trigger shown event')
+ })
+
+ tab.show()
+ })
+
+ it('should not fire shown when tab is already active', done => {
+ fixtureEl.innerHTML = [
+ '
',
+ '
'
+ ].join('')
+
+ const triggerActive = fixtureEl.querySelector('a.active')
+ const tab = new Tab(triggerActive)
+
+ triggerActive.addEventListener('shown.bs.tab', () => {
+ throw new Error('should not trigger shown event')
+ })
+
+ tab.show()
+ setTimeout(() => {
+ expect().nothing()
+ done()
+ }, 30)
+ })
+
+ it('should not fire shown when tab is disabled', done => {
+ fixtureEl.innerHTML = [
+ '
',
+ '
'
+ ].join('')
+
+ const triggerDisabled = fixtureEl.querySelector('a.disabled')
+ const tab = new Tab(triggerDisabled)
+
+ triggerDisabled.addEventListener('shown.bs.tab', () => {
+ throw new Error('should not trigger shown event')
+ })
+
+ tab.show()
+ setTimeout(() => {
+ expect().nothing()
+ done()
+ }, 30)
+ })
+
+ it('show and shown events should reference correct relatedTarget', done => {
+ fixtureEl.innerHTML = [
+ '
',
+ '
'
+ ].join('')
+
+ const secondTabTrigger = fixtureEl.querySelector('#triggerProfile')
+ const secondTab = new Tab(secondTabTrigger)
+
+ secondTabTrigger.addEventListener('show.bs.tab', ev => {
+ expect(ev.relatedTarget.hash).toEqual('#home')
+ })
+
+ secondTabTrigger.addEventListener('shown.bs.tab', ev => {
+ expect(ev.relatedTarget.hash).toEqual('#home')
+ expect(secondTabTrigger.getAttribute('aria-selected')).toEqual('true')
+ expect(fixtureEl.querySelector('a:not(.active)').getAttribute('aria-selected')).toEqual('false')
+ done()
+ })
+
+ secondTab.show()
+ })
+
+ it('should fire hide and hidden events', done => {
+ fixtureEl.innerHTML = [
+ '
'
+ ].join('')
+
+ const triggerList = fixtureEl.querySelectorAll('a')
+ const firstTab = new Tab(triggerList[0])
+ const secondTab = new Tab(triggerList[1])
+
+ let hideCalled = false
+ triggerList[0].addEventListener('shown.bs.tab', () => {
+ secondTab.show()
+ })
+
+ triggerList[0].addEventListener('hide.bs.tab', ev => {
+ hideCalled = true
+ expect(ev.relatedTarget.hash).toEqual('#profile')
+ })
+
+ triggerList[0].addEventListener('hidden.bs.tab', ev => {
+ expect(hideCalled).toEqual(true)
+ expect(ev.relatedTarget.hash).toEqual('#profile')
+ done()
+ })
+
+ firstTab.show()
+ })
+
+ it('should not fire hidden when hide is prevented', done => {
+ fixtureEl.innerHTML = [
+ '
'
+ ].join('')
+
+ const triggerList = fixtureEl.querySelectorAll('a')
+ const firstTab = new Tab(triggerList[0])
+ const secondTab = new Tab(triggerList[1])
+ const expectDone = () => {
+ setTimeout(() => {
+ expect().nothing()
+ done()
+ }, 30)
+ }
+
+ triggerList[0].addEventListener('shown.bs.tab', () => {
+ secondTab.show()
+ })
+
+ triggerList[0].addEventListener('hide.bs.tab', ev => {
+ ev.preventDefault()
+ expectDone()
+ })
+
+ triggerList[0].addEventListener('hidden.bs.tab', () => {
+ throw new Error('should not trigger hidden')
+ })
+
+ firstTab.show()
+ })
+
+ it('should handle removed tabs', done => {
+ fixtureEl.innerHTML = [
+ '
',
+ ' - ',
+ ' ',
+ ' ',
+ ' ',
+ '
',
+ ' - ',
+ ' ',
+ ' ',
+ ' ',
+ '
',
+ ' - ',
+ ' ',
+ ' ',
+ ' ',
+ '
',
+ '
',
+ '
',
+ '
test 1
',
+ '
test 2
',
+ '
test 3
',
+ '
'
+ ].join('')
+
+ const secondNavEl = fixtureEl.querySelector('#secondNav')
+ const btnCloseEl = fixtureEl.querySelector('#btnClose')
+ const secondNavTab = new Tab(secondNavEl)
+
+ secondNavEl.addEventListener('shown.bs.tab', () => {
+ expect(fixtureEl.querySelectorAll('.nav-tab').length).toEqual(2)
+ done()
+ })
+
+ btnCloseEl.addEventListener('click', () => {
+ const linkEl = btnCloseEl.parentNode
+ const liEl = linkEl.parentNode
+ const tabId = linkEl.getAttribute('href')
+ const tabIdEl = fixtureEl.querySelector(tabId)
+
+ liEl.parentNode.removeChild(liEl)
+ tabIdEl.parentNode.removeChild(tabIdEl)
+ secondNavTab.show()
+ })
+
+ btnCloseEl.click()
+ })
+ })
+
+ describe('dispose', () => {
+ it('should dispose a tab', () => {
+ fixtureEl.innerHTML = '
'
+
+ const el = fixtureEl.querySelector('div')
+ const tab = new Tab(fixtureEl.querySelector('div'))
+
+ expect(Tab.getInstance(el)).not.toBeNull()
+
+ tab.dispose()
+
+ expect(Tab.getInstance(el)).toBeNull()
+ })
+ })
+
+ describe('jQueryInterface', () => {
+ it('should create a tab', () => {
+ fixtureEl.innerHTML = '
'
+
+ const div = fixtureEl.querySelector('div')
+
+ jQueryMock.fn.tab = Tab.jQueryInterface
+ jQueryMock.elements = [div]
+
+ jQueryMock.fn.tab.call(jQueryMock)
+
+ expect(Tab.getInstance(div)).toBeDefined()
+ })
+
+ it('should not re create a tab', () => {
+ fixtureEl.innerHTML = '
'
+
+ const div = fixtureEl.querySelector('div')
+ const tab = new Tab(div)
+
+ jQueryMock.fn.tab = Tab.jQueryInterface
+ jQueryMock.elements = [div]
+
+ jQueryMock.fn.tab.call(jQueryMock)
+
+ expect(Tab.getInstance(div)).toEqual(tab)
+ })
+
+ it('should call a tab method', () => {
+ fixtureEl.innerHTML = '
'
+
+ const div = fixtureEl.querySelector('div')
+ const tab = new Tab(div)
+
+ spyOn(tab, 'show')
+
+ jQueryMock.fn.tab = Tab.jQueryInterface
+ jQueryMock.elements = [div]
+
+ jQueryMock.fn.tab.call(jQueryMock, 'show')
+
+ expect(Tab.getInstance(div)).toEqual(tab)
+ expect(tab.show).toHaveBeenCalled()
+ })
+
+ it('should throw error on undefined method', () => {
+ fixtureEl.innerHTML = '
'
+
+ const div = fixtureEl.querySelector('div')
+ const action = 'undefinedMethod'
+
+ jQueryMock.fn.tab = Tab.jQueryInterface
+ jQueryMock.elements = [div]
+
+ try {
+ jQueryMock.fn.tab.call(jQueryMock, action)
+ } catch (error) {
+ expect(error.message).toEqual(`No method named "${action}"`)
+ }
+ })
+ })
+
+ describe('getInstance', () => {
+ it('should return null if there is no instance', () => {
+ expect(Tab.getInstance(fixtureEl)).toEqual(null)
+ })
+
+ it('should return this instance', () => {
+ fixtureEl.innerHTML = '
'
+
+ const divEl = fixtureEl.querySelector('div')
+ const tab = new Tab(divEl)
+
+ expect(Tab.getInstance(divEl)).toEqual(tab)
+ })
+ })
+
+ describe('data-api', () => {
+ it('should create dynamically a tab', done => {
+ fixtureEl.innerHTML = [
+ '
',
+ '
'
+ ].join('')
+
+ const secondTabTrigger = fixtureEl.querySelector('#triggerProfile')
+
+ secondTabTrigger.addEventListener('shown.bs.tab', () => {
+ expect(secondTabTrigger.classList.contains('active')).toEqual(true)
+ expect(fixtureEl.querySelector('#profile').classList.contains('active')).toEqual(true)
+ done()
+ })
+
+ secondTabTrigger.click()
+ })
+
+ it('selected tab should deactivate previous selected link in dropdown', () => {
+ fixtureEl.innerHTML = [
+ '
'
+ ].join('')
+
+ const firstLiLinkEl = fixtureEl.querySelector('li:first-child a')
+
+ firstLiLinkEl.click()
+ expect(firstLiLinkEl.classList.contains('active')).toEqual(true)
+ expect(fixtureEl.querySelector('li:last-child a').classList.contains('active')).toEqual(false)
+ expect(fixtureEl.querySelector('li:last-child .dropdown-menu a:first-child').classList.contains('active')).toEqual(false)
+ })
+
+ it('should handle nested tabs', done => {
+ fixtureEl.innerHTML = [
+ '
',
+ ' Tab 1',
+ ' Tab 2',
+ ' Tab 3',
+ '',
+ '
',
+ '
',
+ '
',
+ ' Nested Tab 1',
+ ' Nested Tab2',
+ ' ',
+ '
',
+ '
Nested Tab1 Content
',
+ '
Nested Tab2 Content
',
+ '
',
+ '
',
+ '
Tab2 Content
',
+ '
Tab3 Content
',
+ '
'
+ ].join('')
+
+ const tab1El = fixtureEl.querySelector('#tab1')
+ const tabNested2El = fixtureEl.querySelector('#tabNested2')
+ const xTab1El = fixtureEl.querySelector('#x-tab1')
+
+ tabNested2El.addEventListener('shown.bs.tab', () => {
+ expect(xTab1El.classList.contains('active')).toEqual(true)
+ done()
+ })
+
+ tab1El.addEventListener('shown.bs.tab', () => {
+ expect(xTab1El.classList.contains('active')).toEqual(true)
+ tabNested2El.click()
+ })
+
+ tab1El.click()
+ })
+
+ it('should not remove fade class if no active pane is present', done => {
+ fixtureEl.innerHTML = [
+ '
',
+ '
'
+ ].join('')
+
+ const triggerTabProfileEl = fixtureEl.querySelector('#tab-profile')
+ const triggerTabHomeEl = fixtureEl.querySelector('#tab-home')
+ const tabProfileEl = fixtureEl.querySelector('#profile')
+ const tabHomeEl = fixtureEl.querySelector('#home')
+
+ triggerTabProfileEl.addEventListener('shown.bs.tab', () => {
+ expect(tabProfileEl.classList.contains('fade')).toEqual(true)
+ expect(tabProfileEl.classList.contains('show')).toEqual(true)
+
+ triggerTabHomeEl.addEventListener('shown.bs.tab', () => {
+ expect(tabProfileEl.classList.contains('fade')).toEqual(true)
+ expect(tabProfileEl.classList.contains('show')).toEqual(false)
+
+ expect(tabHomeEl.classList.contains('fade')).toEqual(true)
+ expect(tabHomeEl.classList.contains('show')).toEqual(true)
+
+ done()
+ })
+
+ triggerTabHomeEl.click()
+ })
+
+ triggerTabProfileEl.click()
+ })
+
+ it('should not add show class to tab panes if there is no `.fade` class', done => {
+ fixtureEl.innerHTML = [
+ '
',
+ ' - ',
+ ' Home',
+ '
',
+ ' - ',
+ ' Profile',
+ '
',
+ '
',
+ '
',
+ '
test 1
',
+ '
test 2
',
+ '
'
+ ].join('')
+
+ const secondNavEl = fixtureEl.querySelector('#secondNav')
+
+ secondNavEl.addEventListener('shown.bs.tab', () => {
+ expect(fixtureEl.querySelectorAll('.show').length).toEqual(0)
+ done()
+ })
+
+ secondNavEl.click()
+ })
+
+ it('should add show class to tab panes if there is a `.fade` class', done => {
+ fixtureEl.innerHTML = [
+ '
',
+ ' - ',
+ ' Home',
+ '
',
+ ' - ',
+ ' Profile',
+ '
',
+ '
',
+ '
',
+ '
test 1
',
+ '
test 2
',
+ '
'
+ ].join('')
+
+ const secondNavEl = fixtureEl.querySelector('#secondNav')
+
+ secondNavEl.addEventListener('shown.bs.tab', () => {
+ expect(fixtureEl.querySelectorAll('.show').length).toEqual(1)
+ done()
+ })
+
+ secondNavEl.click()
+ })
+ })
+})
diff --git a/js/tests/unit/toast.js b/js/tests/unit/toast.js
deleted file mode 100644
index 2081693ebce6..000000000000
--- a/js/tests/unit/toast.js
+++ /dev/null
@@ -1,259 +0,0 @@
-$(function () {
- 'use strict'
-
- if (typeof bootstrap !== 'undefined') {
- window.Toast = bootstrap.Toast
- }
-
- QUnit.module('toast plugin')
-
- QUnit.test('should be defined on jquery object', function (assert) {
- assert.expect(1)
- assert.ok($(document.body).toast, 'toast method is defined')
- })
-
- QUnit.module('toast', {
- beforeEach: function () {
- // Run all tests in noConflict mode -- it's the only way to ensure that the plugin works in noConflict mode
- $.fn.bootstrapToast = $.fn.toast.noConflict()
- },
- afterEach: function () {
- $.fn.toast = $.fn.bootstrapToast
- delete $.fn.bootstrapToast
- $('#qunit-fixture').html('')
- }
- })
-
- QUnit.test('should provide no conflict', function (assert) {
- assert.expect(1)
- assert.strictEqual(typeof $.fn.toast, 'undefined', 'toast was set back to undefined (org value)')
- })
-
- QUnit.test('should return the current version', function (assert) {
- assert.expect(1)
- assert.strictEqual(typeof Toast.VERSION, 'string')
- })
-
- QUnit.test('should throw explicit error on undefined method', function (assert) {
- assert.expect(1)
- var $el = $('
')
- $el.bootstrapToast()
-
- try {
- $el.bootstrapToast('noMethod')
- } catch (err) {
- assert.strictEqual(err.message, 'No method named "noMethod"')
- }
- })
-
- QUnit.test('should return jquery collection containing the element', function (assert) {
- assert.expect(2)
-
- var $el = $('
')
- var $toast = $el.bootstrapToast()
- assert.ok($toast instanceof $, 'returns jquery collection')
- assert.strictEqual($toast[0], $el[0], 'collection contains element')
- })
-
- QUnit.test('should auto hide', function (assert) {
- assert.expect(1)
- var done = assert.async()
-
- var toastHtml =
- '
' +
- '
' +
- 'a simple toast' +
- '
' +
- '
'
-
- var $toast = $(toastHtml)
- .bootstrapToast()
- .appendTo($('#qunit-fixture'))
-
- $toast.on('hidden.bs.toast', function () {
- assert.strictEqual($toast.hasClass('show'), false)
- done()
- })
- .bootstrapToast('show')
- })
-
- QUnit.test('should not add fade class', function (assert) {
- assert.expect(1)
- var done = assert.async()
-
- var toastHtml =
- '
' +
- '
' +
- 'a simple toast' +
- '
' +
- '
'
-
- var $toast = $(toastHtml)
- .bootstrapToast()
- .appendTo($('#qunit-fixture'))
-
- $toast.on('shown.bs.toast', function () {
- assert.strictEqual($toast.hasClass('fade'), false)
- done()
- })
- .bootstrapToast('show')
- })
-
- QUnit.test('should allow to hide toast manually', function (assert) {
- assert.expect(1)
- var done = assert.async()
-
- var toastHtml =
- '
' +
- '
' +
- 'a simple toast' +
- '
' +
- '
'
-
- var $toast = $(toastHtml)
- .bootstrapToast()
- .appendTo($('#qunit-fixture'))
-
- $toast
- .on('shown.bs.toast', function () {
- $toast.bootstrapToast('hide')
- })
- .on('hidden.bs.toast', function () {
- assert.strictEqual($toast.hasClass('show'), false)
- done()
- })
- .bootstrapToast('show')
- })
-
- QUnit.test('should do nothing when we call hide on a non shown toast', function (assert) {
- assert.expect(1)
-
- var $toast = $('
')
- .bootstrapToast()
- .appendTo($('#qunit-fixture'))
-
- var spy = sinon.spy($toast[0].classList, 'contains')
-
- $toast.bootstrapToast('hide')
-
- assert.strictEqual(spy.called, true)
- })
-
- QUnit.test('should allow to destroy toast', function (assert) {
- assert.expect(2)
-
- var $toast = $('
')
- .bootstrapToast()
- .appendTo($('#qunit-fixture'))
-
- assert.ok(typeof $toast.data('bs.toast') !== 'undefined')
-
- $toast.bootstrapToast('dispose')
-
- assert.ok(typeof $toast.data('bs.toast') === 'undefined')
- })
-
- QUnit.test('should allow to destroy toast and hide it before that', function (assert) {
- assert.expect(4)
- var done = assert.async()
-
- var toastHtml =
- '
' +
- '
' +
- 'a simple toast' +
- '
' +
- '
'
-
- var $toast = $(toastHtml)
- .bootstrapToast()
- .appendTo($('#qunit-fixture'))
-
- $toast.one('shown.bs.toast', function () {
- setTimeout(function () {
- assert.ok($toast.hasClass('show'))
- assert.ok(typeof $toast.data('bs.toast') !== 'undefined')
-
- $toast.bootstrapToast('dispose')
-
- assert.ok(typeof $toast.data('bs.toast') === 'undefined')
- assert.ok($toast.hasClass('show') === false)
-
- done()
- }, 1)
- })
- .bootstrapToast('show')
- })
-
- QUnit.test('should allow to config in js', function (assert) {
- assert.expect(1)
- var done = assert.async()
-
- var toastHtml =
- '
' +
- '
' +
- 'a simple toast' +
- '
' +
- '
'
-
- var $toast = $(toastHtml)
- .bootstrapToast({
- delay: 1
- })
- .appendTo($('#qunit-fixture'))
-
- $toast.on('shown.bs.toast', function () {
- assert.strictEqual($toast.hasClass('show'), true)
- done()
- })
- .bootstrapToast('show')
- })
-
-
- QUnit.test('should close toast when close element with data-dismiss attribute is set', function (assert) {
- assert.expect(2)
- var done = assert.async()
-
- var toastHtml =
- '
' +
- '' +
- '
'
-
- var $toast = $(toastHtml)
- .bootstrapToast()
- .appendTo($('#qunit-fixture'))
-
- $toast
- .on('shown.bs.toast', function () {
- assert.strictEqual($toast.hasClass('show'), true)
- var button = $toast.find('.close')
- button.trigger('click')
- })
- .on('hidden.bs.toast', function () {
- assert.strictEqual($toast.hasClass('show'), false)
- done()
- })
- .bootstrapToast('show')
- })
-
- QUnit.test('should expose default setting to allow to override them', function (assert) {
- assert.expect(1)
-
- var defaultDelay = 1000
- Toast.Default.delay = defaultDelay
-
- var toastHtml =
- '
' +
- '' +
- '
'
-
- var $toast = $(toastHtml)
- .bootstrapToast()
-
- var toast = $toast.data('bs.toast')
- assert.strictEqual(toast._config.delay, defaultDelay)
- })
-})
diff --git a/js/tests/unit/toast.spec.js b/js/tests/unit/toast.spec.js
new file mode 100644
index 000000000000..031c841afa08
--- /dev/null
+++ b/js/tests/unit/toast.spec.js
@@ -0,0 +1,401 @@
+import Toast from '../../src/toast'
+
+/** Test helpers */
+import { getFixture, clearFixture, 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('constructor', () => {
+ it('should allow to config in js', done => {
+ fixtureEl.innerHTML = [
+ '
',
+ '
',
+ ' a simple toast',
+ '
',
+ '
'
+ ].join('')
+
+ const toastEl = fixtureEl.querySelector('div')
+ const toast = new Toast(toastEl, {
+ delay: 1
+ })
+
+ toastEl.addEventListener('shown.bs.toast', () => {
+ expect(toastEl.classList.contains('show')).toEqual(true)
+ done()
+ })
+
+ toast.show()
+ })
+
+ it('should close toast when close element with data-dismiss attribute is set', done => {
+ fixtureEl.innerHTML = [
+ '
',
+ ' ',
+ '
'
+ ].join('')
+
+ const toastEl = fixtureEl.querySelector('div')
+ const toast = new Toast(toastEl)
+
+ toastEl.addEventListener('shown.bs.toast', () => {
+ expect(toastEl.classList.contains('show')).toEqual(true)
+
+ const button = toastEl.querySelector('.close')
+
+ button.click()
+ })
+
+ toastEl.addEventListener('hidden.bs.toast', () => {
+ expect(toastEl.classList.contains('show')).toEqual(false)
+ done()
+ })
+
+ toast.show()
+ })
+ })
+
+ describe('Default', () => {
+ it('should expose default setting to allow to override them', () => {
+ const defaultDelay = 1000
+
+ Toast.Default.delay = defaultDelay
+
+ fixtureEl.innerHTML = [
+ '
',
+ ' ',
+ '
'
+ ].join('')
+
+ const toastEl = fixtureEl.querySelector('div')
+ const toast = new Toast(toastEl)
+
+ expect(toast._config.delay).toEqual(defaultDelay)
+ })
+ })
+
+ describe('DefaultType', () => {
+ it('should expose default setting types for read', () => {
+ expect(Toast.DefaultType).toEqual(jasmine.any(Object))
+ })
+ })
+
+ describe('show', () => {
+ it('should auto hide', done => {
+ fixtureEl.innerHTML = [
+ '
',
+ '
',
+ ' a simple toast',
+ '
',
+ '
'
+ ].join('')
+
+ const toastEl = fixtureEl.querySelector('.toast')
+ const toast = new Toast(toastEl)
+
+ toastEl.addEventListener('hidden.bs.toast', () => {
+ expect(toastEl.classList.contains('show')).toEqual(false)
+ done()
+ })
+
+ toast.show()
+ })
+
+ it('should not add fade class', done => {
+ fixtureEl.innerHTML = [
+ '
',
+ '
',
+ ' a simple toast',
+ '
',
+ '
'
+ ].join('')
+
+ const toastEl = fixtureEl.querySelector('.toast')
+ const toast = new Toast(toastEl)
+
+ toastEl.addEventListener('shown.bs.toast', () => {
+ expect(toastEl.classList.contains('fade')).toEqual(false)
+ done()
+ })
+
+ toast.show()
+ })
+
+ it('should not trigger shown if show is prevented', done => {
+ fixtureEl.innerHTML = [
+ '
',
+ '
',
+ ' a simple toast',
+ '
',
+ '
'
+ ].join('')
+
+ const toastEl = fixtureEl.querySelector('.toast')
+ const toast = new Toast(toastEl)
+
+ const assertDone = () => {
+ setTimeout(() => {
+ expect(toastEl.classList.contains('show')).toEqual(false)
+ done()
+ }, 20)
+ }
+
+ toastEl.addEventListener('show.bs.toast', event => {
+ event.preventDefault()
+ assertDone()
+ })
+
+ toastEl.addEventListener('shown.bs.toast', () => {
+ throw new Error('shown event should not be triggered if show is prevented')
+ })
+
+ toast.show()
+ })
+
+ it('should clear timeout if toast is shown again before it is hidden', done => {
+ fixtureEl.innerHTML = [
+ '
',
+ '
',
+ ' a simple toast',
+ '
',
+ '
'
+ ].join('')
+
+ const toastEl = fixtureEl.querySelector('.toast')
+ const toast = new Toast(toastEl)
+
+ setTimeout(() => {
+ toast._config.autohide = false
+ toastEl.addEventListener('shown.bs.toast', () => {
+ expect(toast._clearTimeout).toHaveBeenCalled()
+ expect(toast._timeout).toBeNull()
+ done()
+ })
+ toast.show()
+ }, toast._config.delay / 2)
+
+ spyOn(toast, '_clearTimeout').and.callThrough()
+
+ toast.show()
+ })
+ })
+
+ describe('hide', () => {
+ it('should allow to hide toast manually', done => {
+ fixtureEl.innerHTML = [
+ '
',
+ '
',
+ ' a simple toast',
+ '
',
+ '
'
+ ].join('')
+
+ const toastEl = fixtureEl.querySelector('.toast')
+ const toast = new Toast(toastEl)
+
+ toastEl.addEventListener('shown.bs.toast', () => {
+ toast.hide()
+ })
+
+ toastEl.addEventListener('hidden.bs.toast', () => {
+ expect(toastEl.classList.contains('show')).toEqual(false)
+ done()
+ })
+
+ toast.show()
+ })
+
+ it('should do nothing when we call hide on a non shown toast', () => {
+ fixtureEl.innerHTML = '
'
+
+ const toastEl = fixtureEl.querySelector('div')
+ const toast = new Toast(toastEl)
+
+ spyOn(toastEl.classList, 'contains')
+
+ toast.hide()
+
+ expect(toastEl.classList.contains).toHaveBeenCalled()
+ })
+
+ it('should not trigger hidden if hide is prevented', done => {
+ fixtureEl.innerHTML = [
+ '
',
+ '
',
+ ' a simple toast',
+ '
',
+ '
'
+ ].join('')
+
+ const toastEl = fixtureEl.querySelector('.toast')
+ const toast = new Toast(toastEl)
+
+ const assertDone = () => {
+ setTimeout(() => {
+ expect(toastEl.classList.contains('show')).toEqual(true)
+ done()
+ }, 20)
+ }
+
+ toastEl.addEventListener('shown.bs.toast', () => {
+ toast.hide()
+ })
+
+ toastEl.addEventListener('hide.bs.toast', event => {
+ event.preventDefault()
+ assertDone()
+ })
+
+ toastEl.addEventListener('hidden.bs.toast', () => {
+ throw new Error('hidden event should not be triggered if hide is prevented')
+ })
+
+ toast.show()
+ })
+ })
+
+ describe('dispose', () => {
+ it('should allow to destroy toast', () => {
+ fixtureEl.innerHTML = '
'
+
+ const toastEl = fixtureEl.querySelector('div')
+ const toast = new Toast(toastEl)
+
+ expect(Toast.getInstance(toastEl)).toBeDefined()
+
+ toast.dispose()
+
+ expect(Toast.getInstance(toastEl)).toBeNull()
+ })
+
+ it('should allow to destroy toast and hide it before that', done => {
+ fixtureEl.innerHTML = [
+ '
',
+ '
',
+ ' a simple toast',
+ '
',
+ '
'
+ ].join('')
+
+ const toastEl = fixtureEl.querySelector('div')
+ const toast = new Toast(toastEl)
+ const expected = () => {
+ expect(toastEl.classList.contains('show')).toEqual(true)
+ expect(Toast.getInstance(toastEl)).toBeDefined()
+
+ toast.dispose()
+
+ expect(Toast.getInstance(toastEl)).toBeNull()
+ expect(toastEl.classList.contains('show')).toEqual(false)
+
+ done()
+ }
+
+ toastEl.addEventListener('shown.bs.toast', () => {
+ setTimeout(expected, 1)
+ })
+
+ toast.show()
+ })
+ })
+
+ describe('jQueryInterface', () => {
+ it('should create a toast', () => {
+ fixtureEl.innerHTML = '
'
+
+ const div = fixtureEl.querySelector('div')
+
+ jQueryMock.fn.toast = Toast.jQueryInterface
+ jQueryMock.elements = [div]
+
+ jQueryMock.fn.toast.call(jQueryMock)
+
+ expect(Toast.getInstance(div)).toBeDefined()
+ })
+
+ it('should not re create a toast', () => {
+ fixtureEl.innerHTML = '
'
+
+ 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 = '
'
+
+ const div = fixtureEl.querySelector('div')
+ const toast = new Toast(div)
+
+ spyOn(toast, 'show')
+
+ jQueryMock.fn.toast = Toast.jQueryInterface
+ jQueryMock.elements = [div]
+
+ jQueryMock.fn.toast.call(jQueryMock, 'show')
+
+ expect(Toast.getInstance(div)).toEqual(toast)
+ expect(toast.show).toHaveBeenCalled()
+ })
+
+ it('should throw error on undefined method', () => {
+ fixtureEl.innerHTML = '
'
+
+ const div = fixtureEl.querySelector('div')
+ const action = 'undefinedMethod'
+
+ jQueryMock.fn.toast = Toast.jQueryInterface
+ jQueryMock.elements = [div]
+
+ try {
+ jQueryMock.fn.toast.call(jQueryMock, action)
+ } catch (error) {
+ expect(error.message).toEqual(`No method named "${action}"`)
+ }
+ })
+ })
+
+ describe('getInstance', () => {
+ it('should return collapse instance', () => {
+ fixtureEl.innerHTML = '
'
+
+ const div = fixtureEl.querySelector('div')
+ const toast = new Toast(div)
+
+ expect(Toast.getInstance(div)).toEqual(toast)
+ })
+
+ it('should return null when there is no collapse instance', () => {
+ fixtureEl.innerHTML = '
'
+
+ const div = fixtureEl.querySelector('div')
+
+ expect(Toast.getInstance(div)).toEqual(null)
+ })
+ })
+})
diff --git a/js/tests/unit/tooltip.js b/js/tests/unit/tooltip.js
deleted file mode 100644
index e66450fb8572..000000000000
--- a/js/tests/unit/tooltip.js
+++ /dev/null
@@ -1,1269 +0,0 @@
-$(function () {
- 'use strict'
-
- QUnit.module('tooltip plugin')
-
- QUnit.test('should be defined on jquery object', function (assert) {
- assert.expect(1)
- assert.ok($(document.body).tooltip, 'tooltip method is defined')
- })
-
- QUnit.module('tooltip', {
- beforeEach: function () {
- // Run all tests in noConflict mode -- it's the only way to ensure that the plugin works in noConflict mode
- $.fn.bootstrapTooltip = $.fn.tooltip.noConflict()
- },
- afterEach: function () {
- $.fn.tooltip = $.fn.bootstrapTooltip
- delete $.fn.bootstrapTooltip
- $('.tooltip').remove()
- $('#qunit-fixture').html('')
- }
- })
-
- QUnit.test('should provide no conflict', function (assert) {
- assert.expect(1)
- assert.strictEqual(typeof $.fn.tooltip, 'undefined', 'tooltip was set back to undefined (org value)')
- })
-
- QUnit.test('should throw explicit error on undefined method', function (assert) {
- assert.expect(1)
- var $el = $('
')
- $el.bootstrapTooltip()
- try {
- $el.bootstrapTooltip('noMethod')
- } catch (err) {
- assert.strictEqual(err.message, 'No method named "noMethod"')
- }
- })
-
- QUnit.test('should return jquery collection containing the element', function (assert) {
- assert.expect(2)
- var $el = $('
')
- var $tooltip = $el.bootstrapTooltip()
- assert.ok($tooltip instanceof $, 'returns jquery collection')
- assert.strictEqual($tooltip[0], $el[0], 'collection contains element')
- })
-
- QUnit.test('should expose default settings', function (assert) {
- assert.expect(1)
- assert.ok($.fn.bootstrapTooltip.Constructor.Default, 'defaults is defined')
- })
-
- QUnit.test('should empty title attribute', function (assert) {
- assert.expect(1)
- var $trigger = $('
').bootstrapTooltip()
- assert.strictEqual($trigger.attr('title'), '', 'title attribute was emptied')
- })
-
- QUnit.test('should add data attribute for referencing original title', function (assert) {
- assert.expect(1)
- var $trigger = $('
').bootstrapTooltip()
- assert.strictEqual($trigger.attr('data-original-title'), 'Another tooltip', 'original title preserved in data attribute')
- })
-
- QUnit.test('should add aria-describedby to the trigger on show', function (assert) {
- assert.expect(3)
- var $trigger = $('
')
- .bootstrapTooltip()
- .appendTo('#qunit-fixture')
- .bootstrapTooltip('show')
-
- var id = $('.tooltip').attr('id')
-
- assert.strictEqual($('#' + id).length, 1, 'has a unique id')
- assert.strictEqual($('.tooltip').attr('aria-describedby'), $trigger.attr('id'), 'tooltip id and aria-describedby on trigger match')
- assert.ok($trigger[0].hasAttribute('aria-describedby'), 'trigger has aria-describedby')
- })
-
- QUnit.test('should remove aria-describedby from trigger on hide', function (assert) {
- assert.expect(2)
- var done = assert.async()
- var $trigger = $('
')
- .bootstrapTooltip()
- .appendTo('#qunit-fixture')
-
- $trigger
- .one('shown.bs.tooltip', function () {
- assert.ok($trigger[0].hasAttribute('aria-describedby'), 'trigger has aria-describedby')
- $trigger.bootstrapTooltip('hide')
- })
- .one('hidden.bs.tooltip', function () {
- assert.ok(!$trigger[0].hasAttribute('aria-describedby'), 'trigger does not have aria-describedby')
- done()
- })
- .bootstrapTooltip('show')
- })
-
- QUnit.test('should assign a unique id tooltip element', function (assert) {
- assert.expect(2)
- $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip('show')
-
- var id = $('.tooltip').attr('id')
-
- assert.strictEqual($('#' + id).length, 1, 'tooltip has unique id')
- assert.strictEqual(id.indexOf('tooltip'), 0, 'tooltip id has prefix')
- })
-
- QUnit.test('should place tooltips relative to placement option', function (assert) {
- assert.expect(2)
- var done = assert.async()
- var $tooltip = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- placement: 'bottom'
- })
-
- $tooltip
- .one('shown.bs.tooltip', function () {
- assert.ok($('.tooltip')
- .is('.fade.bs-tooltip-bottom.show'), 'has correct classes applied')
-
- $tooltip.bootstrapTooltip('hide')
- })
- .one('hidden.bs.tooltip', function () {
- assert.strictEqual($tooltip.data('bs.tooltip').tip.parentNode, null, 'tooltip removed')
- done()
- })
- .bootstrapTooltip('show')
- })
-
- QUnit.test('should allow html entities', function (assert) {
- assert.expect(2)
- var done = assert.async()
- var $tooltip = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- html: true
- })
-
- $tooltip
- .one('shown.bs.tooltip', function () {
- assert.notEqual($('.tooltip b').length, 0, 'b tag was inserted')
- $tooltip.bootstrapTooltip('hide')
- })
- .one('hidden.bs.tooltip', function () {
- assert.strictEqual($tooltip.data('bs.tooltip').tip.parentNode, null, 'tooltip removed')
- done()
- })
- .bootstrapTooltip('show')
- })
-
- QUnit.test('should allow DOMElement title (html: false)', function (assert) {
- assert.expect(3)
- var done = assert.async()
- var title = document.createTextNode('<3 writing tests')
- var $tooltip = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- title: title
- })
-
- $tooltip
- .one('shown.bs.tooltip', function () {
- assert.notEqual($('.tooltip').length, 0, 'tooltip inserted')
- assert.strictEqual($('.tooltip').text(), '<3 writing tests', 'title inserted')
- assert.ok(!$.contains($('.tooltip').get(0), title), 'title node copied, not moved')
- done()
- })
- .bootstrapTooltip('show')
- })
-
- QUnit.test('should allow DOMElement title (html: true)', function (assert) {
- assert.expect(3)
- var done = assert.async()
- var title = document.createTextNode('<3 writing tests')
- var $tooltip = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- html: true,
- title: title
- })
-
- $tooltip
- .one('shown.bs.tooltip', function () {
- assert.notEqual($('.tooltip').length, 0, 'tooltip inserted')
- assert.strictEqual($('.tooltip').text(), '<3 writing tests', 'title inserted')
- assert.ok($.contains($('.tooltip').get(0), title), 'title node moved, not copied')
- done()
- })
- .bootstrapTooltip('show')
- })
-
- QUnit.test('should respect custom classes', function (assert) {
- assert.expect(2)
- var done = assert.async()
- var $tooltip = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- template: '
'
- })
-
- $tooltip
- .one('shown.bs.tooltip', function () {
- assert.ok($('.tooltip').hasClass('some-class'), 'custom class is present')
- $tooltip.bootstrapTooltip('hide')
- })
- .one('hidden.bs.tooltip', function () {
- assert.strictEqual($tooltip.data('bs.tooltip').tip.parentNode, null, 'tooltip removed')
- done()
- })
- .bootstrapTooltip('show')
- })
-
- QUnit.test('should fire show event', function (assert) {
- assert.expect(1)
- var done = assert.async()
-
- $('
')
- .on('show.bs.tooltip', function () {
- assert.ok(true, 'show event fired')
- done()
- })
- .bootstrapTooltip('show')
- })
-
- QUnit.test('should throw an error when show is called on hidden elements', function (assert) {
- assert.expect(1)
- var done = assert.async()
-
- try {
- $('
').bootstrapTooltip('show')
- } catch (err) {
- assert.strictEqual(err.message, 'Please use show on visible elements')
- done()
- }
- })
-
- QUnit.test('should fire inserted event', function (assert) {
- assert.expect(2)
- var done = assert.async()
-
- $('
')
- .appendTo('#qunit-fixture')
- .on('inserted.bs.tooltip', function () {
- assert.notEqual($('.tooltip').length, 0, 'tooltip was inserted')
- assert.ok(true, 'inserted event fired')
- done()
- })
- .bootstrapTooltip('show')
- })
-
- QUnit.test('should fire shown event', function (assert) {
- assert.expect(1)
- var done = assert.async()
-
- $('
')
- .appendTo('#qunit-fixture')
- .on('shown.bs.tooltip', function () {
- assert.ok(true, 'shown was called')
- done()
- })
- .bootstrapTooltip('show')
- })
-
- QUnit.test('should not fire shown event when show was prevented', function (assert) {
- assert.expect(1)
- var done = assert.async()
-
- $('
')
- .on('show.bs.tooltip', function (e) {
- e.preventDefault()
- assert.ok(true, 'show event fired')
- done()
- })
- .on('shown.bs.tooltip', function () {
- assert.ok(false, 'shown event fired')
- })
- .bootstrapTooltip('show')
- })
-
- QUnit.test('should fire hide event', function (assert) {
- assert.expect(1)
- var done = assert.async()
-
- $('
')
- .appendTo('#qunit-fixture')
- .on('shown.bs.tooltip', function () {
- $(this).bootstrapTooltip('hide')
- })
- .on('hide.bs.tooltip', function () {
- assert.ok(true, 'hide event fired')
- done()
- })
- .bootstrapTooltip('show')
- })
-
- QUnit.test('should fire hidden event', function (assert) {
- assert.expect(1)
- var done = assert.async()
-
- $('
')
- .appendTo('#qunit-fixture')
- .on('shown.bs.tooltip', function () {
- $(this).bootstrapTooltip('hide')
- })
- .on('hidden.bs.tooltip', function () {
- assert.ok(true, 'hidden event fired')
- done()
- })
- .bootstrapTooltip('show')
- })
-
- QUnit.test('should not fire hidden event when hide was prevented', function (assert) {
- assert.expect(1)
- var done = assert.async()
-
- $('
')
- .appendTo('#qunit-fixture')
- .on('shown.bs.tooltip', function () {
- $(this).bootstrapTooltip('hide')
- })
- .on('hide.bs.tooltip', function (e) {
- e.preventDefault()
- assert.ok(true, 'hide event fired')
- done()
- })
- .on('hidden.bs.tooltip', function () {
- assert.ok(false, 'hidden event fired')
- })
- .bootstrapTooltip('show')
- })
-
- QUnit.test('should destroy tooltip', function (assert) {
- assert.expect(7)
- var $tooltip = $('
')
- .bootstrapTooltip()
- .on('click.foo', function () {}) // eslint-disable-line no-empty-function
-
- assert.ok($tooltip.data('bs.tooltip'), 'tooltip has data')
- assert.ok($._data($tooltip[0], 'events').mouseover && $._data($tooltip[0], 'events').mouseout, 'tooltip has hover events')
- assert.strictEqual($._data($tooltip[0], 'events').click[0].namespace, 'foo', 'tooltip has extra click.foo event')
-
- $tooltip.bootstrapTooltip('show')
- $tooltip.bootstrapTooltip('dispose')
-
- assert.ok(!$tooltip.hasClass('show'), 'tooltip is hidden')
- assert.ok(!$._data($tooltip[0], 'bs.tooltip'), 'tooltip does not have data')
- assert.strictEqual($._data($tooltip[0], 'events').click[0].namespace, 'foo', 'tooltip still has click.foo')
- assert.ok(!$._data($tooltip[0], 'events').mouseover && !$._data($tooltip[0], 'events').mouseout, 'tooltip does not have hover events')
- })
-
- // QUnit.test('should show tooltip with delegate selector on click', function (assert) {
- // assert.expect(2)
- // var $div = $('
')
- // .appendTo('#qunit-fixture')
- // .bootstrapTooltip({
- // selector: 'a[rel="tooltip"]',
- // trigger: 'click'
- // })
-
- // $div.find('a').trigger('click')
- // assert.ok($('.tooltip').is('.fade.in'), 'tooltip is faded in')
-
- // $div.find('a').trigger('click')
- // assert.strictEqual($div.data('bs.tooltip').tip.parentNode, null, 'tooltip removed')
- // })
-
- QUnit.test('should show tooltip when toggle is called', function (assert) {
- assert.expect(1)
- $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- trigger: 'manual'
- })
- .bootstrapTooltip('toggle')
-
- assert.ok($('.tooltip').is('.fade.show'), 'tooltip is faded active')
- })
-
- QUnit.test('should hide previously shown tooltip when toggle is called on tooltip', function (assert) {
- assert.expect(1)
- $('
@ResentedHook')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- trigger: 'manual'
- })
- .bootstrapTooltip('show')
-
- $('.tooltip').bootstrapTooltip('toggle')
- assert.ok($('.tooltip').not('.fade.show'), 'tooltip was faded out')
- })
-
- QUnit.test('should place tooltips inside body when container is body', function (assert) {
- assert.expect(3)
- var done = assert.async()
- var $tooltip = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- container: 'body'
- })
-
- $tooltip
- .one('shown.bs.tooltip', function () {
- assert.notEqual($('body > .tooltip').length, 0, 'tooltip is direct descendant of body')
- assert.strictEqual($('#qunit-fixture > .tooltip').length, 0, 'tooltip is not in parent')
- $tooltip.bootstrapTooltip('hide')
- })
- .one('hidden.bs.tooltip', function () {
- assert.strictEqual($('body > .tooltip').length, 0, 'tooltip was removed from dom')
- done()
- })
- .bootstrapTooltip('show')
- })
-
- QUnit.test('should place tooltips inside a specific container when container is an element', function (assert) {
- assert.expect(3)
- var done = assert.async()
- var $container = $('
').appendTo('#qunit-fixture')
- var $tooltip = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- container: $container[0]
- })
-
- $tooltip
- .one('shown.bs.tooltip', function () {
- assert.strictEqual($container.find('.tooltip').length, 1)
- assert.strictEqual($('#qunit-fixture > .tooltip').length, 0, 'tooltip is not in parent')
- $tooltip.bootstrapTooltip('hide')
- })
- .one('hidden.bs.tooltip', function () {
- assert.strictEqual($container.find('.tooltip').length, 0, 'tooltip was removed from dom')
- done()
- })
- .bootstrapTooltip('show')
- })
-
- QUnit.test('should place tooltips inside a specific container when container is a selector', function (assert) {
- assert.expect(3)
- var done = assert.async()
- var $container = $('
').appendTo('#qunit-fixture')
- var $tooltip = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- container: '#container'
- })
-
- $tooltip
- .one('shown.bs.tooltip', function () {
- assert.strictEqual($container.find('.tooltip').length, 1)
- assert.strictEqual($('#qunit-fixture > .tooltip').length, 0, 'tooltip is not in parent')
- $tooltip.bootstrapTooltip('hide')
- })
- .one('hidden.bs.tooltip', function () {
- assert.strictEqual($container.find('.tooltip').length, 0, 'tooltip was removed from dom')
- done()
- })
- .bootstrapTooltip('show')
- })
-
- QUnit.test('should add position class before positioning so that position-specific styles are taken into account', function (assert) {
- assert.expect(2)
- var done = assert.async()
- var styles = ''
- var $styles = $(styles).appendTo('head')
-
- var $container = $('
').appendTo('#qunit-fixture')
- $('
')
- .appendTo($container)
- .bootstrapTooltip({
- placement: 'right',
- trigger: 'manual'
- })
- .on('inserted.bs.tooltip', function () {
- var $tooltip = $($(this).data('bs.tooltip').tip)
- assert.ok($tooltip.hasClass('bs-tooltip-right'))
- assert.ok(typeof $tooltip.attr('style') === 'undefined')
- $styles.remove()
- done()
- })
- .bootstrapTooltip('show')
- })
-
- QUnit.test('should use title attribute for tooltip text', function (assert) {
- assert.expect(2)
- var done = assert.async()
- var $tooltip = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip()
-
- $tooltip
- .one('shown.bs.tooltip', function () {
- assert.strictEqual($('.tooltip').children('.tooltip-inner').text(), 'Simple tooltip', 'title from title attribute is set')
- $tooltip.bootstrapTooltip('hide')
- })
- .one('hidden.bs.tooltip', function () {
- assert.strictEqual($('.tooltip').length, 0, 'tooltip removed from dom')
- done()
- })
- .bootstrapTooltip('show')
- })
-
- QUnit.test('should prefer title attribute over title option', function (assert) {
- assert.expect(2)
- var done = assert.async()
- var $tooltip = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- title: 'This is a tooltip with some content'
- })
-
- $tooltip
- .one('shown.bs.tooltip', function () {
- assert.strictEqual($('.tooltip').children('.tooltip-inner').text(), 'Simple tooltip', 'title is set from title attribute while preferred over title option')
- $tooltip.bootstrapTooltip('hide')
- })
- .one('hidden.bs.tooltip', function () {
- assert.strictEqual($('.tooltip').length, 0, 'tooltip removed from dom')
- done()
- })
- .bootstrapTooltip('show')
- })
-
- QUnit.test('should use title option', function (assert) {
- assert.expect(2)
- var done = assert.async()
- var $tooltip = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- title: 'This is a tooltip with some content'
- })
-
- $tooltip
- .one('shown.bs.tooltip', function () {
- assert.strictEqual($('.tooltip').children('.tooltip-inner').text(), 'This is a tooltip with some content', 'title from title option is set')
- $tooltip.bootstrapTooltip('hide')
- })
- .one('hidden.bs.tooltip', function () {
- assert.strictEqual($('.tooltip').length, 0, 'tooltip removed from dom')
- done()
- })
- .bootstrapTooltip('show')
- })
-
- QUnit.test('should not error when trying to show an top-placed tooltip that has been removed from the dom', function (assert) {
- assert.expect(1)
- var passed = true
- var $tooltip = $('
')
- .appendTo('#qunit-fixture')
- .one('show.bs.tooltip', function () {
- $(this).remove()
- })
- .bootstrapTooltip({
- placement: 'top'
- })
-
- try {
- $tooltip.bootstrapTooltip('show')
- } catch (err) {
- passed = false
- }
-
- assert.ok(passed, '.tooltip(\'show\') should not throw an error if element no longer is in dom')
- })
-
- QUnit.test('should show tooltip if leave event hasn\'t occurred before delay expires', function (assert) {
- assert.expect(2)
- var done = assert.async()
-
- var $tooltip = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- delay: 150
- })
-
- setTimeout(function () {
- assert.ok(!$('.tooltip').is('.fade.show'), '100ms: tooltip is not faded active')
- }, 100)
-
- setTimeout(function () {
- assert.ok($('.tooltip').is('.fade.show'), '200ms: tooltip is faded active')
- done()
- }, 200)
-
- $tooltip.trigger('mouseenter')
- })
-
- QUnit.test('should not show tooltip if leave event occurs before delay expires', function (assert) {
- assert.expect(2)
- var done = assert.async()
-
- var $tooltip = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- delay: 150
- })
-
- setTimeout(function () {
- assert.ok(!$('.tooltip').is('.fade.show'), '100ms: tooltip not faded active')
- $tooltip.trigger('mouseout')
- }, 100)
-
- setTimeout(function () {
- assert.ok(!$('.tooltip').is('.fade.show'), '200ms: tooltip not faded active')
- done()
- }, 200)
-
- $tooltip.trigger('mouseenter')
- })
-
- QUnit.test('should not hide tooltip if leave event occurs and enter event occurs within the hide delay', function (assert) {
- assert.expect(3)
- var done = assert.async()
-
- var $tooltip = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- delay: {
- show: 0,
- hide: 150
- }
- })
-
- setTimeout(function () {
- assert.ok($('.tooltip').is('.fade.show'), '1ms: tooltip faded active')
- $tooltip.trigger('mouseout')
-
- setTimeout(function () {
- assert.ok($('.tooltip').is('.fade.show'), '100ms: tooltip still faded active')
- $tooltip.trigger('mouseenter')
- }, 100)
-
- setTimeout(function () {
- assert.ok($('.tooltip').is('.fade.show'), '200ms: tooltip still faded active')
- done()
- }, 200)
- }, 0)
-
- $tooltip.trigger('mouseenter')
- })
-
- QUnit.test('should not show tooltip if leave event occurs before delay expires', function (assert) {
- assert.expect(2)
- var done = assert.async()
-
- var $tooltip = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- delay: 150
- })
-
- setTimeout(function () {
- assert.ok(!$('.tooltip').is('.fade.show'), '100ms: tooltip not faded active')
- $tooltip.trigger('mouseout')
- }, 100)
-
- setTimeout(function () {
- assert.ok(!$('.tooltip').is('.fade.show'), '200ms: tooltip not faded active')
- done()
- }, 200)
-
- $tooltip.trigger('mouseenter')
- })
-
- QUnit.test('should not show tooltip if leave event occurs before delay expires, even if hide delay is 0', function (assert) {
- assert.expect(2)
- var done = assert.async()
-
- var $tooltip = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- delay: {
- show: 150,
- hide: 0
- }
- })
-
- setTimeout(function () {
- assert.ok(!$('.tooltip').is('.fade.show'), '100ms: tooltip not faded active')
- $tooltip.trigger('mouseout')
- }, 100)
-
- setTimeout(function () {
- assert.ok(!$('.tooltip').is('.fade.show'), '250ms: tooltip not faded active')
- done()
- }, 250)
-
- $tooltip.trigger('mouseenter')
- })
-
- QUnit.test('should wait 200ms before hiding the tooltip', function (assert) {
- assert.expect(3)
- var done = assert.async()
-
- var $tooltip = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- delay: {
- show: 0,
- hide: 150
- }
- })
-
- setTimeout(function () {
- assert.ok($($tooltip.data('bs.tooltip').tip).is('.fade.show'), '1ms: tooltip faded active')
-
- $tooltip.trigger('mouseout')
-
- setTimeout(function () {
- assert.ok($($tooltip.data('bs.tooltip').tip).is('.fade.show'), '100ms: tooltip still faded active')
- }, 100)
-
- setTimeout(function () {
- assert.ok(!$($tooltip.data('bs.tooltip').tip).is('.show'), '200ms: tooltip removed')
- done()
- }, 200)
- }, 0)
-
- $tooltip.trigger('mouseenter')
- })
-
- QUnit.test('should not reload the tooltip on subsequent mouseenter events', function (assert) {
- assert.expect(1)
- var titleHtml = function () {
- var uid = Util.getUID('tooltip')
- return '
' + uid + '
' + uid + '
' + uid + '
'
- }
-
- var $tooltip = $('
some text')
- .appendTo('#qunit-fixture')
-
- $tooltip.bootstrapTooltip({
- html: true,
- animation: false,
- trigger: 'hover',
- delay: {
- show: 0,
- hide: 500
- },
- container: $tooltip,
- title: titleHtml
- })
-
- $('#tt-outer').trigger('mouseenter')
-
- var currentUid = $('#tt-content').text()
-
- $('#tt-content').trigger('mouseenter')
- assert.strictEqual(currentUid, $('#tt-content').text())
- })
-
- QUnit.test('should not reload the tooltip if the mouse leaves and re-enters before hiding', function (assert) {
- assert.expect(4)
-
- var titleHtml = function () {
- var uid = Util.getUID('tooltip')
- return '
' + uid + '
' + uid + '
' + uid + '
'
- }
-
- var $tooltip = $('
some text')
- .appendTo('#qunit-fixture')
-
- $tooltip.bootstrapTooltip({
- html: true,
- animation: false,
- trigger: 'hover',
- delay: {
- show: 0,
- hide: 500
- },
- title: titleHtml
- })
-
- var obj = $tooltip.data('bs.tooltip')
-
- $('#tt-outer').trigger('mouseenter')
-
- var currentUid = $('#tt-content').text()
-
- $('#tt-outer').trigger('mouseleave')
- assert.strictEqual(currentUid, $('#tt-content').text())
-
- assert.ok(obj._hoverState === 'out', 'the tooltip hoverState should be set to "out"')
-
- $('#tt-outer').trigger('mouseenter')
- assert.ok(obj._hoverState === 'show', 'the tooltip hoverState should be set to "show"')
-
- assert.strictEqual(currentUid, $('#tt-content').text())
- })
-
- QUnit.test('should do nothing when an attempt is made to hide an uninitialized tooltip', function (assert) {
- assert.expect(1)
-
- var $tooltip = $('
some text')
- .appendTo('#qunit-fixture')
- .on('hidden.bs.tooltip shown.bs.tooltip', function () {
- assert.ok(false, 'should not fire any tooltip events')
- })
- .bootstrapTooltip('hide')
- assert.strictEqual(typeof $tooltip.data('bs.tooltip'), 'undefined', 'should not initialize the tooltip')
- })
-
- QUnit.test('should not remove tooltip if multiple triggers are set and one is still active', function (assert) {
- assert.expect(41)
- var $el = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- trigger: 'click hover focus',
- animation: false
- })
- var tooltip = $el.data('bs.tooltip')
- var $tooltip = $(tooltip.getTipElement())
-
- function showingTooltip() {
- return $tooltip.hasClass('show') || tooltip._hoverState === 'show'
- }
-
- var tests = [
- ['mouseenter', 'mouseleave'],
-
- ['focusin', 'focusout'],
-
- ['click', 'click'],
-
- ['mouseenter', 'focusin', 'focusout', 'mouseleave'],
- ['mouseenter', 'focusin', 'mouseleave', 'focusout'],
-
- ['focusin', 'mouseenter', 'mouseleave', 'focusout'],
- ['focusin', 'mouseenter', 'focusout', 'mouseleave'],
-
- ['click', 'focusin', 'mouseenter', 'focusout', 'mouseleave', 'click'],
- ['mouseenter', 'click', 'focusin', 'focusout', 'mouseleave', 'click'],
- ['mouseenter', 'focusin', 'click', 'click', 'mouseleave', 'focusout']
- ]
-
- assert.ok(!showingTooltip())
-
- $.each(tests, function (idx, triggers) {
- for (var i = 0, len = triggers.length; i < len; i++) {
- $el.trigger(triggers[i])
- assert.equal(i < len - 1, showingTooltip())
- }
- })
- })
-
- QUnit.test('should show on first trigger after hide', function (assert) {
- assert.expect(3)
- var $el = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- trigger: 'click hover focus',
- animation: false
- })
-
- var tooltip = $el.data('bs.tooltip')
- var $tooltip = $(tooltip.getTipElement())
-
- function showingTooltip() {
- return $tooltip.hasClass('show') || tooltip._hoverState === 'show'
- }
-
- $el.trigger('click')
- assert.ok(showingTooltip(), 'tooltip is faded in')
-
- $el.bootstrapTooltip('hide')
- assert.ok(!showingTooltip(), 'tooltip was faded out')
-
- $el.trigger('click')
- assert.ok(showingTooltip(), 'tooltip is faded in again')
- })
-
- QUnit.test('should hide tooltip when their containing modal is closed', function (assert) {
- assert.expect(1)
- var done = assert.async()
- var templateHTML = '
'
-
- $(templateHTML).appendTo('#qunit-fixture')
- $('#tooltipTest')
- .bootstrapTooltip({
- trigger: 'manuel'
- })
- .on('shown.bs.tooltip', function () {
- $('#modal-test').modal('hide')
- })
- .on('hide.bs.tooltip', function () {
- assert.ok(true, 'tooltip hide')
- done()
- })
-
- $('#modal-test')
- .on('shown.bs.modal', function () {
- $('#tooltipTest').bootstrapTooltip('show')
- })
- .modal('show')
- })
-
- QUnit.test('should allow to close modal if the tooltip element is detached', function (assert) {
- assert.expect(1)
- var done = assert.async()
- var templateHTML = [
- '
'
- ].join('')
-
- $(templateHTML).appendTo('#qunit-fixture')
- var $tooltip = $('#tooltipTest')
- var $modal = $('#modal-test')
-
- $tooltip.on('shown.bs.tooltip', function () {
- $tooltip.detach()
- $tooltip.bootstrapTooltip('dispose')
- $modal.modal('hide')
- })
-
- $modal.on('shown.bs.modal', function () {
- $tooltip.bootstrapTooltip({
- trigger: 'manuel'
- })
- .bootstrapTooltip('show')
- })
- .on('hidden.bs.modal', function () {
- assert.ok(true, 'modal hidden')
- done()
- })
- .modal('show')
- })
-
- QUnit.test('should reset tip classes when hidden event triggered', function (assert) {
- assert.expect(2)
- var done = assert.async()
- var $el = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip('show')
- .on('hidden.bs.tooltip', function () {
- var tooltip = $el.data('bs.tooltip')
- var $tooltip = $(tooltip.getTipElement())
- assert.ok($tooltip.hasClass('tooltip'))
- assert.ok($tooltip.hasClass('fade'))
- done()
- })
-
- $el.bootstrapTooltip('hide')
- })
-
- QUnit.test('should convert number in title to string', function (assert) {
- assert.expect(1)
- var done = assert.async()
- var $el = $('
')
- .appendTo('#qunit-fixture')
- .on('shown.bs.tooltip', function () {
- var tooltip = $el.data('bs.tooltip')
- var $tooltip = $(tooltip.getTipElement())
- assert.strictEqual($tooltip.children().text(), '7')
- done()
- })
-
- $el.bootstrapTooltip('show')
- })
-
- QUnit.test('tooltip should be shown right away after the call of disable/enable', function (assert) {
- assert.expect(2)
- var done = assert.async()
-
- var $trigger = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip()
- .on('shown.bs.tooltip', function () {
- assert.strictEqual($('.tooltip').hasClass('show'), true)
- done()
- })
-
- $trigger.bootstrapTooltip('disable')
- $trigger.trigger($.Event('click'))
- setTimeout(function () {
- assert.strictEqual($('.tooltip').length === 0, true)
- $trigger.bootstrapTooltip('enable')
- $trigger.trigger($.Event('click'))
- }, 200)
- })
-
- QUnit.test('should call Popper.js to update', function (assert) {
- assert.expect(2)
-
- var $tooltip = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip()
-
- var tooltip = $tooltip.data('bs.tooltip')
- tooltip.show()
- assert.ok(tooltip._popper)
-
- var spyPopper = sinon.spy(tooltip._popper, 'scheduleUpdate')
- tooltip.update()
- assert.ok(spyPopper.called)
- })
-
- QUnit.test('should not call Popper.js to update', function (assert) {
- assert.expect(1)
-
- var $tooltip = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip()
-
- var tooltip = $tooltip.data('bs.tooltip')
- tooltip.update()
-
- assert.ok(tooltip._popper === null)
- })
-
- QUnit.test('should use Popper.js to get the tip on placement change', function (assert) {
- assert.expect(1)
-
- var $tooltip = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip()
-
- var $tipTest = $('
')
- .appendTo('#qunit-fixture')
-
- var tooltip = $tooltip.data('bs.tooltip')
- tooltip.tip = null
-
- tooltip._handlePopperPlacementChange({
- instance: {
- popper: $tipTest[0]
- },
- placement: 'auto'
- })
-
- assert.ok(tooltip.tip === $tipTest[0])
- })
-
- QUnit.test('should toggle enabled', function (assert) {
- assert.expect(3)
-
- var $tooltip = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip()
-
- var tooltip = $tooltip.data('bs.tooltip')
-
- assert.strictEqual(tooltip._isEnabled, true)
-
- tooltip.toggleEnabled()
-
- assert.strictEqual(tooltip._isEnabled, false)
-
- tooltip.toggleEnabled()
-
- assert.strictEqual(tooltip._isEnabled, true)
- })
-
- QUnit.test('should create offset modifier correctly when offset option is a function', function (assert) {
- assert.expect(2)
-
- var getOffset = function (offsets) {
- return offsets
- }
-
- var $trigger = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- offset: getOffset
- })
-
- var tooltip = $trigger.data('bs.tooltip')
- var offset = tooltip._getOffset()
-
- assert.ok(typeof offset.offset === 'undefined')
- assert.ok(typeof offset.fn === 'function')
- })
-
- QUnit.test('should create offset modifier correctly when offset option is not a function', function (assert) {
- assert.expect(2)
-
- var myOffset = 42
- var $trigger = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- offset: myOffset
- })
-
- var tooltip = $trigger.data('bs.tooltip')
- var offset = tooltip._getOffset()
-
- assert.strictEqual(offset.offset, myOffset)
- assert.ok(typeof offset.fn === 'undefined')
- })
-
- QUnit.test('should disable sanitizer', function (assert) {
- assert.expect(1)
-
- var $trigger = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- sanitize: false
- })
-
- var tooltip = $trigger.data('bs.tooltip')
- assert.strictEqual(tooltip.config.sanitize, false)
- })
-
- QUnit.test('should sanitize template by removing disallowed tags', function (assert) {
- assert.expect(1)
-
- var $trigger = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- template: [
- '
',
- ' ',
- ' Some content',
- '
'
- ].join('')
- })
-
- var tooltip = $trigger.data('bs.tooltip')
- assert.strictEqual(tooltip.config.template.indexOf('script'), -1)
- })
-
- QUnit.test('should sanitize template by removing disallowed attributes', function (assert) {
- assert.expect(1)
-
- var $trigger = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- template: [
- '
',
- '
Some content',
- '
'
- ].join('')
- })
-
- var tooltip = $trigger.data('bs.tooltip')
- assert.strictEqual(tooltip.config.template.indexOf('onError'), -1)
- })
-
- QUnit.test('should sanitize template by removing tags with XSS', function (assert) {
- assert.expect(1)
-
- var $trigger = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- template: [
- '
',
- '
Click me',
- '
Some content',
- '
'
- ].join('')
- })
-
- var tooltip = $trigger.data('bs.tooltip')
- assert.strictEqual(tooltip.config.template.indexOf('script'), -1)
- })
-
- QUnit.test('should allow custom sanitization rules', function (assert) {
- assert.expect(2)
-
- var $trigger = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- template: [
- '
Click me',
- '
Some content'
- ].join(''),
- whiteList: {
- span: null
- }
- })
-
- var tooltip = $trigger.data('bs.tooltip')
-
- assert.strictEqual(tooltip.config.template.indexOf('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- template: [
- '
Some content'
- ].join(''),
- sanitizeFn: function (input) {
- return input
- }
- })
-
- var tooltip = $trigger.data('bs.tooltip')
-
- assert.ok(tooltip.config.template.indexOf('span') !== -1)
- })
-
- QUnit.test('should allow passing aria attributes', function (assert) {
- assert.expect(1)
-
- var $trigger = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- template: [
- '
Some content'
- ].join('')
- })
-
- var tooltip = $trigger.data('bs.tooltip')
-
- assert.ok(tooltip.config.template.indexOf('aria-pressed') !== -1)
- })
-
- QUnit.test('should not sanitize element content', function (assert) {
- assert.expect(1)
-
- var $element = $('
').appendTo('#qunit-fixture')
- var content = ''
-
- var $trigger = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- template: [
- '
Some content'
- ].join(''),
- html: true,
- sanitize: false
- })
-
- var tooltip = $trigger.data('bs.tooltip')
- tooltip.setElementContent($element, content)
-
- assert.strictEqual($element[0].innerHTML, content)
- })
-
- QUnit.test('should not take into account sanitize in data attributes', function (assert) {
- assert.expect(1)
-
- var $trigger = $('
')
- .appendTo('#qunit-fixture')
- .bootstrapTooltip({
- template: [
- '
Some content'
- ].join('')
- })
-
- var tooltip = $trigger.data('bs.tooltip')
-
- assert.strictEqual(tooltip.config.sanitize, true)
- })
-})
diff --git a/js/tests/unit/tooltip.spec.js b/js/tests/unit/tooltip.spec.js
new file mode 100644
index 000000000000..3e5c9179401b
--- /dev/null
+++ b/js/tests/unit/tooltip.spec.js
@@ -0,0 +1,1054 @@
+import Tooltip from '../../src/tooltip'
+import EventHandler from '../../src/dom/event-handler'
+import { noop } from '../../src/util/index'
+
+/** Test helpers */
+import { getFixture, clearFixture, jQueryMock, createEvent } from '../helpers/fixture'
+
+describe('Tooltip', () => {
+ let fixtureEl
+
+ beforeAll(() => {
+ fixtureEl = getFixture()
+ })
+
+ afterEach(() => {
+ clearFixture()
+
+ document.querySelectorAll('.tooltip').forEach(tooltipEl => {
+ document.body.removeChild(tooltipEl)
+ })
+ })
+
+ describe('VERSION', () => {
+ it('should return plugin version', () => {
+ expect(Tooltip.VERSION).toEqual(jasmine.any(String))
+ })
+ })
+
+ describe('Default', () => {
+ it('should return plugin default config', () => {
+ expect(Tooltip.Default).toEqual(jasmine.any(Object))
+ })
+ })
+
+ describe('NAME', () => {
+ it('should return plugin name', () => {
+ expect(Tooltip.NAME).toEqual(jasmine.any(String))
+ })
+ })
+
+ describe('DATA_KEY', () => {
+ it('should return plugin data key', () => {
+ expect(Tooltip.DATA_KEY).toEqual('bs.tooltip')
+ })
+ })
+
+ describe('Event', () => {
+ it('should return plugin events', () => {
+ expect(Tooltip.Event).toEqual(jasmine.any(Object))
+ })
+ })
+
+ describe('EVENT_KEY', () => {
+ it('should return plugin event key', () => {
+ expect(Tooltip.EVENT_KEY).toEqual('.bs.tooltip')
+ })
+ })
+
+ describe('DefaultType', () => {
+ it('should return plugin default type', () => {
+ expect(Tooltip.DefaultType).toEqual(jasmine.any(Object))
+ })
+ })
+
+ describe('constructor', () => {
+ it('should not take care of disallowed data attributes', () => {
+ fixtureEl.innerHTML = '
'
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ expect(tooltip.config.sanitize).toEqual(true)
+ })
+
+ it('should convert title and content to string if numbers', () => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ title: 1,
+ content: 7
+ })
+
+ expect(tooltip.config.title).toEqual('1')
+ expect(tooltip.config.content).toEqual('7')
+ })
+
+ it('should enable selector delegation', done => {
+ fixtureEl.innerHTML = ''
+
+ const containerEl = fixtureEl.querySelector('div')
+ const tooltipContainer = new Tooltip(containerEl, {
+ selector: 'a[rel="tooltip"]',
+ trigger: 'click'
+ })
+
+ containerEl.innerHTML = ''
+
+ const tooltipInContainerEl = containerEl.querySelector('a')
+
+ tooltipInContainerEl.addEventListener('shown.bs.tooltip', () => {
+ expect(document.querySelector('.tooltip')).not.toBeNull()
+ tooltipContainer.dispose()
+ done()
+ })
+
+ tooltipInContainerEl.click()
+ })
+
+ it('should allow to pass config to popper.js with `popperConfig`', () => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ popperConfig: {
+ placement: 'left'
+ }
+ })
+
+ const popperConfig = tooltip._getPopperConfig('top')
+
+ expect(popperConfig.placement).toEqual('left')
+ })
+ })
+
+ describe('enable', () => {
+ it('should enable a tooltip', done => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ tooltip.enable()
+
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ expect(document.querySelector('.tooltip')).toBeDefined()
+ done()
+ })
+
+ tooltip.show()
+ })
+ })
+
+ describe('disable', () => {
+ it('should disable tooltip', done => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ tooltip.disable()
+
+ tooltipEl.addEventListener('show.bs.tooltip', () => {
+ throw new Error('should not show a disabled tooltip')
+ })
+
+ tooltip.show()
+
+ setTimeout(() => {
+ expect().nothing()
+ done()
+ }, 10)
+ })
+ })
+
+ describe('toggleEnabled', () => {
+ it('should toggle enabled', () => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ expect(tooltip._isEnabled).toEqual(true)
+
+ tooltip.toggleEnabled()
+
+ expect(tooltip._isEnabled).toEqual(false)
+ })
+ })
+
+ describe('toggle', () => {
+ it('should do nothing if disabled', done => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ tooltip.disable()
+
+ tooltipEl.addEventListener('show.bs.tooltip', () => {
+ throw new Error('should not show a disabled tooltip')
+ })
+
+ tooltip.toggle()
+
+ setTimeout(() => {
+ expect().nothing()
+ done()
+ }, 10)
+ })
+
+ it('should show a tooltip', done => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ expect(document.querySelector('.tooltip')).toBeDefined()
+ done()
+ })
+
+ tooltip.toggle()
+ })
+
+ it('should call toggle and show the tooltip when trigger is "click"', done => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ trigger: 'click'
+ })
+
+ spyOn(tooltip, 'toggle').and.callThrough()
+
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ expect(tooltip.toggle).toHaveBeenCalled()
+ done()
+ })
+
+ tooltipEl.click()
+ })
+
+ it('should hide a tooltip', done => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ tooltip.toggle()
+ })
+
+ tooltipEl.addEventListener('hidden.bs.tooltip', () => {
+ expect(document.querySelector('.tooltip')).toBeNull()
+ done()
+ })
+
+ tooltip.toggle()
+ })
+
+ it('should call toggle and hide the tooltip when trigger is "click"', done => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ trigger: 'click'
+ })
+
+ spyOn(tooltip, 'toggle').and.callThrough()
+
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ tooltipEl.click()
+ })
+
+ tooltipEl.addEventListener('hidden.bs.tooltip', () => {
+ expect(tooltip.toggle).toHaveBeenCalled()
+ done()
+ })
+
+ tooltipEl.click()
+ })
+ })
+
+ describe('dispose', () => {
+ it('should destroy a tooltip', () => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ expect(Tooltip.getInstance(tooltipEl)).toEqual(tooltip)
+
+ tooltip.dispose()
+
+ expect(Tooltip.getInstance(tooltipEl)).toEqual(null)
+ })
+
+ it('should destroy a tooltip and remove it from the dom', done => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ expect(document.querySelector('.tooltip')).toBeDefined()
+
+ tooltip.dispose()
+
+ expect(document.querySelector('.tooltip')).toBeNull()
+ done()
+ })
+
+ tooltip.show()
+ })
+ })
+
+ describe('show', () => {
+ it('should show a tooltip', done => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ const tooltipShown = document.querySelector('.tooltip')
+
+ expect(tooltipShown).toBeDefined()
+ expect(tooltipEl.getAttribute('aria-describedby')).toEqual(tooltipShown.getAttribute('id'))
+ expect(tooltipShown.getAttribute('id').indexOf('tooltip') !== -1).toEqual(true)
+ done()
+ })
+
+ tooltip.show()
+ })
+
+ it('should show a tooltip when hovering a children element', done => {
+ fixtureEl.innerHTML =
+ '' +
+ '' +
+ ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ spyOn(tooltip, 'show')
+
+ tooltipEl.querySelector('rect').dispatchEvent(createEvent('mouseover', { bubbles: true }))
+
+ setTimeout(() => {
+ expect(tooltip.show).toHaveBeenCalled()
+ done()
+ }, 0)
+ })
+
+ it('should show a tooltip on mobile', done => {
+ fixtureEl.innerHTML = '
'
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+ document.documentElement.ontouchstart = noop
+
+ spyOn(EventHandler, 'on')
+
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ expect(document.querySelector('.tooltip')).not.toBeNull()
+ expect(EventHandler.on).toHaveBeenCalled()
+ document.documentElement.ontouchstart = undefined
+ done()
+ })
+
+ tooltip.show()
+ })
+
+ it('should show a tooltip relative to placement option', done => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ placement: 'bottom'
+ })
+
+ tooltipEl.addEventListener('inserted.bs.tooltip', () => {
+ expect(tooltip.getTipElement().classList.contains('bs-tooltip-bottom')).toEqual(true)
+ })
+
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ const tooltipShown = document.querySelector('.tooltip')
+
+ expect(tooltipShown.classList.contains('bs-tooltip-bottom')).toEqual(true)
+ done()
+ })
+
+ tooltip.show()
+ })
+
+ it('should not error when trying to show a tooltip that has been removed from the dom', done => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ const firstCallback = () => {
+ tooltipEl.removeEventListener('shown.bs.tooltip', firstCallback)
+ let tooltipShown = document.querySelector('.tooltip')
+
+ tooltipShown.parentNode.removeChild(tooltipShown)
+
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ tooltipShown = document.querySelector('.tooltip')
+
+ expect(tooltipShown).not.toBeNull()
+ done()
+ })
+
+ tooltip.show()
+ }
+
+ tooltipEl.addEventListener('shown.bs.tooltip', firstCallback)
+
+ tooltip.show()
+ })
+
+ it('should show a tooltip with a dom element container', done => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ container: fixtureEl
+ })
+
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ expect(fixtureEl.querySelector('.tooltip')).toBeDefined()
+ done()
+ })
+
+ tooltip.show()
+ })
+
+ it('should show a tooltip with a jquery element container', done => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ container: {
+ 0: fixtureEl,
+ jquery: 'jQuery'
+ }
+ })
+
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ expect(fixtureEl.querySelector('.tooltip')).toBeDefined()
+ done()
+ })
+
+ tooltip.show()
+ })
+
+ it('should show a tooltip with a selector in container', done => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ container: '#fixture'
+ })
+
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ expect(fixtureEl.querySelector('.tooltip')).toBeDefined()
+ done()
+ })
+
+ tooltip.show()
+ })
+
+ it('should show a tooltip with placement as a function', done => {
+ fixtureEl.innerHTML = ''
+
+ const spy = jasmine.createSpy('placement').and.returnValue('top')
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ placement: spy
+ })
+
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ expect(document.querySelector('.tooltip')).toBeDefined()
+ expect(spy).toHaveBeenCalled()
+ done()
+ })
+
+ tooltip.show()
+ })
+
+ it('should show a tooltip with offset as a function', done => {
+ fixtureEl.innerHTML = ''
+
+ const spy = jasmine.createSpy('offset').and.returnValue({})
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ offset: spy
+ })
+
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ expect(document.querySelector('.tooltip')).toBeDefined()
+ expect(spy).toHaveBeenCalled()
+ done()
+ })
+
+ tooltip.show()
+ })
+
+ it('should show a tooltip without the animation', done => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ animation: false
+ })
+
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ const tip = document.querySelector('.tooltip')
+
+ expect(tip).toBeDefined()
+ expect(tip.classList.contains('fade')).toEqual(false)
+ done()
+ })
+
+ tooltip.show()
+ })
+
+ it('should throw an error the element is not visible', () => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ try {
+ tooltip.show()
+ } catch (error) {
+ expect(error.message).toEqual('Please use show on visible elements')
+ }
+ })
+
+ it('should not show a tooltip if show.bs.tooltip is prevented', done => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ const expectedDone = () => {
+ setTimeout(() => {
+ expect(document.querySelector('.tooltip')).toBeNull()
+ done()
+ }, 10)
+ }
+
+ tooltipEl.addEventListener('show.bs.tooltip', ev => {
+ ev.preventDefault()
+ expectedDone()
+ })
+
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ throw new Error('Tooltip should not be shown')
+ })
+
+ tooltip.show()
+ })
+
+ it('should show tooltip if leave event hasn\'t occurred before delay expires', done => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ delay: 150
+ })
+
+ spyOn(tooltip, 'show')
+
+ setTimeout(() => {
+ expect(tooltip.show).not.toHaveBeenCalled()
+ }, 100)
+
+ setTimeout(() => {
+ expect(tooltip.show).toHaveBeenCalled()
+ done()
+ }, 200)
+
+ tooltipEl.dispatchEvent(createEvent('mouseover'))
+ })
+
+ it('should not show tooltip if leave event occurs before delay expires', done => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ delay: 150
+ })
+
+ spyOn(tooltip, 'show')
+
+ setTimeout(() => {
+ expect(tooltip.show).not.toHaveBeenCalled()
+ tooltipEl.dispatchEvent(createEvent('mouseover'))
+ }, 100)
+
+ setTimeout(() => {
+ expect(tooltip.show).toHaveBeenCalled()
+ expect(document.querySelectorAll('.tooltip').length).toEqual(0)
+ done()
+ }, 200)
+
+ tooltipEl.dispatchEvent(createEvent('mouseover'))
+ })
+
+ it('should not hide tooltip if leave event occurs and enter event occurs within the hide delay', done => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ delay: {
+ show: 0,
+ hide: 150
+ }
+ })
+
+ setTimeout(() => {
+ expect(tooltip.getTipElement().classList.contains('show')).toEqual(true)
+ tooltipEl.dispatchEvent(createEvent('mouseout'))
+
+ setTimeout(() => {
+ expect(tooltip.getTipElement().classList.contains('show')).toEqual(true)
+ tooltipEl.dispatchEvent(createEvent('mouseover'))
+ }, 100)
+
+ setTimeout(() => {
+ expect(tooltip.getTipElement().classList.contains('show')).toEqual(true)
+ done()
+ }, 200)
+ }, 0)
+
+ tooltipEl.dispatchEvent(createEvent('mouseover'))
+ })
+ })
+
+ describe('hide', () => {
+ it('should hide a tooltip', done => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ tooltipEl.addEventListener('shown.bs.tooltip', () => tooltip.hide())
+ tooltipEl.addEventListener('hidden.bs.tooltip', () => {
+ expect(document.querySelector('.tooltip')).toBeNull()
+ expect(tooltipEl.getAttribute('aria-describedby')).toBeNull()
+ done()
+ })
+
+ tooltip.show()
+ })
+
+ it('should hide a tooltip on mobile', done => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ document.documentElement.ontouchstart = noop
+ spyOn(EventHandler, 'off')
+ tooltip.hide()
+ })
+
+ tooltipEl.addEventListener('hidden.bs.tooltip', () => {
+ expect(document.querySelector('.tooltip')).toBeNull()
+ expect(EventHandler.off).toHaveBeenCalled()
+ document.documentElement.ontouchstart = undefined
+ done()
+ })
+
+ tooltip.show()
+ })
+
+ it('should hide a tooltip without animation', done => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ animation: false
+ })
+
+ tooltipEl.addEventListener('shown.bs.tooltip', () => tooltip.hide())
+ tooltipEl.addEventListener('hidden.bs.tooltip', () => {
+ expect(document.querySelector('.tooltip')).toBeNull()
+ expect(tooltipEl.getAttribute('aria-describedby')).toBeNull()
+ done()
+ })
+
+ tooltip.show()
+ })
+
+ it('should not hide a tooltip if hide event is prevented', done => {
+ fixtureEl.innerHTML = ''
+
+ const assertDone = () => {
+ setTimeout(() => {
+ expect(document.querySelector('.tooltip')).not.toBeNull()
+ done()
+ }, 20)
+ }
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ animation: false
+ })
+
+ tooltipEl.addEventListener('shown.bs.tooltip', () => tooltip.hide())
+ tooltipEl.addEventListener('hide.bs.tooltip', event => {
+ event.preventDefault()
+ assertDone()
+ })
+ tooltipEl.addEventListener('hidden.bs.tooltip', () => {
+ throw new Error('should not trigger hidden event')
+ })
+
+ tooltip.show()
+ })
+
+ it('should not throw error running hide if popper hasn\'t been shown', () => {
+ fixtureEl.innerHTML = ''
+
+ const div = fixtureEl.querySelector('div')
+ const tooltip = new Tooltip(div)
+
+ try {
+ tooltip.hide()
+ expect().nothing()
+ } catch {
+ throw new Error('should not throw error')
+ }
+ })
+ })
+
+ describe('update', () => {
+ it('should call popper schedule update', done => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ tooltipEl.addEventListener('shown.bs.tooltip', () => {
+ spyOn(tooltip._popper, 'scheduleUpdate')
+
+ tooltip.update()
+
+ expect(tooltip._popper.scheduleUpdate).toHaveBeenCalled()
+ done()
+ })
+
+ tooltip.show()
+ })
+
+ it('should do nothing if the tooltip is not shown', () => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ tooltip.update()
+ expect().nothing()
+ })
+ })
+
+ describe('isWithContent', () => {
+ it('should return true if there is content', () => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ expect(tooltip.isWithContent()).toEqual(true)
+ })
+
+ it('should return false if there is no content', () => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ expect(tooltip.isWithContent()).toEqual(false)
+ })
+ })
+
+ describe('getTipElement', () => {
+ it('should create the tip element and return it', () => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ spyOn(document, 'createElement').and.callThrough()
+
+ expect(tooltip.getTipElement()).toBeDefined()
+ expect(document.createElement).toHaveBeenCalled()
+ })
+
+ it('should return the created tip element', () => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ const spy = spyOn(document, 'createElement').and.callThrough()
+
+ expect(tooltip.getTipElement()).toBeDefined()
+ expect(spy).toHaveBeenCalled()
+
+ spy.calls.reset()
+
+ expect(tooltip.getTipElement()).toBeDefined()
+ expect(spy).not.toHaveBeenCalled()
+ })
+ })
+
+ describe('setContent', () => {
+ it('should set tip content', () => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ tooltip.setContent()
+
+ const tip = tooltip.getTipElement()
+
+ expect(tip.classList.contains('show')).toEqual(false)
+ expect(tip.classList.contains('fade')).toEqual(false)
+ expect(tip.querySelector('.tooltip-inner').textContent).toEqual('Another tooltip')
+ })
+ })
+
+ describe('setElementContent', () => {
+ it('should do nothing if the element is null', () => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ tooltip.setElementContent(null, null)
+ expect().nothing()
+ })
+
+ it('should add the content as a child of the element', () => {
+ fixtureEl.innerHTML = [
+ '',
+ ''
+ ].join('')
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const childContent = fixtureEl.querySelector('div')
+ const tooltip = new Tooltip(tooltipEl, {
+ html: true
+ })
+
+ tooltip.setElementContent(tooltip.getTipElement(), childContent)
+
+ expect(childContent.parentNode).toEqual(tooltip.getTipElement())
+ })
+
+ it('should do nothing if the content is a child of the element', () => {
+ fixtureEl.innerHTML = [
+ '',
+ ''
+ ].join('')
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const childContent = fixtureEl.querySelector('div')
+ const tooltip = new Tooltip(tooltipEl, {
+ html: true
+ })
+
+ tooltip.getTipElement().appendChild(childContent)
+ tooltip.setElementContent(tooltip.getTipElement(), childContent)
+
+ expect().nothing()
+ })
+
+ it('should add the content as a child of the element for jQuery elements', () => {
+ fixtureEl.innerHTML = [
+ '',
+ ''
+ ].join('')
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const childContent = fixtureEl.querySelector('div')
+ const tooltip = new Tooltip(tooltipEl, {
+ html: true
+ })
+
+ tooltip.setElementContent(tooltip.getTipElement(), { 0: childContent, jquery: 'jQuery' })
+
+ expect(childContent.parentNode).toEqual(tooltip.getTipElement())
+ })
+
+ it('should add the child text content in the element', () => {
+ fixtureEl.innerHTML = [
+ '',
+ 'Tooltip
'
+ ].join('')
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const childContent = fixtureEl.querySelector('div')
+ const tooltip = new Tooltip(tooltipEl)
+
+ tooltip.setElementContent(tooltip.getTipElement(), childContent)
+
+ expect(childContent.textContent).toEqual(tooltip.getTipElement().textContent)
+ })
+
+ it('should add html without sanitize it', () => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ sanitize: false,
+ html: true
+ })
+
+ tooltip.setElementContent(tooltip.getTipElement(), 'Tooltip
')
+
+ expect(tooltip.getTipElement().querySelector('div').id).toEqual('childContent')
+ })
+
+ it('should add html sanitized', () => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ html: true
+ })
+
+ tooltip.setElementContent(tooltip.getTipElement(), [
+ '',
+ ' ',
+ '
'
+ ].join(''))
+
+ expect(tooltip.getTipElement().querySelector('div').id).toEqual('childContent')
+ expect(tooltip.getTipElement().querySelector('button')).toEqual(null)
+ })
+
+ it('should add text content', () => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ tooltip.setElementContent(tooltip.getTipElement(), 'test')
+
+ expect(tooltip.getTipElement().textContent).toEqual('test')
+ })
+ })
+
+ describe('getTitle', () => {
+ it('should return the title', () => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl)
+
+ expect(tooltip.getTitle()).toEqual('Another tooltip')
+ })
+
+ it('should call title function', () => {
+ fixtureEl.innerHTML = ''
+
+ const tooltipEl = fixtureEl.querySelector('a')
+ const tooltip = new Tooltip(tooltipEl, {
+ title: () => 'test'
+ })
+
+ expect(tooltip.getTitle()).toEqual('test')
+ })
+ })
+
+ describe('jQueryInterface', () => {
+ it('should create a tooltip', () => {
+ fixtureEl.innerHTML = '
'
+
+ const div = fixtureEl.querySelector('div')
+
+ jQueryMock.fn.tooltip = Tooltip.jQueryInterface
+ jQueryMock.elements = [div]
+
+ jQueryMock.fn.tooltip.call(jQueryMock)
+
+ expect(Tooltip.getInstance(div)).toBeDefined()
+ })
+
+ it('should not re create a tooltip', () => {
+ fixtureEl.innerHTML = '
'
+
+ const div = fixtureEl.querySelector('div')
+ const tooltip = new Tooltip(div)
+
+ jQueryMock.fn.tooltip = Tooltip.jQueryInterface
+ jQueryMock.elements = [div]
+
+ jQueryMock.fn.tooltip.call(jQueryMock)
+
+ expect(Tooltip.getInstance(div)).toEqual(tooltip)
+ })
+
+ it('should call a tooltip method', () => {
+ fixtureEl.innerHTML = '
'
+
+ const div = fixtureEl.querySelector('div')
+ const tooltip = new Tooltip(div)
+
+ spyOn(tooltip, 'show')
+
+ jQueryMock.fn.tooltip = Tooltip.jQueryInterface
+ jQueryMock.elements = [div]
+
+ jQueryMock.fn.tooltip.call(jQueryMock, 'show')
+
+ expect(Tooltip.getInstance(div)).toEqual(tooltip)
+ expect(tooltip.show).toHaveBeenCalled()
+ })
+
+ it('should do nothing when we call dispose or hide if there is no tooltip created', () => {
+ fixtureEl.innerHTML = '
'
+
+ const div = fixtureEl.querySelector('div')
+
+ spyOn(Tooltip.prototype, 'dispose')
+
+ jQueryMock.fn.tooltip = Tooltip.jQueryInterface
+ jQueryMock.elements = [div]
+
+ jQueryMock.fn.tooltip.call(jQueryMock, 'dispose')
+
+ expect(Tooltip.prototype.dispose).not.toHaveBeenCalled()
+ })
+
+ it('should throw error on undefined method', () => {
+ fixtureEl.innerHTML = '
'
+
+ const div = fixtureEl.querySelector('div')
+ const action = 'undefinedMethod'
+
+ jQueryMock.fn.tooltip = Tooltip.jQueryInterface
+ jQueryMock.elements = [div]
+
+ try {
+ jQueryMock.fn.tooltip.call(jQueryMock, action)
+ } catch (error) {
+ expect(error.message).toEqual(`No method named "${action}"`)
+ }
+ })
+ })
+})
diff --git a/js/tests/unit/util.js b/js/tests/unit/util.js
deleted file mode 100644
index 858fee6f4d02..000000000000
--- a/js/tests/unit/util.js
+++ /dev/null
@@ -1,163 +0,0 @@
-$(function () {
- 'use strict'
-
- window.Util = typeof bootstrap !== 'undefined' ? bootstrap.Util : Util
-
- QUnit.module('util', {
- afterEach: function () {
- $('#qunit-fixture').html('')
- }
- })
-
- QUnit.test('Util.getSelectorFromElement should return the correct element', function (assert) {
- assert.expect(2)
-
- var $el = $('
').appendTo($('#qunit-fixture'))
- assert.strictEqual(Util.getSelectorFromElement($el[0]), 'body')
-
- // Not found element
- var $el2 = $('
').appendTo($('#qunit-fixture'))
- assert.strictEqual(Util.getSelectorFromElement($el2[0]), null)
- })
-
- QUnit.test('Util.getSelectorFromElement should return null when there is a bad selector', function (assert) {
- assert.expect(2)
-
- var $el = $('
').appendTo($('#qunit-fixture'))
-
- assert.strictEqual(Util.getSelectorFromElement($el[0]), null)
-
- var $el2 = $('
').appendTo($('#qunit-fixture'))
-
- assert.strictEqual(Util.getSelectorFromElement($el2[0]), null)
- })
-
- QUnit.test('Util.typeCheckConfig should thrown an error when a bad config is passed', function (assert) {
- assert.expect(1)
- var namePlugin = 'collapse'
- var defaultType = {
- toggle: 'boolean',
- parent: '(string|element)'
- }
- var config = {
- toggle: true,
- parent: 777
- }
-
- try {
- Util.typeCheckConfig(namePlugin, config, defaultType)
- } catch (err) {
- assert.strictEqual(err.message, 'COLLAPSE: Option "parent" provided type "number" but expected type "(string|element)".')
- }
- })
-
- QUnit.test('Util.isElement should check if we passed an element or not', function (assert) {
- assert.expect(3)
- var $div = $('
').appendTo($('#qunit-fixture'))
-
- assert.strictEqual(Util.isElement($div), 1)
- assert.strictEqual(Util.isElement($div[0]), 1)
- assert.strictEqual(typeof Util.isElement({}) === 'undefined', true)
- })
-
- QUnit.test('Util.getTransitionDurationFromElement should accept transition durations in milliseconds', function (assert) {
- assert.expect(1)
- var $div = $('
').appendTo($('#qunit-fixture'))
-
- assert.strictEqual(Util.getTransitionDurationFromElement($div[0]), 300)
- })
-
- QUnit.test('Util.getTransitionDurationFromElement should accept transition durations in seconds', function (assert) {
- assert.expect(1)
- var $div = $('
').appendTo($('#qunit-fixture'))
-
- assert.strictEqual(Util.getTransitionDurationFromElement($div[0]), 400)
- })
-
- QUnit.test('Util.getTransitionDurationFromElement should return the addition of transition-delay and transition-duration', function (assert) {
- assert.expect(2)
- var $fixture = $('#qunit-fixture')
- var $div = $('
').appendTo($fixture)
- var $div2 = $('
').appendTo($fixture)
-
- assert.strictEqual(Util.getTransitionDurationFromElement($div[0]), 150)
- assert.strictEqual(Util.getTransitionDurationFromElement($div2[0]), 280)
- })
-
- QUnit.test('Util.getTransitionDurationFromElement should get the first transition duration if multiple transition durations are defined', function (assert) {
- assert.expect(1)
- var $div = $('
').appendTo($('#qunit-fixture'))
-
- assert.strictEqual(Util.getTransitionDurationFromElement($div[0]), 300)
- })
-
- QUnit.test('Util.getTransitionDurationFromElement should return 0 if transition duration is not defined', function (assert) {
- assert.expect(1)
- var $div = $('
').appendTo($('#qunit-fixture'))
-
- assert.strictEqual(Util.getTransitionDurationFromElement($div[0]), 0)
- })
-
- QUnit.test('Util.getTransitionDurationFromElement should return 0 if element is not found in DOM', function (assert) {
- assert.expect(1)
- var $div = $('#fake-id')
-
- assert.strictEqual(Util.getTransitionDurationFromElement($div[0]), 0)
- })
-
- QUnit.test('Util.getUID should generate a new id uniq', function (assert) {
- assert.expect(2)
- var id = Util.getUID('test')
- var id2 = Util.getUID('test')
-
- assert.ok(id !== id2, id + ' !== ' + id2)
-
- id = Util.getUID('test')
- $('
').appendTo($('#qunit-fixture'))
-
- id2 = Util.getUID('test')
- assert.ok(id !== id2, id + ' !== ' + id2)
- })
-
- QUnit.test('Util.supportsTransitionEnd should return true', function (assert) {
- assert.expect(1)
- assert.ok(Util.supportsTransitionEnd())
- })
-
- QUnit.test('Util.findShadowRoot should find the shadow DOM root', function (assert) {
- // Only for newer browsers
- if (!document.documentElement.attachShadow) {
- assert.expect(0)
- return
- }
-
- assert.expect(2)
- var $div = $('
').appendTo($('#qunit-fixture'))
- var shadowRoot = $div[0].attachShadow({
- mode: 'open'
- })
-
- assert.equal(shadowRoot, Util.findShadowRoot(shadowRoot))
- shadowRoot.innerHTML = '
'
- assert.equal(shadowRoot, Util.findShadowRoot(shadowRoot.firstChild))
- })
-
- QUnit.test('Util.findShadowRoot should return null when attachShadow is not available', function (assert) {
- assert.expect(1)
-
- var $div = $('
').appendTo($('#qunit-fixture'))
- if (!document.documentElement.attachShadow) {
- assert.equal(null, Util.findShadowRoot($div[0]))
- } else {
- var sandbox = sinon.createSandbox()
-
- sandbox.replace(document.documentElement, 'attachShadow', function () {
- // to avoid empty function
- return $div
- })
-
- assert.equal(null, Util.findShadowRoot($div[0]))
- sandbox.restore()
- }
- })
-})
diff --git a/js/tests/unit/util/index.spec.js b/js/tests/unit/util/index.spec.js
new file mode 100644
index 000000000000..541c10baa785
--- /dev/null
+++ b/js/tests/unit/util/index.spec.js
@@ -0,0 +1,397 @@
+import * as Util from '../../../src/util/index'
+
+/** Test helpers */
+import { getFixture, clearFixture } from '../../helpers/fixture'
+
+describe('Util', () => {
+ let fixtureEl
+
+ beforeAll(() => {
+ fixtureEl = getFixture()
+ })
+
+ afterEach(() => {
+ clearFixture()
+ })
+
+ describe('getUID', () => {
+ it('should generate uid', () => {
+ const uid = Util.getUID('bs')
+ const uid2 = Util.getUID('bs')
+
+ expect(uid).not.toEqual(uid2)
+ })
+ })
+
+ describe('getSelectorFromElement', () => {
+ it('should get selector from data-target', () => {
+ fixtureEl.innerHTML = [
+ '
',
+ '
'
+ ].join('')
+
+ const testEl = fixtureEl.querySelector('#test')
+
+ expect(Util.getSelectorFromElement(testEl)).toEqual('.target')
+ })
+
+ it('should get selector from href if no data-target set', () => {
+ fixtureEl.innerHTML = [
+ '
',
+ '
'
+ ].join('')
+
+ const testEl = fixtureEl.querySelector('#test')
+
+ expect(Util.getSelectorFromElement(testEl)).toEqual('.target')
+ })
+
+ it('should get selector from href if data-target equal to #', () => {
+ fixtureEl.innerHTML = [
+ '
',
+ '
'
+ ].join('')
+
+ const testEl = fixtureEl.querySelector('#test')
+
+ expect(Util.getSelectorFromElement(testEl)).toEqual('.target')
+ })
+
+ it('should return null if selector not found', () => {
+ fixtureEl.innerHTML = '
'
+
+ const testEl = fixtureEl.querySelector('#test')
+
+ expect(Util.getSelectorFromElement(testEl)).toBeNull()
+ })
+
+ it('should return null if no selector', () => {
+ fixtureEl.innerHTML = '
'
+
+ const testEl = fixtureEl.querySelector('div')
+
+ expect(Util.getSelectorFromElement(testEl)).toBeNull()
+ })
+ })
+
+ describe('getElementFromSelector', () => {
+ it('should get element from data-target', () => {
+ fixtureEl.innerHTML = [
+ '
',
+ '
'
+ ].join('')
+
+ const testEl = fixtureEl.querySelector('#test')
+
+ expect(Util.getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target'))
+ })
+
+ it('should get element from href if no data-target set', () => {
+ fixtureEl.innerHTML = [
+ '
',
+ '
'
+ ].join('')
+
+ const testEl = fixtureEl.querySelector('#test')
+
+ expect(Util.getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target'))
+ })
+
+ it('should return null if element not found', () => {
+ fixtureEl.innerHTML = '
'
+
+ const testEl = fixtureEl.querySelector('#test')
+
+ expect(Util.getElementFromSelector(testEl)).toBeNull()
+ })
+
+ it('should return null if no selector', () => {
+ fixtureEl.innerHTML = '
'
+
+ const testEl = fixtureEl.querySelector('div')
+
+ expect(Util.getElementFromSelector(testEl)).toBeNull()
+ })
+ })
+
+ describe('getTransitionDurationFromElement', () => {
+ it('should get transition from element', () => {
+ fixtureEl.innerHTML = '
'
+
+ 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 = '
'
+
+ expect(Util.getTransitionDurationFromElement(fixtureEl.querySelector('div'))).toEqual(0)
+ })
+ })
+
+ describe('triggerTransitionEnd', () => {
+ it('should trigger transitionend event', done => {
+ fixtureEl.innerHTML = '
'
+
+ const el = fixtureEl.querySelector('div')
+
+ el.addEventListener('transitionend', () => {
+ expect().nothing()
+ done()
+ })
+
+ Util.triggerTransitionEnd(el)
+ })
+ })
+
+ describe('isElement', () => {
+ it('should detect if the parameter is an element or not', () => {
+ fixtureEl.innerHTML = '
'
+
+ const el = document.querySelector('div')
+
+ expect(Util.isElement(el)).toEqual(el.nodeType)
+ expect(Util.isElement({})).toEqual(undefined)
+ })
+
+ it('should detect jQuery element', () => {
+ fixtureEl.innerHTML = '
'
+
+ const el = document.querySelector('div')
+ const fakejQuery = {
+ 0: el
+ }
+
+ expect(Util.isElement(fakejQuery)).toEqual(el.nodeType)
+ })
+ })
+
+ describe('emulateTransitionEnd', () => {
+ it('should emulate transition end', () => {
+ fixtureEl.innerHTML = '
'
+
+ const el = document.querySelector('div')
+ const spy = spyOn(window, 'setTimeout')
+
+ Util.emulateTransitionEnd(el, 10)
+ expect(spy).toHaveBeenCalled()
+ })
+
+ it('should not emulate transition end if already triggered', done => {
+ fixtureEl.innerHTML = '
'
+
+ const el = fixtureEl.querySelector('div')
+ const spy = spyOn(el, 'removeEventListener')
+
+ Util.emulateTransitionEnd(el, 10)
+ Util.triggerTransitionEnd(el)
+
+ setTimeout(() => {
+ expect(spy).toHaveBeenCalled()
+ done()
+ }, 20)
+ })
+ })
+
+ describe('typeCheckConfig', () => {
+ const namePlugin = 'collapse'
+
+ it('should check type of the config object', () => {
+ const defaultType = {
+ toggle: 'boolean',
+ parent: '(string|element)'
+ }
+ const config = {
+ toggle: true,
+ parent: 777
+ }
+
+ expect(() => {
+ Util.typeCheckConfig(namePlugin, config, defaultType)
+ }).toThrow(new Error('COLLAPSE: Option "parent" provided type "number" but expected type "(string|element)".'))
+ })
+
+ it('should return null stringified when null is passed', () => {
+ const defaultType = {
+ toggle: 'boolean',
+ parent: '(null|element)'
+ }
+ const config = {
+ toggle: true,
+ parent: null
+ }
+
+ Util.typeCheckConfig(namePlugin, config, defaultType)
+ expect().nothing()
+ })
+
+ it('should return undefined stringified when undefined is passed', () => {
+ const defaultType = {
+ toggle: 'boolean',
+ parent: '(undefined|element)'
+ }
+ const config = {
+ toggle: true,
+ parent: undefined
+ }
+
+ Util.typeCheckConfig(namePlugin, config, defaultType)
+ expect().nothing()
+ })
+ })
+
+ describe('isVisible', () => {
+ it('should return false if the element is not defined', () => {
+ expect(Util.isVisible(null)).toEqual(false)
+ expect(Util.isVisible(undefined)).toEqual(false)
+ })
+
+ it('should return false if the element provided is not a dom element', () => {
+ expect(Util.isVisible({})).toEqual(false)
+ })
+
+ it('should return false if the element is not visible with display none', () => {
+ fixtureEl.innerHTML = '
'
+
+ const div = fixtureEl.querySelector('div')
+
+ expect(Util.isVisible(div)).toEqual(false)
+ })
+
+ it('should return false if the element is not visible with visibility hidden', () => {
+ fixtureEl.innerHTML = '
'
+
+ const div = fixtureEl.querySelector('div')
+
+ expect(Util.isVisible(div)).toEqual(false)
+ })
+
+ it('should return false if the parent element is not visible', () => {
+ fixtureEl.innerHTML = [
+ '
'
+ ].join('')
+
+ const div = fixtureEl.querySelector('.content')
+
+ expect(Util.isVisible(div)).toEqual(false)
+ })
+
+ it('should return true if the element is visible', () => {
+ fixtureEl.innerHTML = [
+ '
'
+ ].join('')
+
+ const div = fixtureEl.querySelector('#element')
+
+ expect(Util.isVisible(div)).toEqual(true)
+ })
+ })
+
+ describe('findShadowRoot', () => {
+ it('should return null if shadow dom is not available', () => {
+ // Only for newer browsers
+ if (!document.documentElement.attachShadow) {
+ expect().nothing()
+ return
+ }
+
+ fixtureEl.innerHTML = '
'
+
+ const div = fixtureEl.querySelector('div')
+
+ spyOn(document.documentElement, 'attachShadow').and.returnValue(null)
+
+ expect(Util.findShadowRoot(div)).toEqual(null)
+ })
+
+ it('should return null when we do not find a shadow root', () => {
+ // Only for newer browsers
+ if (!document.documentElement.attachShadow) {
+ expect().nothing()
+ return
+ }
+
+ spyOn(document, 'getRootNode').and.returnValue(undefined)
+
+ expect(Util.findShadowRoot(document)).toEqual(null)
+ })
+
+ it('should return the shadow root when found', () => {
+ // Only for newer browsers
+ if (!document.documentElement.attachShadow) {
+ expect().nothing()
+ return
+ }
+
+ fixtureEl.innerHTML = '
'
+
+ const div = fixtureEl.querySelector('div')
+ const shadowRoot = div.attachShadow({
+ mode: 'open'
+ })
+
+ expect(Util.findShadowRoot(shadowRoot)).toEqual(shadowRoot)
+
+ shadowRoot.innerHTML = '
'
+
+ expect(Util.findShadowRoot(shadowRoot.firstChild)).toEqual(shadowRoot)
+ })
+ })
+
+ describe('noop', () => {
+ it('should return a function', () => {
+ expect(typeof Util.noop()).toEqual('function')
+ })
+ })
+
+ describe('reflow', () => {
+ it('should return element offset height to force the reflow', () => {
+ fixtureEl.innerHTML = '
'
+
+ const div = fixtureEl.querySelector('div')
+
+ expect(Util.reflow(div)).toEqual(0)
+ })
+ })
+
+ describe('getjQuery', () => {
+ const fakejQuery = { trigger() {} }
+
+ beforeEach(() => {
+ Object.defineProperty(window, 'jQuery', {
+ value: fakejQuery,
+ writable: true
+ })
+ })
+
+ afterEach(() => {
+ window.jQuery = undefined
+ })
+
+ it('should return jQuery object when present', () => {
+ expect(Util.getjQuery()).toEqual(fakejQuery)
+ })
+
+ it('should not return jQuery object when present if data-no-jquery', () => {
+ document.body.setAttribute('data-no-jquery', '')
+
+ expect(window.jQuery).toEqual(fakejQuery)
+ expect(Util.getjQuery()).toEqual(null)
+
+ document.body.removeAttribute('data-no-jquery')
+ })
+
+ it('should not return jQuery if not present', () => {
+ window.jQuery = undefined
+ expect(Util.getjQuery()).toEqual(null)
+ })
+ })
+})
diff --git a/js/tests/unit/util/sanitizer.spec.js b/js/tests/unit/util/sanitizer.spec.js
new file mode 100644
index 000000000000..dcfad8436f8f
--- /dev/null
+++ b/js/tests/unit/util/sanitizer.spec.js
@@ -0,0 +1,70 @@
+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 = [
+ '
',
+ '
Click me',
+ '
Some content',
+ '
'
+ ].join('')
+
+ const result = sanitizeHtml(template, DefaultAllowlist, null)
+
+ expect(result.indexOf('script') === -1).toEqual(true)
+ })
+
+ it('should allow aria attributes and safe attributes', () => {
+ const template = [
+ '
',
+ ' Some content',
+ '
'
+ ].join('')
+
+ const result = sanitizeHtml(template, DefaultAllowlist, null)
+
+ expect(result.indexOf('aria-pressed') !== -1).toEqual(true)
+ expect(result.indexOf('class="test"') !== -1).toEqual(true)
+ })
+
+ it('should remove tags not in allowlist', () => {
+ const template = [
+ '
',
+ ' ',
+ '
'
+ ].join('')
+
+ const result = sanitizeHtml(template, DefaultAllowlist, null)
+
+ expect(result.indexOf('
-
+
+
+
+
diff --git a/js/tests/visual/button.html b/js/tests/visual/button.html
index b7ba7964d404..54a35dffbc1b 100644
--- a/js/tests/visual/button.html
+++ b/js/tests/visual/button.html
@@ -2,7 +2,7 @@
-
+
Button
@@ -44,8 +44,10 @@
Button Bootstrap Visual Test