Skip to content

Commit

Permalink
feat: accept HTMLCollection or NodeList as needle
Browse files Browse the repository at this point in the history
  • Loading branch information
fczbkk committed Nov 23, 2022
1 parent edd174e commit 18ce528
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 32 deletions.
3 changes: 3 additions & 0 deletions src/utilities-selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,9 @@ export function getClosestIdentifiableParent(
* Converts input into list of elements, removing duplicates and non-elements.
*/
export function sanitizeSelectorNeedle(needle: unknown): Element[] {
if (needle instanceof NodeList || needle instanceof HTMLCollection) {
needle = Array.from(needle);
}
const elements = (Array.isArray(needle) ? needle : [needle]).filter(
isElement
);
Expand Down
27 changes: 27 additions & 0 deletions src/utilities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export function getCommonParent (needle: Element[]) {
if (needle.length > 0) {
let parent = needle[0].parentElement

// optimization for single element
if (needle.length === 1) {
return parent
}

// find common parent for multiple elements
while (parent) {
if (needle.every(element => parent.contains(element))) {
return parent
}
parent = parent.parentElement
}
}
return null
}

export function * parentsGenerator (needle: Element[]) {
// TODO
}

export function * viableParentsGenerator () {
// TODO
}
39 changes: 7 additions & 32 deletions test/memo.spec.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,16 @@
import { assert } from "chai";
import { createMemo } from "../src/memo.js";
import { CssSelectorType } from "../src/types.js";

function createRoot() {
return document.body.appendChild(document.createElement("div"));
}
import {createRoot, getTargetElement, getTargetElements} from "./test-utilities";

describe("Memo", () => {
let root = createRoot();

/**
* Simple way to retrieve target element for test.
* @returns {Element}
*/
function getTargetElement() {
return root.querySelector("[data-target]");
}

/**
* Simple way to retrieve multiple target elements for test.
* @returns {Element[]}
*/
function getTargetElements() {
return root.querySelectorAll("[data-target]");
}

beforeEach(() => {
root = createRoot();
});

afterEach(() => {
root.parentNode.removeChild(root);
});
let root: Element;
beforeEach(() => { root = createRoot(); });
afterEach(() => { root.parentNode.removeChild(root); });

it("should retrieve selectors for an element", () => {
root.innerHTML = "<div data-target></div>";
const element = getTargetElement();
const element = getTargetElement(root);
const getElementSelectors = createMemo();
const result = getElementSelectors(element, [CssSelectorType.tag]);
assert.deepEqual(result, { [CssSelectorType.tag]: ["div"] });
Expand All @@ -46,15 +21,15 @@ describe("Memo", () => {
<div data-target class='aaa bbb'></div>
<div data-target class='aaa ccc'></div>
`;
const elements = [...getTargetElements()];
const elements = [...getTargetElements(root)];
const getElementSelectors = createMemo();
const result = getElementSelectors(elements, [CssSelectorType.class]);
assert.deepEqual(result, { [CssSelectorType.class]: [".aaa"] });
});

it("should use remembered value", () => {
root.innerHTML = "<div data-target></div>";
const element = getTargetElement();
const element = getTargetElement(root);
const memoData = new Map([
[element, new Map([[CssSelectorType.tag, ["mock_tag_selector"]]])],
]);
Expand Down
19 changes: 19 additions & 0 deletions test/test-utilities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export function createRoot() {
return document.body.appendChild(document.createElement("div"));
}

/**
* Simple way to retrieve target element for test.
* @returns {Element}
*/
export function getTargetElement(root: Element): Element {
return root.querySelector("[data-target]");
}

/**
* Simple way to retrieve multiple target elements for test.
* @returns {Element[]}
*/
export function getTargetElements(root: Element): Element[] {
return [...root.querySelectorAll("[data-target]")];
}
35 changes: 35 additions & 0 deletions test/utilities-selectors.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {sanitizeSelectorNeedle} from "../src/utilities-selectors";
import {assert} from "chai";
import {createRoot} from "./test-utilities";

describe('Utilities - selectors', () => {
let root: Element;
beforeEach(() => { root = createRoot(); });
afterEach(() => { root.parentNode.removeChild(root); });

describe('sanitizeSelectorNeedle', () => {
it('should remove non-element inputs', () => {
const result = sanitizeSelectorNeedle([1, false, null, 'aaa', undefined]);
assert.equal(result.length, 0);
});
it('should convert single element into an array', () => {
const result = sanitizeSelectorNeedle(root)
assert.equal(result.length, 1);
});
it('should return array of elements', () => {
root.innerHTML = '<div></div><div></div>';
const result = sanitizeSelectorNeedle([root.children[0], root.children[1]]);
assert.equal(result.length, 2)
})
it('should convert NodeList into array of elements', () => {
root.innerHTML = '<div></div><div></div>';
const result = sanitizeSelectorNeedle(root.querySelectorAll('div'));
assert.equal(result.length, 2)
});
it('should convert HTMLCollection into array of elements', () => {
root.innerHTML = '<form></form><form></form>';
const result = sanitizeSelectorNeedle(document.forms);
assert.equal(result.length, 2)
});
})
});
52 changes: 52 additions & 0 deletions test/utilities.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {
createRoot,
getTargetElement,
getTargetElements
} from "./test-utilities";
import {assert} from "chai";
import {getCommonParent} from "../src/utilities";

describe('Utilities', () => {

describe('getCommonParent', () => {
let root: Element;
beforeEach(() => { root = createRoot(); });
afterEach(() => { root.parentNode.removeChild(root); });

it('should get direct parent of single element', () => {
root.innerHTML = `
<div>
<div class="directParent">
<div data-target></div>
</div>
</div>
`
const target = getTargetElement(root);
const result = getCommonParent([target]);
assert.equal(result.className, 'directParent')
});
it('should get common parent of multiple elements', () => {
root.innerHTML = `
<div>
<div class="commonParent">
<div>
<div data-target></div>
</div>
<div data-target></div>
</div>
</div>
`
const target = getTargetElements(root);
const result = getCommonParent(target);
assert.equal(result.className, 'commonParent')
});
it('should return `null` if there is no common parent', () => {
const result = getCommonParent([
document.createElement('div'),
document.createElement('div')
])
assert.isNull(result)
});
})

})

0 comments on commit 18ce528

Please sign in to comment.