Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/twbs/bootstrap.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build/build-plugins.js2
-rw-r--r--js/index.esm.js2
-rw-r--r--js/index.umd.js2
-rw-r--r--js/src/button/button.js (renamed from js/src/button.js)11
-rw-r--r--js/src/button/button.spec.js292
-rw-r--r--js/tests/helpers/fixture.js18
-rw-r--r--js/tests/unit/button.js225
7 files changed, 318 insertions, 234 deletions
diff --git a/build/build-plugins.js b/build/build-plugins.js
index 471707035b..5d8c429635 100644
--- a/build/build-plugins.js
+++ b/build/build-plugins.js
@@ -33,7 +33,7 @@ const bsPlugins = {
Polyfill: path.resolve(__dirname, '../js/src/dom/polyfill.js'),
SelectorEngine: path.resolve(__dirname, '../js/src/dom/selector-engine.js'),
Alert: path.resolve(__dirname, '../js/src/alert/alert.js'),
- Button: path.resolve(__dirname, '../js/src/button.js'),
+ Button: path.resolve(__dirname, '../js/src/button/button.js'),
Carousel: path.resolve(__dirname, '../js/src/carousel.js'),
Collapse: path.resolve(__dirname, '../js/src/collapse.js'),
Dropdown: path.resolve(__dirname, '../js/src/dropdown.js'),
diff --git a/js/index.esm.js b/js/index.esm.js
index e3a851537c..ca47d7405e 100644
--- a/js/index.esm.js
+++ b/js/index.esm.js
@@ -6,7 +6,7 @@
*/
import Alert from './src/alert/alert'
-import Button from './src/button'
+import Button from './src/button/button'
import Carousel from './src/carousel'
import Collapse from './src/collapse'
import Dropdown from './src/dropdown'
diff --git a/js/index.umd.js b/js/index.umd.js
index 039e6d1bb4..2cb90696da 100644
--- a/js/index.umd.js
+++ b/js/index.umd.js
@@ -6,7 +6,7 @@
*/
import Alert from './src/alert/alert'
-import Button from './src/button'
+import Button from './src/button/button'
import Carousel from './src/carousel'
import Collapse from './src/collapse'
import Dropdown from './src/dropdown'
diff --git a/js/src/button.js b/js/src/button/button.js
index c69a8a3901..2e6033b64e 100644
--- a/js/src/button.js
+++ b/js/src/button/button.js
@@ -5,10 +5,10 @@
* --------------------------------------------------------------------------
*/
-import { jQuery as $ } from './util/index'
-import Data from './dom/data'
-import EventHandler from './dom/event-handler'
-import SelectorEngine from './dom/selector-engine'
+import { jQuery as $ } from '../util/index'
+import Data from '../dom/data'
+import EventHandler from '../dom/event-handler'
+import SelectorEngine from '../dom/selector-engine'
/**
* ------------------------------------------------------------------------
@@ -158,7 +158,6 @@ EventHandler.on(document, Event.CLICK_DATA_API, Selector.DATA_TOGGLE_CARROT, eve
let data = Data.getData(button, DATA_KEY)
if (!data) {
data = new Button(button)
- Data.setData(button, DATA_KEY, data)
}
data.toggle()
@@ -186,7 +185,7 @@ EventHandler.on(document, Event.BLUR_DATA_API, Selector.DATA_TOGGLE_CARROT, even
* ------------------------------------------------------------------------
* add .button to jQuery only if jQuery is present
*/
-
+/* istanbul ignore if */
if (typeof $ !== 'undefined') {
const JQUERY_NO_CONFLICT = $.fn[NAME]
$.fn[NAME] = Button._jQueryInterface
diff --git a/js/src/button/button.spec.js b/js/src/button/button.spec.js
new file mode 100644
index 0000000000..7114088964
--- /dev/null
+++ b/js/src/button/button.spec.js
@@ -0,0 +1,292 @@
+import Button from './button'
+import EventHandler from '../dom/event-handler'
+
+/** Test helpers */
+import {
+ getFixture,
+ clearFixture,
+ createEvent,
+ jQueryMock
+} from '../../tests/helpers/fixture'
+
+describe('Button', () => {
+ let fixtureEl
+
+ beforeAll(() => {
+ fixtureEl = getFixture()
+ })
+
+ afterEach(() => {
+ clearFixture()
+ })
+
+ describe('VERSION', () => {
+ it('should return plugin version', () => {
+ expect(Button.VERSION).toEqual(jasmine.any(String))
+ })
+ })
+
+ describe('data-api', () => {
+ it('should toggle active class on click', () => {
+ fixtureEl.innerHTML = [
+ '<button class="btn" data-toggle="button">btn</button>',
+ '<button class="btn testParent" data-toggle="button"><div class="test"></div></button>'
+ ].join('')
+
+ const btn = fixtureEl.querySelector('.btn')
+ const divTest = fixtureEl.querySelector('.test')
+ const btnTestParent = fixtureEl.querySelector('.testParent')
+
+ expect(btn.classList.contains('active')).toEqual(false)
+
+ btn.click()
+
+ expect(btn.classList.contains('active')).toEqual(true)
+
+ btn.click()
+
+ expect(btn.classList.contains('active')).toEqual(false)
+
+ divTest.click()
+
+ expect(btnTestParent.classList.contains('active')).toEqual(true)
+ })
+
+ it('should trigger input change event when toggled button has input field', done => {
+ fixtureEl.innerHTML = [
+ '<div class="btn-group" data-toggle="buttons">',
+ ' <label class="btn btn-primary">',
+ ' <input type="radio" id="radio" autocomplete="off"> Radio',
+ ' </label>',
+ '</div>'
+ ].join('')
+
+ const input = fixtureEl.querySelector('input')
+ const label = fixtureEl.querySelector('label')
+
+ input.addEventListener('change', () => {
+ expect().nothing()
+ done()
+ })
+
+ label.click()
+ })
+
+ it('should not trigger input change event when input already checked and button is active', () => {
+ fixtureEl.innerHTML = [
+ '<button type="button" class="btn btn-primary active" data-toggle="buttons">',
+ ' <input type="radio" id="radio" autocomplete="off" checked> Radio',
+ '</button>'
+ ].join('')
+
+ const button = fixtureEl.querySelector('button')
+
+ spyOn(EventHandler, 'trigger')
+
+ button.click()
+
+ expect(EventHandler.trigger).not.toHaveBeenCalled()
+ })
+
+ it('should remove active when an other radio button is clicked', () => {
+ fixtureEl.innerHTML = [
+ '<div class="btn-group btn-group-toggle" data-toggle="buttons">',
+ ' <label class="btn btn-secondary active">',
+ ' <input type="radio" name="options" id="option1" autocomplete="off" checked> Active',
+ ' </label>',
+ ' <label class="btn btn-secondary">',
+ ' <input type="radio" name="options" id="option2" autocomplete="off"> Radio',
+ ' </label>',
+ ' <label class="btn btn-secondary">',
+ ' <input type="radio" name="options" id="option3" autocomplete="off"> Radio',
+ ' </label>',
+ '</div>'
+ ].join('')
+
+ const option1 = fixtureEl.querySelector('#option1')
+ const option2 = fixtureEl.querySelector('#option2')
+
+ expect(option1.checked).toEqual(true)
+ expect(option1.parentElement.classList.contains('active')).toEqual(true)
+
+ const clickEvent = createEvent('click')
+
+ option2.dispatchEvent(clickEvent)
+
+ expect(option1.checked).toEqual(false)
+ expect(option1.parentElement.classList.contains('active')).toEqual(false)
+ expect(option2.checked).toEqual(true)
+ expect(option2.parentElement.classList.contains('active')).toEqual(true)
+ })
+
+ it('should do nothing if the child is not an input', () => {
+ fixtureEl.innerHTML = [
+ '<div class="btn-group btn-group-toggle" data-toggle="buttons">',
+ ' <label class="btn btn-secondary active">',
+ ' <span id="option1">el 1</span>',
+ ' </label>',
+ ' <label class="btn btn-secondary">',
+ ' <span id="option2">el 2</span>',
+ ' </label>',
+ ' <label class="btn btn-secondary">',
+ ' <span>el 3</span>',
+ ' </label>',
+ '</div>'
+ ].join('')
+
+ const option2 = fixtureEl.querySelector('#option2')
+ const clickEvent = createEvent('click')
+
+ option2.dispatchEvent(clickEvent)
+
+ expect().nothing()
+ })
+
+ it('should add focus class on focus event', () => {
+ fixtureEl.innerHTML = '<button class="btn" data-toggle="button"><input type="text" /></button>'
+
+ const btn = fixtureEl.querySelector('.btn')
+ const input = fixtureEl.querySelector('input')
+
+ const focusEvent = createEvent('focus')
+ input.dispatchEvent(focusEvent)
+
+ expect(btn.classList.contains('focus')).toEqual(true)
+ })
+
+ it('should not add focus class', () => {
+ fixtureEl.innerHTML = '<button data-toggle="button"><input type="text" /></button>'
+
+ const btn = fixtureEl.querySelector('button')
+ const input = fixtureEl.querySelector('input')
+
+ const focusEvent = createEvent('focus')
+ input.dispatchEvent(focusEvent)
+
+ expect(btn.classList.contains('focus')).toEqual(false)
+ })
+
+ it('should remove focus class on blur event', () => {
+ fixtureEl.innerHTML = '<button class="btn focus" data-toggle="button"><input type="text" /></button>'
+
+ const btn = fixtureEl.querySelector('.btn')
+ const input = fixtureEl.querySelector('input')
+
+ const focusEvent = createEvent('blur')
+ input.dispatchEvent(focusEvent)
+
+ expect(btn.classList.contains('focus')).toEqual(false)
+ })
+
+ it('should not remove focus class on blur event', () => {
+ fixtureEl.innerHTML = '<button class="focus" data-toggle="button"><input type="text" /></button>'
+
+ const btn = fixtureEl.querySelector('button')
+ const input = fixtureEl.querySelector('input')
+
+ const focusEvent = createEvent('blur')
+ input.dispatchEvent(focusEvent)
+
+ expect(btn.classList.contains('focus')).toEqual(true)
+ })
+ })
+
+ describe('toggle', () => {
+ it('should toggle aria-pressed', () => {
+ fixtureEl.innerHTML = '<button class="btn" data-toggle="button" aria-pressed="false"></button>'
+
+ const btnEl = fixtureEl.querySelector('.btn')
+ const button = new Button(btnEl)
+
+ expect(btnEl.getAttribute('aria-pressed')).toEqual('false')
+ expect(btnEl.classList.contains('active')).toEqual(false)
+
+ button.toggle()
+
+ expect(btnEl.getAttribute('aria-pressed')).toEqual('true')
+ expect(btnEl.classList.contains('active')).toEqual(true)
+ })
+
+ it('should handle disabled attribute on non-button elements', () => {
+ fixtureEl.innerHTML = [
+ '<div class="btn-group disabled" data-toggle="buttons" aria-disabled="true" disabled>',
+ ' <label class="btn btn-danger disabled" aria-disabled="true" disabled>',
+ ' <input type="checkbox" aria-disabled="true" autocomplete="off" disabled class="disabled"/>',
+ ' </label>',
+ '</div>'
+ ].join('')
+
+ const btnGroupEl = fixtureEl.querySelector('.btn-group')
+ const btnDanger = fixtureEl.querySelector('.btn-danger')
+ const input = fixtureEl.querySelector('input')
+
+ const button = new Button(btnGroupEl)
+
+ button.toggle()
+
+ expect(btnDanger.hasAttribute('disabled')).toEqual(true)
+ expect(input.checked).toEqual(false)
+ })
+ })
+
+ describe('dispose', () => {
+ it('should dispose a button', () => {
+ fixtureEl.innerHTML = '<button class="btn" data-toggle="button"></button>'
+
+ const btnEl = fixtureEl.querySelector('.btn')
+ const button = new Button(btnEl)
+
+ expect(Button._getInstance(btnEl)).toBeDefined()
+
+ button.dispose()
+
+ expect(Button._getInstance(btnEl)).toBeNull()
+ })
+ })
+
+ describe('_jQueryInterface', () => {
+ it('should handle config passed and toggle existing button', () => {
+ fixtureEl.innerHTML = '<button class="btn" data-toggle="button"></button>'
+
+ const btnEl = fixtureEl.querySelector('.btn')
+ const button = new Button(btnEl)
+
+ spyOn(button, 'toggle')
+
+ jQueryMock.fn.button = Button._jQueryInterface
+ jQueryMock.elements = [btnEl]
+
+ jQueryMock.fn.button.call(jQueryMock, 'toggle')
+
+ expect(button.toggle).toHaveBeenCalled()
+ })
+
+ it('should create new button instance and call toggle', () => {
+ fixtureEl.innerHTML = '<button class="btn" data-toggle="button"></button>'
+
+ const btnEl = fixtureEl.querySelector('.btn')
+
+ jQueryMock.fn.button = Button._jQueryInterface
+ jQueryMock.elements = [btnEl]
+
+ jQueryMock.fn.button.call(jQueryMock, 'toggle')
+
+ expect(Button._getInstance(btnEl)).toBeDefined()
+ expect(btnEl.classList.contains('active')).toEqual(true)
+ })
+
+ it('should just create a button instance without calling toggle', () => {
+ fixtureEl.innerHTML = '<button class="btn" data-toggle="button"></button>'
+
+ const btnEl = fixtureEl.querySelector('.btn')
+
+ jQueryMock.fn.button = Button._jQueryInterface
+ jQueryMock.elements = [btnEl]
+
+ jQueryMock.fn.button.call(jQueryMock)
+
+ expect(Button._getInstance(btnEl)).toBeDefined()
+ expect(btnEl.classList.contains('active')).toEqual(false)
+ })
+ })
+})
diff --git a/js/tests/helpers/fixture.js b/js/tests/helpers/fixture.js
index 524d544448..e7240ee181 100644
--- a/js/tests/helpers/fixture.js
+++ b/js/tests/helpers/fixture.js
@@ -18,3 +18,21 @@ export const clearFixture = () => {
fixtureEl.innerHTML = ''
}
+
+export const createEvent = (eventName, params) => {
+ params = params || {}
+ const e = document.createEvent('Event')
+
+ e.initEvent(eventName, Boolean(params.bubbles), Boolean(params.cancelable))
+ return e
+}
+
+export const jQueryMock = {
+ elements: undefined,
+ fn: {},
+ each(fn) {
+ this.elements.forEach(el => {
+ fn.call(el)
+ })
+ }
+}
diff --git a/js/tests/unit/button.js b/js/tests/unit/button.js
deleted file mode 100644
index d351cc7705..0000000000
--- a/js/tests/unit/button.js
+++ /dev/null
@@ -1,225 +0,0 @@
-$(function () {
- 'use strict'
-
- var Button = typeof window.bootstrap === 'undefined' ? window.Button : window.bootstrap.Button
-
- QUnit.module('button plugin')
-
- QUnit.test('should be defined on jquery object', function (assert) {
- assert.expect(1)
- assert.ok($(document.body).button, 'button method is defined')
- })
-
- QUnit.module('button', {
- beforeEach: function () {
- // Run all tests in noConflict mode -- it's the only way to ensure that the plugin works in noConflict mode
- $.fn.bootstrapButton = $.fn.button.noConflict()
- },
- afterEach: function () {
- $.fn.button = $.fn.bootstrapButton
- delete $.fn.bootstrapButton
- $('#qunit-fixture').html('')
- }
- })
-
- QUnit.test('should provide no conflict', function (assert) {
- assert.expect(1)
- assert.strictEqual(typeof $.fn.button, 'undefined', 'button was set back to undefined (org value)')
- })
-
- QUnit.test('should return jquery collection containing the element', function (assert) {
- assert.expect(2)
- var $el = $('<div/>')
- var $button = $el.bootstrapButton()
- assert.ok($button instanceof $, 'returns jquery collection')
- assert.strictEqual($button[0], $el[0], 'collection contains element')
- })
-
- QUnit.test('should toggle active', function (assert) {
- assert.expect(2)
- var $btn = $('<button class="btn" data-toggle="button">mdo</button>')
- assert.ok(!$btn.hasClass('active'), 'btn does not have active class')
- $btn.bootstrapButton('toggle')
- assert.ok($btn.hasClass('active'), 'btn has class active')
- })
-
- QUnit.test('should toggle active when btn children are clicked', function (assert) {
- assert.expect(2)
- var $btn = $('<button class="btn" data-toggle="button">mdo</button>')
- var $inner = $('<i/>')
- $btn
- .append($inner)
- .appendTo('#qunit-fixture')
- assert.ok(!$btn.hasClass('active'), 'btn does not have active class')
- $inner.trigger('click')
- assert.ok($btn.hasClass('active'), 'btn has class active')
- })
-
- QUnit.test('should toggle aria-pressed', function (assert) {
- assert.expect(2)
- var $btn = $('<button class="btn" data-toggle="button" aria-pressed="false">redux</button>')
- assert.strictEqual($btn.attr('aria-pressed'), 'false', 'btn aria-pressed state is false')
- $btn.bootstrapButton('toggle')
- assert.strictEqual($btn.attr('aria-pressed'), 'true', 'btn aria-pressed state is true')
- })
-
- QUnit.test('should toggle aria-pressed on buttons with container', function (assert) {
- assert.expect(1)
- var groupHTML = '<div class="btn-group" data-toggle="buttons">' +
- '<button id="btn1" class="btn btn-secondary" type="button">One</button>' +
- '<button class="btn btn-secondary" type="button">Two</button>' +
- '</div>'
- $('#qunit-fixture').append(groupHTML)
- $('#btn1').bootstrapButton('toggle')
- assert.strictEqual($('#btn1').attr('aria-pressed'), 'true')
- })
-
- QUnit.test('should toggle aria-pressed when btn children are clicked', function (assert) {
- assert.expect(2)
- var $btn = $('<button class="btn" data-toggle="button" aria-pressed="false">redux</button>')
- var $inner = $('<i/>')
- $btn
- .append($inner)
- .appendTo('#qunit-fixture')
- assert.strictEqual($btn.attr('aria-pressed'), 'false', 'btn aria-pressed state is false')
- $inner.trigger('click')
- assert.strictEqual($btn.attr('aria-pressed'), 'true', 'btn aria-pressed state is true')
- })
-
- QUnit.test('should trigger input change event when toggled button has input field', function (assert) {
- assert.expect(1)
- var done = assert.async()
-
- var groupHTML = '<div class="btn-group" data-toggle="buttons">' +
- '<label class="btn btn-primary">' +
- '<input type="radio" id="radio" autocomplete="off">Radio' +
- '</label>' +
- '</div>'
- var $group = $(groupHTML).appendTo('#qunit-fixture')
-
- $group.find('input').on('change', function (e) {
- e.preventDefault()
- assert.ok(true, 'change event fired')
- done()
- })
-
- $group.find('label').trigger('click')
- })
-
- QUnit.test('should check for closest matching toggle', function (assert) {
- assert.expect(12)
- var groupHTML =
- '<div class="btn-group" data-toggle="buttons">' +
- ' <label class="btn btn-primary active">' +
- ' <input type="radio" name="options" id="option1" checked="true"> Option 1' +
- ' </label>' +
- ' <label class="btn btn-primary">' +
- ' <input type="radio" name="options" id="option2"> Option 2' +
- ' </label>' +
- ' <label class="btn btn-primary">' +
- ' <input type="radio" name="options" id="option3"> Option 3' +
- ' </label>' +
- '</div>'
-
- var $group = $(groupHTML).appendTo('#qunit-fixture')
-
- var $btn1 = $group.children().eq(0)
- var $btn2 = $group.children().eq(1)
- var inputBtn2 = $btn2.find('input')[0]
-
- assert.ok($btn1.hasClass('active'), 'btn1 has active class')
- assert.ok($btn1.find('input').prop('checked'), 'btn1 is checked')
- assert.ok(!$btn2.hasClass('active'), 'btn2 does not have active class')
- assert.ok(!inputBtn2.checked, 'btn2 is not checked')
-
- inputBtn2.dispatchEvent(new Event('click'))
-
- assert.ok(!$btn1.hasClass('active'), 'btn1 does not have active class')
- assert.ok(!$btn1.find('input').prop('checked'), 'btn1 is not checked')
- assert.ok($btn2.hasClass('active'), 'btn2 has active class')
- assert.ok(inputBtn2.checked, 'btn2 is checked')
-
- inputBtn2.dispatchEvent(new Event('click')) // clicking an already checked radio should not un-check it
-
- assert.ok(!$btn1.hasClass('active'), 'btn1 does not have active class')
- assert.ok(!$btn1.find('input').prop('checked'), 'btn1 is not checked')
- assert.ok($btn2.hasClass('active'), 'btn2 has active class')
- assert.ok(inputBtn2.checked, 'btn2 is checked')
- })
-
- QUnit.test('should only toggle selectable inputs', function (assert) {
- assert.expect(6)
- var groupHTML = '<div class="btn-group" data-toggle="buttons">' +
- '<label class="btn btn-primary active">' +
- '<input type="hidden" name="option1" id="option1-default" value="false">' +
- '<input type="checkbox" name="option1" id="option1" checked="true"> Option 1' +
- '</label>' +
- '</div>'
- var $group = $(groupHTML).appendTo('#qunit-fixture')
-
- var $btn = $group.children().eq(0)
- var $hidden = $btn.find('input#option1-default')
- var $cb = $btn.find('input#option1')
-
- assert.ok($btn.hasClass('active'), 'btn has active class')
- assert.ok($cb.prop('checked'), 'btn is checked')
- assert.ok(!$hidden.prop('checked'), 'hidden is not checked')
- $btn.trigger('click')
- assert.ok(!$btn.hasClass('active'), 'btn does not have active class')
- assert.ok(!$cb.prop('checked'), 'btn is not checked')
- assert.ok(!$hidden.prop('checked'), 'hidden is not checked') // should not be changed
- })
-
- QUnit.test('should not add aria-pressed on labels for radio/checkbox inputs in a data-toggle="buttons" group', function (assert) {
- assert.expect(2)
- var groupHTML = '<div class="btn-group" data-toggle="buttons">' +
- '<label class="btn btn-primary"><input type="checkbox" autocomplete="off"> Checkbox</label>' +
- '<label class="btn btn-primary"><input type="radio" name="options" autocomplete="off"> Radio</label>' +
- '</div>'
- var $group = $(groupHTML).appendTo('#qunit-fixture')
-
- var $btn1 = $group.children().eq(0)
- var $btn2 = $group.children().eq(1)
-
- $btn1.find('input').trigger('click')
- assert.ok($btn1.is(':not([aria-pressed])'), 'label for nested checkbox input has not been given an aria-pressed attribute')
-
- $btn2.find('input').trigger('click')
- assert.ok($btn2.is(':not([aria-pressed])'), 'label for nested radio input has not been given an aria-pressed attribute')
- })
-
- QUnit.test('should handle disabled attribute on non-button elements', function (assert) {
- assert.expect(2)
- var groupHTML = '<div class="btn-group disabled" data-toggle="buttons" aria-disabled="true" disabled>' +
- '<label class="btn btn-danger disabled" aria-disabled="true" disabled>' +
- '<input type="checkbox" aria-disabled="true" autocomplete="off" disabled class="disabled"/>' +
- '</label>' +
- '</div>'
- var $group = $(groupHTML).appendTo('#qunit-fixture')
-
- var $btn = $group.children().eq(0)
- var $input = $btn.children().eq(0)
-
- $btn.trigger('click')
- assert.ok($btn.is(':not(.active)'), 'button did not become active')
- assert.ok(!$input.is(':checked'), 'checkbox did not get checked')
- })
-
- QUnit.test('dispose should remove data and the element', function (assert) {
- assert.expect(2)
-
- var $el = $('<div/>')
- var $button = $el.bootstrapButton()
-
- assert.ok(typeof Button._getInstance($button[0]) !== 'undefined')
-
- Button._getInstance($button[0]).dispose()
-
- assert.ok(Button._getInstance($button[0]) === null)
- })
-
- QUnit.test('should return the version', function (assert) {
- assert.expect(1)
- assert.strictEqual(typeof Button.VERSION, 'string')
- })
-})