Skip to content

Commit

Permalink
capricorn86#792@minor: Add adjacent sibling selector.
Browse files Browse the repository at this point in the history
  • Loading branch information
btea committed Feb 25, 2023
1 parent 13bcfe7 commit f169c44
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 7 deletions.
36 changes: 29 additions & 7 deletions packages/happy-dom/src/query-selector/QuerySelector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import INodeList from '../nodes/node/INodeList';
import SelectorItem from './SelectorItem';
import NodeListFactory from '../nodes/node/NodeListFactory';

const SELECTOR_PART_REGEXP = /(\[[^\]]+\]|[a-zA-Z0-9-_.#"*:()\]]+)|([ ,>]+)/g;
const SELECTOR_PART_REGEXP = /(\[[^\]]+\]|[a-zA-Z0-9-_.#"*:()\]]+)|([ ,>+]+)/g;

/**
* Utility for query selection in an HTML element.
Expand Down Expand Up @@ -141,13 +141,15 @@ export default class QuerySelector {
selectorItem?: SelectorItem
): IElement[] {
const isDirectChild = selectorParts[0] === '>';
if (isDirectChild) {
const isNextSibling = selectorParts[0] === '+';
if (isDirectChild || isNextSibling) {
selectorParts = selectorParts.slice(1);
}
const selector = selectorItem || new SelectorItem(selectorParts[0]);
let matched = [];

for (const node of nodes) {
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
if (node.nodeType === Node.ELEMENT_NODE) {
if (selector.match(<Element>node).matches) {
if (selectorParts.length === 1) {
Expand All @@ -158,11 +160,16 @@ export default class QuerySelector {
matched = matched.concat(
this.findAll(rootNode, (<Element>node).children, selectorParts.slice(1), null)
);
if (nodes[i + 1]) {
matched = matched.concat(
this.findAll(rootNode, [nodes[i + 1]], selectorParts.slice(1), null)
);
}
}
}
}

if (!isDirectChild && node['children']) {
if (!isDirectChild && !isNextSibling && node['children']) {
matched = matched.concat(this.findAll(rootNode, node['children'], selectorParts, selector));
}
}
Expand All @@ -187,12 +194,14 @@ export default class QuerySelector {
selectorItem?: SelectorItem
): IElement {
const isDirectChild = selectorParts[0] === '>';
if (isDirectChild) {
const isNextSibling = selectorParts[0] === '+';
if (isDirectChild || isNextSibling) {
selectorParts = selectorParts.slice(1);
}
const selector = selectorItem || new SelectorItem(selectorParts[0]);

for (const node of nodes) {
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
if (node.nodeType === Node.ELEMENT_NODE && selector.match(<Element>node).matches) {
if (selectorParts.length === 1) {
if (rootNode !== node) {
Expand All @@ -208,10 +217,21 @@ export default class QuerySelector {
if (childSelector) {
return childSelector;
}
if (nodes[i + 1]) {
const siblingSelector = this.findFirst(
rootNode,
[nodes[i + 1]],
selectorParts.slice(1),
null
);
if (siblingSelector) {
return siblingSelector;
}
}
}
}

if (!isDirectChild && node['children']) {
if (!isDirectChild && !isNextSibling && node['children']) {
const childSelector = this.findFirst(rootNode, node['children'], selectorParts, selector);

if (childSelector) {
Expand Down Expand Up @@ -252,6 +272,8 @@ export default class QuerySelector {
parts = [];
} else if (trimmed === '>') {
parts.push('>');
} else if (trimmed === '+') {
parts.push('+');
}
} else if (match[1]) {
currentSelector += match[1];
Expand Down
22 changes: 22 additions & 0 deletions packages/happy-dom/test/query-selector/QuerySelector.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,28 @@ describe('QuerySelector', () => {
expect(elements[2]).toBe(container.children[0].children[1].children[2]);
});

it('Returns all elements with tag name and class ".a + .b".', () => {
const div = document.createElement('div');
div.innerHTML = `
<div class="a">a</div>
<div class="b">b</div>
<div class="c">c</div>
<div class="a">d</div>
<div class="b">e</div>
<div class="a">f</div>
`;
const one = div.querySelector('.a + .b');
expect(one).toBe(div.children[1]);
const el = div.querySelectorAll('.a + .b');
expect(el.length).toBe(2);
expect(el[0].textContent).toBe('b');
expect(el[1].textContent).toBe('e');
const two = div.querySelector('.a + .c');
expect(two).toBe(null);
const three = div.querySelector('.e + .f');
expect(three).toBe(div.children[5]);
});

it('Returns all elements with matching attributes using "[attr1="value1"]".', () => {
const container = document.createElement('div');
container.innerHTML = QuerySelectorHTML;
Expand Down

0 comments on commit f169c44

Please sign in to comment.