Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 46 additions & 25 deletions src/browser/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AstSelector, AstString, createParser } from 'css-selector-parser'
import { AstPseudoClass, AstSelector, AstString, createParser } from 'css-selector-parser'
import { throwIfUndefined } from 'throw-expression'

const getAllParents = (element: sap.ui.core.Element): string[] => {
Expand Down Expand Up @@ -37,7 +37,7 @@ const parse = createParser({
namespace: false,
attributes: { operators: ['=', '^=', '$=', '*=', '~=', '|='] },
pseudoElements: { definitions: ['subclass'] },
pseudoClasses: { definitions: { Selector: ['has'] } },
pseudoClasses: { definitions: { Selector: ['has'], String: ['has-label'] } },
tag: { wildcard: true },
ids: true,
// classes are actually not supported but need to enable this to support . in tag names,
Expand Down Expand Up @@ -99,29 +99,50 @@ const querySelector = (root: Element | Document, selector: AstSelector): Element
})
})
return controls
.map((control) => control.getDomRef())
.filter((element): element is Element => {
if (
element === null ||
// on nested selectors/locators, exclude any elements from outside that scope by making sure they're present in this root:
root.querySelector(`[id='${element.id}']`) === null
) {
return false
}
if (
rule.pseudoClasses &&
querySelector(
element,
throwIfUndefined(
rule.pseudoClasses[0],
'":has" pseudo-class was specified without an argument',
).argument as AstSelector,
).length === 0
) {
return false
}
return true
})
.map((control) => ({ control, element: control.getDomRef() }))
.filter(
(
controlAndElement,
): controlAndElement is { control: sap.ui.core.Element; element: Element } => {
const { control, element } = controlAndElement
if (
element === null ||
// on nested selectors/locators, exclude any elements from outside that scope by making sure they're present in this root:
root.querySelector(`[id='${element.id}']`) === null
) {
return false
}
for (const pseudoClass of rule.pseudoClasses ?? []) {
const getArgument = <T extends AstPseudoClass['argument']>() =>
throwIfUndefined(
pseudoClass,
`":${pseudoClass.name} pseudo-class was specified without an argument`,
).argument as T
switch (pseudoClass.name) {
case 'has':
if (
querySelector(element, getArgument<AstSelector>()).length === 0
) {
return false
}
break
case 'has-label': {
const labelToFind = getArgument<AstString>().value
if (
!(control instanceof sap.m.InputBase) ||
!control
.getLabels()
.find((label) => label.getProperty('text') === labelToFind)
) {
return false
}
}
}
}
return true
},
)
.map(({ element }) => element)
})

const queryAll = (root: Element | Document, selector: string): Element[] => {
Expand Down
33 changes: 21 additions & 12 deletions tests/spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,18 +120,27 @@ test.describe('ui5 site - button', () => {
})
})

test('~', async ({ page }) => {
await navigateToControlSample(page, 'sap.m', 'sap.m.sample.InputAssisted')
// on webkit clicking it brings up a popup with a second input box
const control = page.locator('ui5=sap.m.Input').last()
// need to click and use keyboard because on webkit the input field is readonly
await control.click()
// focus the second one
await control.click()
const element = control.locator('input')
await element.fill('asdf ')
await element.blur()
await expect(control.and(page.locator("ui5=[value~='asdf']"))).toBeVisible()
test.describe('ui5 site - Input', () => {
test.beforeEach(({ page }) =>
navigateToControlSample(page, 'sap.m', 'sap.m.sample.InputAssisted'),
)
test('~', async ({ page }) => {
// on webkit clicking it brings up a popup with a second input box
const control = page.locator('ui5=sap.m.Input').last()
// need to click and use keyboard because on webkit the input field is readonly
await control.click()
// focus the second one
await control.click()
const element = control.locator('input')
await element.fill('asdf ')
await element.blur()
await expect(control.and(page.locator("ui5=[value~='asdf']"))).toBeVisible()
})
test.describe(':has-label', () => {
test('exists', ({ page }) => expect(page.locator('ui5=:has-label(Product)')).toHaveCount(1))
test("doesn't exist", ({ page }) =>
expect(page.locator('ui5=:has-label(asdf)')).toHaveCount(0))
})
})

test.describe('no ui5 site', () => {
Expand Down