Add test coverage for utils.closest

Add docblock

Move test to new location

Fix linting issues
This commit is contained in:
Tim Rourke 2017-09-30 07:02:30 -05:00
parent 1c21f18ca1
commit aaa5c2b597
2 changed files with 129 additions and 0 deletions

View File

@ -0,0 +1,68 @@
import {closest} from 'utils';
import {
createSandbox,
} from 'helper';
const sampleMarkup = `
<div class="tree">
<section class="branch">
<ul class="twig">
<li class="leaf"></li>
</ul>
</section>
</div>
`;
describe('utils', () => {
let sandbox;
beforeEach(() => {
sandbox = createSandbox(sampleMarkup);
});
afterEach(() => {
sandbox.parentNode.removeChild(sandbox);
});
test('should return null when no element specified', () => {
expect(closest()).toBe(null);
});
test('should return null when string selector does not match', () => {
const element = sandbox.querySelector('.leaf');
expect(closest(element, 'will-not-match')).toBe(null);
});
test('should return null when function selector does not match', () => {
const element = sandbox.querySelector('.leaf');
function selector() { return false; }
expect(closest(element, selector)).toBe(null);
});
test('should return null when selector targets child element', () => {
const element = sandbox.querySelector('.twig');
expect(closest(element, '.leaf')).toBe(null);
});
[
'.twig',
'ul',
'.branch',
'section',
'.tree',
'div',
'body',
'document',
].forEach((expectedMatchingSelector) => {
test(`should return matched element when selector targets parent element matching selector ${expectedMatchingSelector}`, () => {
const element = sandbox.querySelector('.leaf');
const expected = sandbox.querySelector(expectedMatchingSelector);
expect(closest(element, expectedMatchingSelector)).toBe(expected);
});
});
});

61
src/utils.js Normal file
View File

@ -0,0 +1,61 @@
/** @module utils */
/**
* Get the closest parent element of a given element that matches the given
* selector string or matching function
*
* @param {Element} element The child element to find a parent of
* @param {String|Function} selector The string or function to use to match
* the parent element
* @return {Element|null}
*/
export function closest(element, selector) {
if (!element) {
return null;
}
function conditionFn(currentElement) {
if (!currentElement) {
return currentElement;
} else if (typeof selector === 'string') {
const matchFunction = Element.prototype.matches ||
Element.prototype.webkitMatchesSelector ||
Element.prototype.mozMatchesSelector ||
Element.prototype.msMatchesSelector;
return matchFunction.call(currentElement, selector);
} else {
return selector(currentElement);
}
}
let current = element;
do {
current = current.correspondingUseElement || current.correspondingElement || current;
if (conditionFn(current)) {
return current;
}
current = current.parentNode;
} while (current !== document.body && current !== document);
return null;
}
let scrollAnimationFrame;
export function scroll(element, {clientX, clientY, speed, sensitivity}) {
if (scrollAnimationFrame) {
cancelAnimationFrame(scrollAnimationFrame);
}
function scrollFn() {
const rect = element.getBoundingClientRect();
const offsetY = (Math.abs(rect.bottom - clientY) <= sensitivity) - (Math.abs(rect.top - clientY) <= sensitivity);
const offsetX = (Math.abs(rect.right - clientX) <= sensitivity) - (Math.abs(rect.left - clientX) <= sensitivity);
element.scrollTop += offsetY * speed;
element.scrollLeft += offsetX * speed;
scrollAnimationFrame = requestAnimationFrame(scrollFn);
}
scrollAnimationFrame = requestAnimationFrame(scrollFn);
}