Skip to content

Commit

Permalink
fix: do not ignore root, use descendant selectors
Browse files Browse the repository at this point in the history
refs #78
  • Loading branch information
fczbkk committed Aug 25, 2021
1 parent cc4decb commit 29e4fc6
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 49 deletions.
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export function getCssSelector (
foundElements,
selector
} = closestIdentifiableParent
if (testSelector(elements, selector)) {
if (testSelector(elements, selector, options.root)) {
return selector
}
currentRoot = foundElements[0]
Expand Down
100 changes: 52 additions & 48 deletions src/utilities-selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import cartesian from 'cartesian'
import {
CHILD_OPERATOR,
DESCENDANT_OPERATOR,
SELECTOR_PATTERN
SELECTOR_PATTERN,
} from './constants'
import {getAttributeSelectors} from './selector-attribute'
import {getClassSelectors} from './selector-class'
Expand All @@ -12,15 +12,15 @@ import {getNthOfTypeSelector} from './selector-nth-of-type'
import {getTagSelector} from './selector-tag'
import {
convertMatchListToRegExp,
flattenArray
flattenArray,
} from './utilities-data'
import {getParents, testSelector} from './utilities-dom'
import {
CssSelector,
CssSelectorData,
CssSelectorGeneratorOptions,
CssSelectorType,
IdentifiableParent
IdentifiableParent,
} from './types'
import isElement from 'iselement'
import {getPowerSet} from './utilities-powerset'
Expand Down Expand Up @@ -58,15 +58,15 @@ export const SELECTOR_TYPE_GETTERS = {
class: getClassSelectors,
attribute: getAttributeSelectors,
nthchild: getNthChildSelector,
nthoftype: getNthOfTypeSelector
nthoftype: getNthOfTypeSelector,
}

/**
* Returns list of selectors of given type for the element.
*/
export function getSelectorsByType (
elements: Element[],
selector_type: CssSelectorType
selector_type: CssSelectorType,
): Array<CssSelector> {
const getter = (
SELECTOR_TYPE_GETTERS[selector_type]
Expand All @@ -81,7 +81,7 @@ export function getSelectorsByType (
export function filterSelectors (
list: Array<CssSelector> = [],
blacklist_re: RegExp,
whitelist_re: RegExp
whitelist_re: RegExp,
): Array<CssSelector> {
return list.filter((item) => (
whitelist_re.test(item)
Expand All @@ -94,7 +94,7 @@ export function filterSelectors (
*/
export function orderSelectors (
list: Array<CssSelector> = [],
whitelist_re: RegExp
whitelist_re: RegExp,
): Array<CssSelector> {
return list.sort((a, b) => {
const a_is_whitelisted = whitelist_re.test(a)
Expand All @@ -115,7 +115,7 @@ export function orderSelectors (
export function getAllSelectors (
elements: Element[],
root: ParentNode,
options: CssSelectorGeneratorOptions
options: CssSelectorGeneratorOptions,
): Array<CssSelector> {
const selectors_list = getSelectorsList(elements, options)
const type_combinations = getTypeCombinations(selectors_list, options)
Expand All @@ -128,13 +128,13 @@ export function getAllSelectors (
*/
export function getSelectorsList (
elements: Element[],
options: CssSelectorGeneratorOptions
options: CssSelectorGeneratorOptions,
): CssSelectorData {
const {
blacklist,
whitelist,
combineWithinSelector,
maxCombinations
maxCombinations,
} = options

const blacklist_re = convertMatchListToRegExp(blacklist)
Expand All @@ -161,11 +161,11 @@ export function getSelectorsList (
* Creates list of selector types that we will need to generate the selector.
*/
export function getSelectorsToGet (
options: CssSelectorGeneratorOptions
options: CssSelectorGeneratorOptions,
): Array<CssSelectorType> {
const {
selectors,
includeTag
includeTag,
} = options

const selectors_to_get = [].concat(selectors)
Expand All @@ -181,7 +181,7 @@ export function getSelectorsToGet (
* TAG part.
*/
function addTagTypeIfNeeded (
list: Array<CssSelectorType>
list: Array<CssSelectorType>,
): Array<CssSelectorType> {
return (list.includes('tag') || list.includes('nthoftype'))
? [...list]
Expand All @@ -192,13 +192,13 @@ function addTagTypeIfNeeded (
* Generates list of possible selector type combinations.
*/
export function combineSelectorTypes (
options: CssSelectorGeneratorOptions
options: CssSelectorGeneratorOptions,
): Array<Array<CssSelectorType>> {
const {
selectors,
combineBetweenSelectors,
includeTag,
maxCandidates
maxCandidates,
} = options

const combinations = combineBetweenSelectors
Expand All @@ -215,7 +215,7 @@ export function combineSelectorTypes (
*/
export function getTypeCombinations (
selectors_list: CssSelectorData,
options: CssSelectorGeneratorOptions
options: CssSelectorGeneratorOptions,
): Array<Array<CssSelector>> {
return combineSelectorTypes(options)
.map((item) => {
Expand All @@ -229,7 +229,7 @@ export function getTypeCombinations (
*/
export function constructSelectors (
selector_types: Array<CssSelectorType>,
selectors_by_type: CssSelectorData
selectors_by_type: CssSelectorData,
): Array<CssSelector> {
const data: CssSelectorData = {}
selector_types.forEach((selector_type) => {
Expand All @@ -248,7 +248,7 @@ export function constructSelectors (
*/
export function constructSelectorType (
selector_type: CssSelectorType,
selectors_data: CssSelectorData
selectors_data: CssSelectorData,
): CssSelector {
return (selectors_data[selector_type])
? selectors_data[selector_type].join('')
Expand All @@ -259,7 +259,7 @@ export function constructSelectorType (
* Converts selector data object to a selector.
*/
export function constructSelector (
selectorData: CssSelectorData = {}
selectorData: CssSelectorData = {},
): CssSelector {
const pattern = [...SELECTOR_PATTERN]
// selector "nthoftype" already contains "tag"
Expand All @@ -273,28 +273,32 @@ export function constructSelector (
}

/**
* Generator of CSS selector candidates for given element, from simplest child selectors to more complex descendant selectors.
* Generates combinations of child and descendant selectors within root selector.
*/
export function getElementSelectorCandidates (
elements: Element[],
root: ParentNode,
options: CssSelectorGeneratorOptions
): Array<CssSelector> {
const result = []
const selectorCandidates = getAllSelectors(elements, root, options)
for (const selectorCandidate of selectorCandidates) {
result.push(CHILD_OPERATOR + selectorCandidate)
}
function generateCandidateCombinations (
selectors: CssSelector[],
rootSelector: CssSelector
): CssSelector[] {
return [
...selectors.map(
(selector) => rootSelector + CHILD_OPERATOR + selector
),
...selectors.map(
(selector) => rootSelector + DESCENDANT_OPERATOR + selector
),
]
}

if (elements.every((element) => (
element.parentNode === root
&& element.parentNode !== options.root
))) {
for (const selectorCandidate of selectorCandidates) {
result.push(DESCENDANT_OPERATOR + selectorCandidate)
}
}
return result
/**
* Generates a list of selector candidates that can potentially match target element.
*/
function generateCandidates (
selectors: CssSelector[],
rootSelector: CssSelector
): CssSelector[] {
return rootSelector === ''
? selectors
: generateCandidateCombinations(selectors, rootSelector)
}

/**
Expand All @@ -304,14 +308,13 @@ export function getSelectorWithinRoot (
elements: Element[],
root: ParentNode,
rootSelector: CssSelector = '',
options: CssSelectorGeneratorOptions
options: CssSelectorGeneratorOptions,
): (null | CssSelector) {
const selectorCandidates =
getElementSelectorCandidates(elements, options.root, options)
const elementSelectors = getAllSelectors(elements, options.root, options)
const selectorCandidates = generateCandidates(elementSelectors, rootSelector)
for (const candidateSelector of selectorCandidates) {
const attemptSelector = (rootSelector + candidateSelector).trim()
if (testSelector(elements, attemptSelector, options.root)) {
return attemptSelector
if (testSelector(elements, candidateSelector, options.root)) {
return candidateSelector
}
}
return null
Expand All @@ -324,15 +327,16 @@ export function getClosestIdentifiableParent (
elements: Element[],
root: ParentNode,
rootSelector: CssSelector = '',
options: CssSelectorGeneratorOptions
options: CssSelectorGeneratorOptions,
): IdentifiableParent {
if (elements.length === 0) {
return null
}

const candidatesList = [
(elements.length > 1) ? elements : [],
...getParents(elements, root).map((element) => [element])
...getParents(elements, root)
.map((element) => [element]),
]

for (const currentElements of candidatesList) {
Expand All @@ -341,7 +345,7 @@ export function getClosestIdentifiableParent (
if (result) {
return {
foundElements: currentElements,
selector: result
selector: result,
}
}
}
Expand Down
26 changes: 26 additions & 0 deletions test/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,32 @@ describe('CssSelectorGenerator', function () {
assert.equal(result, '#aaa .aaa')
})

it('should produce descendant selector', () => {
root.innerHTML = `
<div>
<span>
<span></span>
</span>
</div>
`
const needle = root.firstElementChild.firstElementChild
const result = getCssSelector(needle, {root})
assert.equal(result, 'div > span')
})

it('should take root element into account', () => {
root.innerHTML = `
<span></span>
<div>
<span></span>
</div>
`
const haystack = root.querySelector('div')
const needle = haystack.querySelector('span')
const result = getCssSelector(needle, {root: haystack})
assert.equal(result, 'span')
})

})

describe('special scenarios', function () {
Expand Down
2 changes: 2 additions & 0 deletions tools/karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ module.exports = (config) => {
config.set({
files: [
'../test/**/*.spec.js',
'../test/**/*.spec.ts',
],
preprocessors: {
'../test/**/*.spec.js': ['webpack'],
'../test/**/*.spec.ts': ['webpack'],
},
webpack: webpack_config,
webpackMiddleware: {
Expand Down

0 comments on commit 29e4fc6

Please sign in to comment.