diff options
author | Johann-S <johann.servoire@gmail.com> | 2018-06-09 22:11:05 +0300 |
---|---|---|
committer | XhmikosR <xhmikosr@gmail.com> | 2019-02-20 23:05:45 +0300 |
commit | 64591b3722128d89252b8f1c840cd846940b7f5c (patch) | |
tree | 971c65a6fd9f506da55e4be31d1fece7f9b20b89 /js | |
parent | 4d6e41dea6492f18029f0dd70b118217c02f27d8 (diff) |
fix(manipulator): increase coverage for manipulator
Diffstat (limited to 'js')
-rw-r--r-- | js/src/carousel.js | 4 | ||||
-rw-r--r-- | js/src/collapse.js | 5 | ||||
-rw-r--r-- | js/src/dom/manipulator.js | 80 | ||||
-rw-r--r-- | js/src/dropdown.js | 2 | ||||
-rw-r--r-- | js/src/modal.js | 20 | ||||
-rw-r--r-- | js/src/scrollspy.js | 2 | ||||
-rw-r--r-- | js/src/tooltip.js | 11 | ||||
-rw-r--r-- | js/src/util.js | 49 | ||||
-rw-r--r-- | js/tests/index.html | 5 | ||||
-rw-r--r-- | js/tests/karma.conf.js | 10 | ||||
-rw-r--r-- | js/tests/unit/dom/manipulator.js | 179 | ||||
-rw-r--r-- | js/tests/unit/modal.js | 6 |
12 files changed, 286 insertions, 87 deletions
diff --git a/js/src/carousel.js b/js/src/carousel.js index 15a56bd76a..352b238490 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -562,8 +562,8 @@ class Carousel { } const config = { - ...Util.getDataAttributes(target), - ...Util.getDataAttributes(this) + ...Manipulator.getDataAttributes(target), + ...Manipulator.getDataAttributes(this) } const slideIndex = this.getAttribute('data-slide-to') diff --git a/js/src/collapse.js b/js/src/collapse.js index d04743d039..eebac13bbf 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -7,6 +7,7 @@ import Data from './dom/data' import EventHandler from './dom/eventHandler' +import Manipulator from './dom/manipulator' import SelectorEngine from './dom/selectorEngine' import Util from './util' @@ -347,7 +348,7 @@ class Collapse { let data = Data.getData(element, DATA_KEY) const _config = { ...Default, - ...Util.getDataAttributes(element), + ...Manipulator.getDataAttributes(element), ...typeof config === 'object' && config ? config : {} } @@ -391,7 +392,7 @@ EventHandler.on(document, Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function ( event.preventDefault() } - const triggerData = Util.getDataAttributes(this) + const triggerData = Manipulator.getDataAttributes(this) const selector = Util.getSelectorFromElement(this) const selectorElements = Util.makeArray(SelectorEngine.find(selector)) diff --git a/js/src/dom/manipulator.js b/js/src/dom/manipulator.js index 201902b77e..db3113f88d 100644 --- a/js/src/dom/manipulator.js +++ b/js/src/dom/manipulator.js @@ -1,12 +1,32 @@ -import Util from '../util' - /** * -------------------------------------------------------------------------- - * Bootstrap (v4.0.0-beta): dom/manipulator.js + * Bootstrap (v4.1.1): dom/manipulator.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * -------------------------------------------------------------------------- */ +const regexDataKey = /[A-Z]/g + +function normalizeData(val) { + if (val === 'true') { + return true + } else if (val === 'false') { + return false + } else if (val === 'null') { + return null + } else if (val === Number(val).toString()) { + return Number(val) + } else if (val === '') { + return null + } + + return val +} + +function normalizeDataKey(key) { + return key.replace(regexDataKey, (chr) => chr.toLowerCase()) +} + const Manipulator = { setChecked(input, val) { if (input instanceof HTMLInputElement) { @@ -23,21 +43,55 @@ const Manipulator = { }, setDataAttribute(element, key, value) { - const $ = Util.jQuery - if (typeof $ !== 'undefined') { - $(element).data(key, value) - } - - element.setAttribute(`data-${key.replace(/[A-Z]/g, (chr) => `-${chr.toLowerCase()}`)}`, value) + element.setAttribute(`data-${normalizeDataKey(key)}`, value) }, removeDataAttribute(element, key) { - const $ = Util.jQuery - if (typeof $ !== 'undefined') { - $(element).removeData(key) + element.removeAttribute(`data-${normalizeDataKey(key)}`) + }, + + getDataAttributes(element) { + if (typeof element === 'undefined' || element === null) { + return {} + } + + let attributes + if (Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'dataset')) { + attributes = { + ...element.dataset + } + } else { + attributes = {} + for (let i = 0; i < element.attributes.length; i++) { + const attribute = element.attributes[i] + + if (attribute.nodeName.indexOf('data-') !== -1) { + // remove 'data-' part of the attribute name + const attributeName = attribute + .nodeName + .substring('data-'.length) + .replace(/-./g, (str) => str.charAt(1).toUpperCase()) + + attributes[attributeName] = attribute.nodeValue + } + } } - element.removeAttribute(`data-${key.replace(/[A-Z]/g, (chr) => `-${chr.toLowerCase()}`)}`) + for (const key in attributes) { + if (!Object.prototype.hasOwnProperty.call(attributes, key)) { + continue + } + + attributes[key] = normalizeData(attributes[key]) + } + + return attributes + }, + + getDataAttribute(element, key) { + return normalizeData(element + .getAttribute(`data-${normalizeDataKey(key)}`) + ) }, offset(element) { diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 282e7645ff..b1487b64ad 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -267,7 +267,7 @@ class Dropdown { _getConfig(config) { config = { ...this.constructor.Default, - ...Util.getDataAttributes(this._element), + ...Manipulator.getDataAttributes(this._element), ...config } diff --git a/js/src/modal.js b/js/src/modal.js index 4f23fff741..0ecd6948f2 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -473,7 +473,7 @@ class Modal { // Restore fixed content padding Util.makeArray(SelectorEngine.find(Selector.FIXED_CONTENT)) .forEach((element) => { - const padding = Util.getDataAttribute(element, 'padding-right') + const padding = Manipulator.getDataAttribute(element, 'padding-right') if (typeof padding !== 'undefined') { Manipulator.removeDataAttribute(element, 'padding-right') element.style.paddingRight = padding @@ -483,7 +483,7 @@ class Modal { // Restore sticky content and navbar-toggler margin Util.makeArray(SelectorEngine.find(`${Selector.STICKY_CONTENT}`)) .forEach((element) => { - const margin = Util.getDataAttribute(element, 'margin-right') + const margin = Manipulator.getDataAttribute(element, 'margin-right') if (typeof margin !== 'undefined') { Manipulator.removeDataAttribute(element, 'margin-right') element.style.marginRight = margin @@ -491,17 +491,13 @@ class Modal { }) // Restore body padding - const padding = Util.getDataAttribute(document.body, 'padding-right') + const padding = Manipulator.getDataAttribute(document.body, 'padding-right') if (typeof padding !== 'undefined') { Manipulator.removeDataAttribute(document.body, 'padding-right') document.body.style.paddingRight = padding } else { document.body.style.paddingRight = '' } - - static _getInstance(element) { - return Data.getData(element, DATA_KEY) - } } _getScrollbarWidth() { // thx d.walsh @@ -520,7 +516,7 @@ class Modal { let data = Data.getData(this, DATA_KEY) const _config = { ...Default, - ...Util.getDataAttributes(this), + ...Manipulator.getDataAttributes(this), ...typeof config === 'object' && config ? config : {} } @@ -539,6 +535,10 @@ class Modal { } }) } + + static _getInstance(element) { + return Data.getData(element, DATA_KEY) + } } /** @@ -557,8 +557,8 @@ EventHandler.on(document, Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function ( const config = Data.getData(target, DATA_KEY) ? 'toggle' : { - ...Util.getDataAttributes(target), - ...Util.getDataAttributes(this) + ...Manipulator.getDataAttributes(target), + ...Manipulator.getDataAttributes(this) } if (this.tagName === 'A' || this.tagName === 'AREA') { diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index 458f5170e9..f317284c9c 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -322,7 +322,7 @@ class ScrollSpy { EventHandler.on(window, Event.LOAD_DATA_API, () => { Util.makeArray(SelectorEngine.find(Selector.DATA_SPY)) - .forEach((spy) => new ScrollSpy(spy, Util.getDataAttributes(spy))) + .forEach((spy) => new ScrollSpy(spy, Manipulator.getDataAttributes(spy))) }) /** diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 9b8b8263a9..fbe9ed856a 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -11,6 +11,7 @@ import { } from './tools/sanitizer' import Data from './dom/data' import EventHandler from './dom/eventHandler' +import Manipulator from './dom/manipulator' import Popper from 'popper.js' import SelectorEngine from './dom/selectorEngine' import Util from './util' @@ -671,7 +672,7 @@ class Tooltip { } _getConfig(config) { - const dataAttributes = Util.getDataAttributes(this.element) + const dataAttributes = Manipulator.getDataAttributes(this.element) Object.keys(dataAttributes) .forEach((dataAttr) => { @@ -741,10 +742,6 @@ class Tooltip { .map((token) => token.trim()) .forEach((tClass) => tip.classList.remove(tClass)) } - - static _getInstance(element) { - return Data.getData(element, DATA_KEY) - } } _handlePopperPlacementChange(popperData) { @@ -793,6 +790,10 @@ class Tooltip { } }) } + + static _getInstance(element) { + return Data.getData(element, DATA_KEY) + } } /** diff --git a/js/src/util.js b/js/src/util.js index 78f5fe3fbe..0b7f492fee 100644 --- a/js/src/util.js +++ b/js/src/util.js @@ -22,20 +22,6 @@ function toType(obj) { return {}.toString.call(obj).match(/\s([a-z]+)/i)[1].toLowerCase() } -function normalizeData(val) { - if (val === 'true') { - return true - } else if (val === 'false') { - return false - } else if (val === 'null') { - return null - } else if (val === Number(val).toString()) { - return Number(val) - } - - return val -} - const Util = { TRANSITION_END: 'bsTransitionEnd', @@ -164,41 +150,6 @@ const Util = { return [nodeList] }, - getDataAttributes(element) { - if (typeof element === 'undefined' || element === null) { - return {} - } - - let attributes - if (Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'dataset')) { - attributes = this.extend({}, element.dataset) - } else { - attributes = {} - for (let i = 0; i < element.attributes.length; i++) { - const attribute = element.attributes[i] - if (attribute.nodeName.indexOf('data-') !== -1) { - // remove 'data-' part of the attribute name - const attributeName = attribute.nodeName.substring('data-'.length).replace(/-./g, (str) => str.charAt(1).toUpperCase()) - attributes[attributeName] = attribute.nodeValue - } - } - } - - for (const key in attributes) { - if (!Object.prototype.hasOwnProperty.call(attributes, key)) { - continue - } - - attributes[key] = normalizeData(attributes[key]) - } - - return attributes - }, - - getDataAttribute(element, key) { - return normalizeData(element.getAttribute(`data-${key.replace(/[A-Z]/g, (chr) => `-${chr.toLowerCase()}`)}`)) - }, - isVisible(element) { if (typeof element === 'undefined' || element === null) { return false diff --git a/js/tests/index.html b/js/tests/index.html index 19ff53ce8a..f4a99df44e 100644 --- a/js/tests/index.html +++ b/js/tests/index.html @@ -97,10 +97,11 @@ </script> <!-- Transpiled Plugins --> + <script src="../dist/dom/polyfill.js"></script> + <script src="../dist/dom/manipulator.js"></script> <script src="../dist/dom/eventHandler.js"></script> <script src="../dist/dom/selectorEngine.js"></script> <script src="../dist/dom/data.js"></script> - <script src="../dist/dom/manipulator.js"></script> <script src="../dist/util.js"></script> <script src="../dist/alert.js"></script> <script src="../dist/button.js"></script> @@ -116,6 +117,8 @@ <!-- Unit Tests --> <script src="unit/dom/eventHandler.js"></script> + <script src="unit/dom/manipulator.js"></script> + <script src="unit/dom/data.js"></script> <script src="unit/alert.js"></script> <script src="unit/button.js"></script> <script src="unit/carousel.js"></script> diff --git a/js/tests/karma.conf.js b/js/tests/karma.conf.js index 066165a14d..469a95561f 100644 --- a/js/tests/karma.conf.js +++ b/js/tests/karma.conf.js @@ -140,6 +140,16 @@ if (bundle) { branches: 86, functions: 89, lines: 90 + }, + each: { + overrides: { + 'js/src/dom/polyfill.js': { + statements: 39, + lines: 37, + branches: 19, + functions: 50 + } + } } } } diff --git a/js/tests/unit/dom/manipulator.js b/js/tests/unit/dom/manipulator.js new file mode 100644 index 0000000000..19effa4231 --- /dev/null +++ b/js/tests/unit/dom/manipulator.js @@ -0,0 +1,179 @@ +$(function () { + 'use strict' + + QUnit.module('manipulator') + + QUnit.test('should be defined', function (assert) { + assert.expect(1) + assert.ok(Manipulator, 'Manipulator is defined') + }) + + QUnit.test('should set checked for input', function (assert) { + assert.expect(2) + + var $input = $('<input type="checkbox" />').appendTo('#qunit-fixture') + Manipulator.setChecked($input[0], true) + + assert.ok($input[0].checked) + + Manipulator.setChecked($input[0], false) + + assert.ok(!$input[0].checked) + }) + + QUnit.test('should not set checked for non input element', function (assert) { + assert.expect(1) + + var $div = $('<div />').appendTo('#qunit-fixture') + Manipulator.setChecked($div[0], true) + + assert.ok(typeof $div[0].checked === 'undefined') + }) + + QUnit.test('should verify if an element is checked', function (assert) { + assert.expect(2) + + var $input = $('<input type="checkbox" />').appendTo('#qunit-fixture') + Manipulator.setChecked($input[0], true) + + assert.ok(Manipulator.isChecked($input[0])) + + Manipulator.setChecked($input[0], false) + + assert.ok(!Manipulator.isChecked($input[0])) + }) + + QUnit.test('should throw an error when the element is not an input', function (assert) { + assert.expect(1) + + var $div = $('<div />').appendTo('#qunit-fixture') + try { + Manipulator.isChecked($div[0]) + } catch (e) { + assert.strictEqual(e.message, 'INPUT parameter is not an HTMLInputElement') + } + }) + + QUnit.test('should set data attribute', function (assert) { + assert.expect(1) + + var $div = $('<div />').appendTo('#qunit-fixture') + + Manipulator.setDataAttribute($div[0], 'test', 'test') + + assert.strictEqual($div[0].getAttribute('data-test'), 'test') + }) + + QUnit.test('should set data attribute in lower case', function (assert) { + assert.expect(1) + + var $div = $('<div />').appendTo('#qunit-fixture') + + Manipulator.setDataAttribute($div[0], 'tEsT', 'test') + + assert.strictEqual($div[0].getAttribute('data-test'), 'test') + }) + + QUnit.test('should get data attribute', function (assert) { + assert.expect(2) + + var $div = $('<div data-test="null" />').appendTo('#qunit-fixture') + + assert.strictEqual(Manipulator.getDataAttribute($div[0], 'test'), null) + + var $div2 = $('<div data-test2="js" />').appendTo('#qunit-fixture') + + assert.strictEqual(Manipulator.getDataAttribute($div2[0], 'tEst2'), 'js') + }) + + QUnit.test('should get data attributes', function (assert) { + assert.expect(2) + + var $div = $('<div data-test="js" data-test2="js2" />').appendTo('#qunit-fixture') + var $div2 = $('<div data-test3="js" data-test4="js2" />').appendTo('#qunit-fixture') + + assert.propEqual(Manipulator.getDataAttributes($div[0]), { + test: 'js', + test2: 'js2' + }) + + var stub = sinon + .stub(Object, 'getOwnPropertyDescriptor') + .callsFake(function () { + return false + }) + + assert.propEqual(Manipulator.getDataAttributes($div2[0]), { + test3: 'js', + test4: 'js2' + }) + + stub.restore() + }) + + QUnit.test('should remove data attribute', function (assert) { + assert.expect(2) + + var $div = $('<div />').appendTo('#qunit-fixture') + + Manipulator.setDataAttribute($div[0], 'test', 'test') + + assert.strictEqual($div[0].getAttribute('data-test'), 'test') + + Manipulator.removeDataAttribute($div[0], 'test') + + assert.strictEqual($div[0].getAttribute('data-test'), null) + }) + + QUnit.test('should remove data attribute in lower case', function (assert) { + assert.expect(2) + + var $div = $('<div />').appendTo('#qunit-fixture') + + Manipulator.setDataAttribute($div[0], 'test', 'test') + + assert.strictEqual($div[0].getAttribute('data-test'), 'test') + + Manipulator.removeDataAttribute($div[0], 'tESt') + + assert.strictEqual($div[0].getAttribute('data-test'), null) + }) + + QUnit.test('should return element offsets', function (assert) { + assert.expect(2) + + var $div = $('<div />').appendTo('#qunit-fixture') + + var offset = Manipulator.offset($div[0]) + + assert.ok(typeof offset.top === 'number') + assert.ok(typeof offset.left === 'number') + }) + + QUnit.test('should return element position', function (assert) { + assert.expect(2) + + var $div = $('<div />').appendTo('#qunit-fixture') + + var offset = Manipulator.position($div[0]) + + assert.ok(typeof offset.top === 'number') + assert.ok(typeof offset.left === 'number') + }) + + QUnit.test('should toggle class', function (assert) { + assert.expect(2) + + var $div = $('<div class="test" />').appendTo('#qunit-fixture') + + Manipulator.toggleClass($div[0], 'test') + + assert.ok(!$div.hasClass('test')) + + Manipulator.toggleClass($div[0], 'test') + + assert.ok($div.hasClass('test')) + + Manipulator.toggleClass(null) + }) +}) diff --git a/js/tests/unit/modal.js b/js/tests/unit/modal.js index 7aa7a95b3c..0739f03782 100644 --- a/js/tests/unit/modal.js +++ b/js/tests/unit/modal.js @@ -422,7 +422,7 @@ $(function () { $('<div id="modal-test"/>') .on('hidden.bs.modal', function () { - assert.strictEqual(typeof $body.data('padding-right'), 'undefined', 'data-padding-right should be cleared after closing') + assert.strictEqual(document.body.getAttribute('data-padding-right'), null, 'data-padding-right should be cleared after closing') $body.removeAttr('style') done() }) @@ -488,7 +488,7 @@ $(function () { $('<div id="modal-test"/>') .on('hidden.bs.modal', function () { - assert.strictEqual(typeof $element.data('padding-right'), 'undefined', 'data-padding-right should be cleared after closing') + assert.strictEqual($element[0].getAttribute('data-padding-right'), null, 'data-padding-right should be cleared after closing') $element.remove() done() }) @@ -530,7 +530,7 @@ $(function () { $('<div id="modal-test"/>') .on('hidden.bs.modal', function () { - assert.strictEqual(typeof $element.data('margin-right'), 'undefined', 'data-margin-right should be cleared after closing') + assert.strictEqual($element[0].getAttribute('data-margin-right'), null, 'data-margin-right should be cleared after closing') $element.remove() done() }) |