Skip to content

Commit

Permalink
chore: [#1507] Fixes selector validation, so that it converts values …
Browse files Browse the repository at this point in the history
…to string
  • Loading branch information
capricorn86 committed Aug 30, 2024
1 parent 9bde659 commit 5b2a828
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 52 deletions.
110 changes: 58 additions & 52 deletions packages/happy-dom/src/query-selector/QuerySelector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,27 +78,32 @@ export default class QuerySelector {
node: Element | Document | DocumentFragment,
selector: string
): NodeList<Element> {
if (selector === null || selector === undefined) {
return new NodeList<Element>(PropertySymbol.illegalConstructor, []);
}

const window = node[PropertySymbol.window];

if (<string>selector === '') {
throw new window.Error(
throw new window.DOMException(
`Failed to execute 'querySelectorAll' on '${node.constructor.name}': The provided selector is empty.`
);
}

if (typeof selector !== 'string' && typeof selector !== 'boolean') {
if (typeof selector === 'function') {
throw new window.DOMException(
`Failed to execute 'querySelectorAll' on '${node.constructor.name}': '${selector}' is not a valid selector.`,
'SyntaxError'
`Failed to execute 'querySelectorAll' on '${node.constructor.name}': '${selector}' is not a valid selector.`
);
}

if (typeof selector === 'symbol') {
throw new window.TypeError(`Cannot convert a Symbol value to a string`);
}

selector = String(selector);

if (INVALID_SELECTOR_REGEXP.test(selector)) {
throw new window.DOMException(
`Failed to execute 'querySelectorAll' on '${node.constructor.name}': '${selector}' is not a valid selector.`
);
}

const cache = node[PropertySymbol.cache].querySelectorAll;
const cachedResult = cache.get(selector);

Expand All @@ -109,12 +114,6 @@ export default class QuerySelector {
}
}

if (INVALID_SELECTOR_REGEXP.test(selector)) {
throw new window.Error(
`Failed to execute 'querySelectorAll' on '${node.constructor.name}': '${selector}' is not a valid selector.`
);
}

const groups = SelectorParser.getSelectorGroups(selector);
const items: Element[] = [];
const nodeList = new NodeList<Element>(PropertySymbol.illegalConstructor, items);
Expand Down Expand Up @@ -199,27 +198,38 @@ export default class QuerySelector {
node: Element | Document | DocumentFragment,
selector: string
): Element | null {
if (selector === null || selector === undefined) {
return null;
}

const window = node[PropertySymbol.window];

if (selector === '') {
throw new window.Error(
throw new window.DOMException(
`Failed to execute 'querySelector' on '${node.constructor.name}': The provided selector is empty.`
);
}

if (typeof selector !== 'string' && typeof selector !== 'boolean') {
if (typeof selector === 'function' || typeof selector === 'symbol') {
throw new window.DOMException(
`Failed to execute 'querySelector' on '${node.constructor.name}': '${selector}' is not a valid selector.`,
'SyntaxError'
`Failed to execute 'querySelector' on '${node.constructor.name}': '${selector}' is not a valid selector.`
);
}

if (typeof selector === 'function') {
throw new window.DOMException(
`Failed to execute 'querySelector' on '${node.constructor.name}': '${selector}' is not a valid selector.`
);
}

if (typeof selector === 'symbol') {
throw new window.TypeError(`Cannot convert a Symbol value to a string`);
}

selector = String(selector);

if (INVALID_SELECTOR_REGEXP.test(selector)) {
throw new window.DOMException(
`Failed to execute 'querySelector' on '${node.constructor.name}': '${selector}' is not a valid selector.`
);
}

const cachedResult = node[PropertySymbol.cache].querySelector.get(selector);

if (cachedResult?.result) {
Expand All @@ -229,12 +239,6 @@ export default class QuerySelector {
}
}

if (INVALID_SELECTOR_REGEXP.test(selector)) {
throw new window.Error(
`Failed to execute 'querySelector' on '${node.constructor.name}': '${selector}' is not a valid selector.`
);
}

const cachedItem: ICachedQuerySelectorItem = {
result: <WeakRef<Element | null>>{
deref: () => null
Expand Down Expand Up @@ -277,55 +281,57 @@ export default class QuerySelector {
selector: string,
options?: { ignoreErrors?: boolean }
): ISelectorMatch | null {
if (!selector) {
return null;
}
const ignoreErrors = options?.ignoreErrors;
const window = element[PropertySymbol.window];

if (selector === null || selector === undefined) {
if (selector === '*') {
return {
priorityWeight: 0
priorityWeight: 1
};
}

const window = element[PropertySymbol.window];

if (<string>selector === '') {
throw new window.Error(
if (ignoreErrors) {
return null;
}
throw new window.DOMException(
`Failed to execute 'matches' on '${element.constructor.name}': The provided selector is empty.`
);
}

if (typeof selector !== 'string' && typeof selector !== 'boolean') {
if (typeof selector === 'function') {
if (ignoreErrors) {
return null;
}
throw new window.DOMException(
`Failed to execute 'matches' on '${element.constructor.name}': '${selector}' is not a valid selector.`,
DOMExceptionNameEnum.syntaxError
`Failed to execute 'matches' on '${element.constructor.name}': '${selector}' is not a valid selector.`
);
}

selector = String(selector);

if (selector === '*') {
return {
priorityWeight: 1
};
if (typeof selector === 'symbol') {
if (ignoreErrors) {
return null;
}
throw new window.TypeError(`Cannot convert a Symbol value to a string`);
}

const ignoreErrors = options?.ignoreErrors;
const cachedResult = element[PropertySymbol.cache].matches.get(selector);

if (cachedResult?.result) {
return cachedResult.result.match;
}
selector = String(selector);

if (INVALID_SELECTOR_REGEXP.test(selector)) {
if (ignoreErrors) {
return null;
}
throw new window.Error(
throw new window.DOMException(
`Failed to execute 'matches' on '${element.constructor.name}': '${selector}' is not a valid selector.`
);
}

const cachedResult = element[PropertySymbol.cache].matches.get(selector);

if (cachedResult?.result) {
return cachedResult.result.match;
}

const cachedItem: ICachedMatchesItem = {
result: { match: null }
};
Expand Down
80 changes: 80 additions & 0 deletions packages/happy-dom/test/query-selector/QuerySelector.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,38 @@ describe('QuerySelector', () => {
expect(() => container.querySelectorAll(<string>(<unknown>true))).not.toThrow();
});

it('Converts selector values to string.', () => {
const container = document.createElement('div');
container.innerHTML = `
<span>
<false></false>
<true></true>
<null></null>
<undefined></undefined>
</span>
`;

const elements = container.querySelectorAll(<string>(<unknown>['false']));
expect(elements.length).toBe(1);
expect(elements[0] === container.children[0].children[0]).toBe(true);

const elements2 = container.querySelectorAll(<string>(<unknown>false));
expect(elements2.length).toBe(1);
expect(elements2[0] === container.children[0].children[0]).toBe(true);

const elements3 = container.querySelectorAll(<string>(<unknown>true));
expect(elements3.length).toBe(1);
expect(elements3[0] === container.children[0].children[1]).toBe(true);

const elements4 = container.querySelectorAll(<string>(<unknown>null));
expect(elements4.length).toBe(1);
expect(elements4[0] === container.children[0].children[2]).toBe(true);

const elements5 = container.querySelectorAll(<string>(<unknown>undefined));
expect(elements5.length).toBe(1);
expect(elements5[0] === container.children[0].children[3]).toBe(true);
});

it('Returns all span elements.', () => {
const container = document.createElement('div');
container.innerHTML = QuerySelectorHTML;
Expand Down Expand Up @@ -1134,6 +1166,38 @@ describe('QuerySelector', () => {
expect(() => container.querySelector(<string>(<unknown>true))).not.toThrow();
});

it('Converts selector values to string.', () => {
const container = document.createElement('div');
container.innerHTML = `
<span>
<false></false>
<true></true>
<null></null>
<undefined></undefined>
</span>
`;

expect(container.querySelector(<string>(<unknown>['false']))).toBe(
container.children[0].children[0]
);

expect(container.querySelector(<string>(<string>(<unknown>false)))).toBe(
container.children[0].children[0]
);

expect(container.querySelector(<string>(<string>(<unknown>true)))).toBe(
container.children[0].children[1]
);

expect(container.querySelector(<string>(<string>(<unknown>null)))).toBe(
container.children[0].children[2]
);

expect(container.querySelector(<string>(<string>(<unknown>undefined)))).toBe(
container.children[0].children[3]
);
});

it('Returns a span matching "span".', () => {
const div1 = document.createElement('div');
const div2 = document.createElement('div');
Expand Down Expand Up @@ -1513,6 +1577,22 @@ describe('QuerySelector', () => {
expect(() => container.matches(<string>(<unknown>true))).not.toThrow();
});

it('Converts selector values to string.', () => {
const container = document.createElement('div');
container.innerHTML = `
<false></false>
<true></true>
<null></null>
<undefined></undefined>
`;

expect(container.children[0].matches(<string>(<unknown>['false']))).toBe(true);
expect(container.children[0].matches(<string>(<unknown>false))).toBe(true);
expect(container.children[1].matches(<string>(<unknown>true))).toBe(true);
expect(container.children[2].matches(<string>(<unknown>null))).toBe(true);
expect(container.children[3].matches(<string>(<unknown>undefined))).toBe(true);
});

it('Returns true when the element matches the selector', () => {
const div = document.createElement('div');
div.innerHTML = '<div class="foo"></div>';
Expand Down

0 comments on commit 5b2a828

Please sign in to comment.