fix(selectors): text engine after capture matches scope (#4749)

This commit is contained in:
Dmitry Gozman 2020-12-17 06:19:43 -08:00 committed by GitHub
parent 35533b15c1
commit 9a0023cc03
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 16 additions and 5 deletions

View file

@ -54,7 +54,7 @@ export function parseSelector(selector: string, customNames: Set<string>): Parse
};
}
const chain = (from: number, to: number): CSSComplexSelector => {
const chain = (from: number, to: number, turnFirstTextIntoScope: boolean): CSSComplexSelector => {
const result: CSSComplexSelector = { simples: [] };
for (const part of v1.parts.slice(from, to)) {
let name = part.name;
@ -76,6 +76,8 @@ export function parseSelector(selector: string, customNames: Set<string>): Parse
}
} else if (name === 'text') {
let simple = textSelectorToSimple(part.body);
if (turnFirstTextIntoScope)
simple.functions.push({ name: 'is', args: [ simpleToComplex(callWith('scope', [])), simpleToComplex({ css: '*', functions: [] }) ]});
if (result.simples.length)
result.simples[result.simples.length - 1].combinator = '>=';
if (wrapInLight)
@ -87,14 +89,16 @@ export function parseSelector(selector: string, customNames: Set<string>): Parse
simple = callWith('light', [simpleToComplex(simple)]);
result.simples.push({ selector: simple, combinator: '' });
}
if (name !== 'text')
turnFirstTextIntoScope = false;
}
return result;
};
const capture = v1.capture === undefined ? v1.parts.length - 1 : v1.capture;
const result = chain(0, capture + 1);
const result = chain(0, capture + 1, false);
if (capture + 1 < v1.parts.length) {
const has = chain(capture + 1, v1.parts.length);
const has = chain(capture + 1, v1.parts.length, true);
const last = result.simples[result.simples.length - 1];
last.selector.functions.push({ name: 'has', args: [has] });
}

View file

@ -120,8 +120,8 @@ export class SelectorEvaluatorImpl implements SelectorEvaluator {
private _matchesSimple(element: Element, simple: CSSSimpleSelector, context: QueryContext): boolean {
return this._cached<boolean>(this._cacheMatchesSimple, element, [simple, context], () => {
const isScopeClause = simple.functions.some(f => f.name === 'scope');
if (!isScopeClause && element === context.scope)
const isPossiblyScopeClause = simple.functions.some(f => f.name === 'scope' || f.name === 'is');
if (!isPossiblyScopeClause && element === context.scope)
return false;
if (simple.css && !this._matchesCSS(element, simple.css))
return false;

View file

@ -348,6 +348,8 @@ it('should work with :is', async ({page, server}) => {
expect(await page.$$eval(`css=:is(div, span)`, els => els.length)).toBe(7);
expect(await page.$$eval(`css=section:is(section) div:is(section div)`, els => els.length)).toBe(3);
expect(await page.$$eval(`css=:is(div, span) > *`, els => els.length)).toBe(6);
expect(await page.$$eval(`css=#root1:has(:is(#root1))`, els => els.length)).toBe(0);
expect(await page.$$eval(`css=#root1:has(:is(:scope, #root1))`, els => els.length)).toBe(1);
});
it('should work with :has', async ({page, server}) => {

View file

@ -227,3 +227,8 @@ it('should match root after >>', async ({page, server}) => {
const element2 = await page.$('text=test >> text=test');
expect(element2).toBeTruthy();
});
it('should match root after >> with *', async ({ page }) => {
await page.setContent(`<button> hello world </button> <button> hellow <span> world </span> </button>`);
expect(await page.$$eval('*css=button >> text=hello >> text=world', els => els.length)).toBe(2);
});