diff --git a/.codeclimate.yml b/.codeclimate.yml index f1ecd1f14..c10b9266c 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,5 +1,11 @@ version: "2" checks: + argument-count: + config: + threshold: 10 + method-count: + config: + threshold: 25 method-lines: config: threshold: 30 diff --git a/src/js/utils/modal.js b/src/js/utils/modal.js index b4f9a40b9..129cf8092 100644 --- a/src/js/utils/modal.js +++ b/src/js/utils/modal.js @@ -28,11 +28,13 @@ function _createModalContainer() { function _createMaskContainer() { const element = document.createElementNS(svgNS, 'mask'); - element.setAttributeNS(null, 'id', elementIds.modalOverlayMask); - element.setAttributeNS(null, 'x', '0'); - element.setAttributeNS(null, 'y', '0'); - element.setAttributeNS(null, 'width', '100%'); - element.setAttributeNS(null, 'height', '100%'); + _setAttributes(element, { + height: '100%', + id: elementIds.modalOverlayMask, + width: '100%', + x: '0', + y: '0' + }); return element; } @@ -43,11 +45,13 @@ function _createMaskContainer() { function _createMaskRect() { const element = document.createElementNS(svgNS, 'rect'); - element.setAttributeNS(null, 'x', '0'); - element.setAttributeNS(null, 'y', '0'); - element.setAttributeNS(null, 'width', '100%'); - element.setAttributeNS(null, 'height', '100%'); - element.setAttributeNS(null, 'fill', '#FFFFFF'); + _setAttributes(element, { + fill: '#FFFFFF', + height: '100%', + width: '100%', + x: '0', + y: '0' + }); return element; } @@ -58,8 +62,10 @@ function _createMaskRect() { function _createMaskOpening() { const element = document.createElementNS(svgNS, 'rect'); - element.setAttributeNS(null, 'id', elementIds.modalOverlayMaskOpening); - element.setAttributeNS(null, 'fill', '#000000'); + _setAttributes(element, { + fill: '#000000', + id: elementIds.modalOverlayMaskOpening + }); return element; } @@ -70,11 +76,13 @@ function _createMaskOpening() { function _createMaskConsumer() { const element = document.createElementNS(svgNS, 'rect'); - element.setAttributeNS(null, 'x', '0'); - element.setAttributeNS(null, 'y', '0'); - element.setAttributeNS(null, 'width', '100%'); - element.setAttributeNS(null, 'height', '100%'); - element.setAttributeNS(null, 'mask', `url(#${elementIds.modalOverlayMask})`); + _setAttributes(element, { + height: '100%', + width: '100%', + x: '0', + y: '0' + }); + element.setAttribute('mask', `url(#${elementIds.modalOverlayMask})`); return element; } @@ -83,15 +91,15 @@ function _createMaskConsumer() { * Generates an SVG with the following structure: * ```html * - - - - - - - - - + + + + + + + + + * ``` */ function createModalOverlay() { @@ -113,23 +121,27 @@ function createModalOverlay() { return containerElement; } +/** + * Uses the bounds of the element we want the opening overtop of to set the dimensions of the opening and position it + * @param {HTMLElement} targetElement The element the opening will expose + * @param {SVGElement} openingElement The svg mask for the opening + */ function positionModalOpening(targetElement, openingElement) { if (targetElement.getBoundingClientRect && openingElement instanceof SVGElement) { const { x, y, width, height } = targetElement.getBoundingClientRect(); - openingElement.setAttributeNS(null, 'x', x); - openingElement.setAttributeNS(null, 'y', y); - openingElement.setAttributeNS(null, 'width', width); - openingElement.setAttributeNS(null, 'height', height); + _setAttributes(openingElement, { x, y, width, height }); } } function closeModalOpening(openingElement) { if (openingElement && openingElement instanceof SVGElement) { - openingElement.setAttributeNS(null, 'x', '0'); - openingElement.setAttributeNS(null, 'y', '0'); - openingElement.setAttributeNS(null, 'width', '0'); - openingElement.setAttributeNS(null, 'height', '0'); + _setAttributes(openingElement, { + height: '0', + x: '0', + y: '0', + width: '0' + }); } } @@ -159,6 +171,18 @@ function toggleShepherdModalClass(currentElement) { currentElement.classList.add(classNames.modalTarget); } +/** + * Set multiple attributes on an element, via a hash + * @param {HTMLElement|SVGElement} el The element to set the attributes on + * @param {Object} attrs A hash of key value pairs for attributes to set + * @private + */ +function _setAttributes(el, attrs) { + Object.keys(attrs).forEach((key) => { + el.setAttribute(key, attrs[key]); + }); +} + export { createModalOverlay, positionModalOpening, diff --git a/test/unit/utils/dom.spec.js b/test/unit/utils/dom.spec.js index 688b1f592..ebc7bdc7d 100644 --- a/test/unit/utils/dom.spec.js +++ b/test/unit/utils/dom.spec.js @@ -1,14 +1,63 @@ -import { elementIsHidden } from '../../../src/js/utils/dom'; +import { + elementIsHidden, + getElementForStep +} from '../../../src/js/utils/dom'; describe('DOM Utils', function() { describe('elementIsHidden', function() { it('returns true when hidden', () => { - const element = { - offsetHeight: 0, - offsetWidth: 0 - }; + const element = document.createElement('div'); + element.setAttribute('offsetHeight', 0); + element.setAttribute('offsetWidth', 0); expect(elementIsHidden(element), 'evaluates to true when offset height and width are 0').toBeTruthy(); }); }); + + describe('getElementForStep', function() { + it('attachTo object - element is HTMLElement', () => { + const element = document.createElement('div'); + const step = { + options: { + attachTo: { + element, + on: 'bottom' + } + } + }; + + expect(getElementForStep(step), 'returns element as is for HTMLElement').toEqual(element); + }); + + it('attachTo object - element is selector', () => { + const element = document.createElement('div'); + element.classList.add('foo'); + document.body.appendChild(element); + + const step = { + options: { + attachTo: { + element: '.foo', + on: 'bottom' + } + } + }; + + expect(getElementForStep(step), 'returns element from selector').toEqual(element); + }); + + it('attachTo string', () => { + const element = document.createElement('div'); + element.classList.add('foo'); + document.body.appendChild(element); + + const step = { + options: { + attachTo: '.foo bottom' + } + }; + + expect(getElementForStep(step), 'returns element from selector').toEqual(element); + }); + }); }); \ No newline at end of file diff --git a/test/unit/utils/modal.spec.js b/test/unit/utils/modal.spec.js new file mode 100644 index 000000000..e08fb187d --- /dev/null +++ b/test/unit/utils/modal.spec.js @@ -0,0 +1,47 @@ +import { + closeModalOpening, + positionModalOpening +} from '../../../src/js/utils/modal'; + +const svgNS = 'http://www.w3.org/2000/svg'; + +describe('Modal Utils', function() { + describe('closeModalOpening', function() { + it('sets the correct attributes when closed', () => { + const element = document.createElementNS(svgNS, 'rect'); + element.setAttribute('x', 20); + element.setAttribute('y', 20); + element.setAttribute('width', '100%'); + element.setAttribute('height', '100%'); + + closeModalOpening(element); + + expect(element.getAttribute('x'), 'x should be 0').toBe('0'); + expect(element.getAttribute('y'), 'y should be 0').toBe('0'); + expect(element.getAttribute('width'), 'width should be \'0\'').toBe('0'); + expect(element.getAttribute('height'), 'height should be \'0\'').toBe('0'); + }); + }); + + describe('positionModalOpening', function() { + it('sets the correct attributes when positioning modal opening', () => { + const targetElement = document.createElement('div'); + targetElement.getBoundingClientRect = () => { + return { + x: 20, + y: 20, + width: 500, + height: 250 + }; + }; + + const svgElement = document.createElementNS(svgNS, 'rect'); + positionModalOpening(targetElement, svgElement); + + expect(svgElement.getAttribute('x'), 'x should be 20').toBe('20'); + expect(svgElement.getAttribute('y'), 'y should be 20').toBe('20'); + expect(svgElement.getAttribute('width'), 'width should be 500').toBe('500'); + expect(svgElement.getAttribute('height'), 'height should be 250').toBe('250'); + }); + }); +}); \ No newline at end of file