Skip to content

Commit

Permalink
Fixed closest helper (#6712)
Browse files Browse the repository at this point in the history
Add additional undefined check to the loop expression.

Issue: #6711

Co-authored-by: Piotr Laszczkowski <swistach@users.noreply.github.com>
  • Loading branch information
2 people authored and jansiegel committed Feb 17, 2020
1 parent 17b8c33 commit a54f922
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 11 deletions.
15 changes: 15 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -116,7 +116,7 @@
"on-build-webpack": "^0.1.0",
"optimize-css-assets-webpack-plugin": "^5.0.1",
"progress-bar-webpack-plugin": "^1.10.0",
"puppeteer": "^2.0.0",
"puppeteer": "^2.1.1",
"rimraf": "^2.5.4",
"spawn-command": "0.0.2",
"string-replace-webpack-plugin": "^0.1.3",
Expand Down
26 changes: 16 additions & 10 deletions src/helpers/dom/element.js
Expand Up @@ -66,23 +66,29 @@ export function hasAccessToParentWindow(frame) {
}

/**
* Goes up the DOM tree (including given element) until it finds an element that matches the nodes or nodes name.
* Goes up the DOM tree (including given element) until it finds an parent element that matches the nodes or nodes name.
* This method goes up through web components.
*
* @param {HTMLElement} element Element from which traversing is started
* @param {Array} nodes Array of elements or Array of elements name
* @param {HTMLElement} [until]
* @returns {HTMLElement|null}
* @param {Node} element Element from which traversing is started.
* @param {(string | Node)[]} [nodes] Array of elements or Array of elements name (in uppercase form).
* @param {Node} [until] The element until the traversing ends.
* @returns {Node|null}
*/
export function closest(element, nodes, until) {
export function closest(element, nodes = [], until) {
const { ELEMENT_NODE, DOCUMENT_FRAGMENT_NODE } = Node;
let elementToCheck = element;

while (elementToCheck !== null && elementToCheck !== until) {
if (elementToCheck.nodeType === Node.ELEMENT_NODE && (nodes.indexOf(elementToCheck.nodeName) > -1 || nodes.indexOf(elementToCheck) > -1)) {
while (elementToCheck !== null && elementToCheck !== void 0 && elementToCheck !== until) {
const { nodeType, nodeName } = elementToCheck;

if (nodeType === ELEMENT_NODE && (nodes.includes(nodeName) || nodes.includes(elementToCheck))) {
return elementToCheck;
}
if (elementToCheck.host && elementToCheck.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
elementToCheck = elementToCheck.host;

const { host } = elementToCheck;

if (host && nodeType === DOCUMENT_FRAGMENT_NODE) {
elementToCheck = host;

} else {
elementToCheck = elementToCheck.parentNode;
Expand Down
107 changes: 107 additions & 0 deletions test/unit/helpers/dom/Element.spec.js
@@ -1,5 +1,6 @@
import {
addClass,
closest,
closestDown,
getParent,
hasClass,
Expand Down Expand Up @@ -28,6 +29,112 @@ describe('DomElement helper', () => {
});
});

//
// Handsontable.helper.closest
//
describe('closest', () => {
describe('catching errors', () => {
it('should return null if element is falsy (null, undefined)', () => {
expect(closest()).toBe(null);
expect(closest(null)).toBe(null);
});

it('should return null if element is not valid', () => {
expect(closest(123)).toBe(null);
expect(closest('123')).toBe(null);
expect(closest(true)).toBe(null);
expect(closest({})).toBe(null);
});
});

describe('lookup for the closest element', () => {
let wrapper = null;

beforeEach(() => {
wrapper = document.createElement('div');
});

afterEach(() => {
wrapper = null;
});

it('should return element itself if the searched elment is the same one', () => {
wrapper.innerHTML = '<a><b><c></c></b></a>';

const element = wrapper.querySelector('c');

expect(closest(element, [element])).toBe(element);
expect(closest(element, ['C'])).toBe(element);
});

it('should return element declared in nodes as string', () => {
wrapper.innerHTML = '<a><b><c></c></b></a>';

const element = wrapper.querySelector('c');

expect(closest(element, ['B'])).toBe(wrapper.querySelector('b'));
});

it('should return null if declared nodes are passed as lowercase string', () => {
wrapper.innerHTML = '<a><b><c></c></b></a>';

const element = wrapper.querySelector('c');

expect(closest(element, ['b'])).toBe(null);
});

it('should return null if the searched element is also an until element', () => {
wrapper.innerHTML = '<a><b><c></c></b></a>';

const element = wrapper.querySelector('c');
const nodes = ['a', 'b'];
const until = element;

expect(closest(element, nodes, until)).toBe(null);
});

it('should return null if doesn\'t find any element fitting to the nodes\' list', () => {
wrapper.innerHTML = '<a><b><c></c></b></a>';

const element = wrapper.querySelector('c');
const nodes = ['x', 'y', 'z'];

expect(closest(element, nodes)).toBe(null);
});

it('should return null if the searched element lies over until element', () => {
wrapper.innerHTML = '<a><b><c></c></b></a>';

const element = wrapper.querySelector('c');
const nodes = ['A'];
const until = wrapper.querySelector('b');

expect(closest(element, nodes, until)).toBe(null);
});

it('should return the closest parent from the starting element', () => {
wrapper.innerHTML = '<a><b><c></c></b></a>';

const parentA = wrapper.querySelector('a');
const parentB = wrapper.querySelector('b');
const element = wrapper.querySelector('c');
const nodes = [parentA, parentB];

expect(closest(element, nodes)).toBe(parentB);
});

it('should not throw an error if window is starting element', () => {
wrapper.innerHTML = '<a><b><c></c></b></a>';

const element = window;
const nodes = ['A'];
const until = wrapper.querySelector('b');

expect(closest(element, nodes, until)).toBe(null);
});
});
});

//
// Handsontable.helper.closestDown
//
Expand Down

0 comments on commit a54f922

Please sign in to comment.