]> git.ipfire.org Git - thirdparty/bootstrap.git/commitdiff
rewrite button unit tests
authorJohann-S <johann.servoire@gmail.com>
Mon, 25 Mar 2019 10:32:02 +0000 (11:32 +0100)
committerJohann-S <johann.servoire@gmail.com>
Tue, 23 Jul 2019 12:23:50 +0000 (14:23 +0200)
build/build-plugins.js
js/index.esm.js
js/index.umd.js
js/src/button/button.js [moved from js/src/button.js with 95% similarity]
js/src/button/button.spec.js [new file with mode: 0644]
js/tests/helpers/fixture.js
js/tests/unit/button.js [deleted file]

index 471707035b4f616e00ce2c9b85386b4155c2cf3a..5d8c429635d36acad44350a2ee4f67d24432e94a 100644 (file)
@@ -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'),
index e3a851537c9ae66537ed02bb8290b13e2296a565..ca47d7405e752d8e795e7640d67fffb78a349df9 100644 (file)
@@ -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'
index 039e6d1bb45e6c6a5e25ac01a65c2e27249dc868..2cb90696dae485ecf95498bbb2d221368d98774b 100644 (file)
@@ -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'
similarity index 95%
rename from js/src/button.js
rename to js/src/button/button.js
index c69a8a3901032e3e154f51701373021222815ec4..2e6033b64ed4cae599bbb72e1eee07df1b521e5b 100644 (file)
@@ -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 (file)
index 0000000..7114088
--- /dev/null
@@ -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)
+    })
+  })
+})
index 524d544448afbf6a740422a73e8fb5df349151e8..e7240ee1818b3b1baada878a6f75dab4f89c87dc 100644 (file)
@@ -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 (file)
index d351cc7..0000000
+++ /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')
-  })
-})