feat(queryhandler): add built-in pierce handler (#6509)

Adds a handler 'pierce' that pierces shadow roots while querying.
This commit is contained in:
Johan Bay 2020-10-13 11:05:47 +02:00 committed by GitHub
parent f04bec5a15
commit 8fabe32800
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 93 additions and 1 deletions

View File

@ -112,7 +112,60 @@ const _defaultHandler = makeQueryHandler({
element.querySelectorAll(selector),
});
const _builtInHandlers = new Map([['aria', ariaHandler]]);
const pierceHandler = makeQueryHandler({
queryOne: (element, selector) => {
let found: Element | null = null;
const search = (root: Element | ShadowRoot) => {
const iter = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
do {
const currentNode = iter.currentNode as HTMLElement;
if (currentNode.shadowRoot) {
search(currentNode.shadowRoot);
}
if (currentNode instanceof ShadowRoot) {
continue;
}
if (!found && currentNode.matches(selector)) {
found = currentNode;
}
} while (!found && iter.nextNode());
};
if (element instanceof Document) {
element = element.documentElement;
}
search(element);
return found;
},
queryAll: (element, selector) => {
const result: Element[] = [];
const collect = (root: Element | ShadowRoot) => {
const iter = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
do {
const currentNode = iter.currentNode as HTMLElement;
if (currentNode.shadowRoot) {
collect(currentNode.shadowRoot);
}
if (currentNode instanceof ShadowRoot) {
continue;
}
if (currentNode.matches(selector)) {
result.push(currentNode);
}
} while (iter.nextNode());
};
if (element instanceof Document) {
element = element.documentElement;
}
collect(element);
return result;
},
});
const _builtInHandlers = new Map([
['aria', ariaHandler],
['pierce', pierceHandler],
]);
const _queryHandlers = new Map(_builtInHandlers);
/**

View File

@ -68,6 +68,45 @@ describe('querySelector', function () {
});
});
describe('pierceHandler', function () {
beforeEach(async () => {
const { page } = getTestState();
await page.setContent(
`<script>
const div = document.createElement('div');
const shadowRoot = div.attachShadow({mode: 'open'});
const div1 = document.createElement('div');
div1.textContent = 'Hello';
div1.className = 'foo';
const div2 = document.createElement('div');
div2.textContent = 'World';
div2.className = 'foo';
shadowRoot.appendChild(div1);
shadowRoot.appendChild(div2);
document.documentElement.appendChild(div);
</script>`
);
});
it('should find first element in shadow', async () => {
const { page } = getTestState();
const div = await page.$('pierce/.foo');
const text = await div.evaluate(
(element: Element) => element.textContent
);
expect(text).toBe('Hello');
});
it('should find all elements in shadow', async () => {
const { page } = getTestState();
const divs = await page.$$('pierce/.foo');
const text = await Promise.all(
divs.map((div) =>
div.evaluate((element: Element) => element.textContent)
)
);
expect(text.join(' ')).toBe('Hello World');
});
});
// The tests for $$eval are repeated later in this file in the test group 'QueryAll'.
// This is done to also test a query handler where QueryAll returns an Element[]
// as opposed to NodeListOf<Element>.