Skip to content

Commit

Permalink
check selectors for multiple elements
Browse files Browse the repository at this point in the history
  • Loading branch information
autarc committed Nov 4, 2016
1 parent 2ceca7b commit 33cbb87
Showing 1 changed file with 56 additions and 29 deletions.
85 changes: 56 additions & 29 deletions src/optimize.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,29 @@ import adapt from './adapt'
/**
* Apply different optimization techniques
*
* @param {string} selector - [description]
* @param {HTMLElement} element - [description]
* @return {string} - [description]
* @param {string} selector - [description]
* @param {HTMLElement|Array.<HTMLElement>} element - [description]
* @param {Object} options - [description]
* @return {string} - [description]
*/
export default function optimize (selector, element, options = {}) {
export default function optimize (selector, elements, options = {}) {

if (!element || element.nodeType !== 1) {
throw new Error(`Invalid input - to compare HTMLElements its necessary to provide a reference of the node! (missing "element")`)
// convert single entry and NodeList
if (!Array.isArray(elements)) {
elements = !elements.length ? [elements] : [...elements]
}

const globalModified = adapt(element, options)
if (!elements.length || elements.some((element) => element.nodeType !== 1)) {
throw new Error(`Invalid input - to compare HTMLElements its necessary to provide a reference of the selected node(s)! (missing "elements")`)
}

const globalModified = adapt(elements[0], options)

// chunk parts outside of quotes (http://stackoverflow.com/a/25663729)
var path = selector.replace(/> /g, '>').split(/\s+(?=(?:(?:[^"]*"){2})*[^"]*$)/)

if (path.length < 3) {
return optimizePart('', selector, '', element)
return optimizePart('', selector, '', elements)
}

const shortened = [path.pop()]
Expand All @@ -37,16 +43,16 @@ export default function optimize (selector, element, options = {}) {

const pattern = `${prePart} ${postPart}`
const matches = document.querySelectorAll(pattern)
if (matches.length !== 1) {
shortened.unshift(optimizePart(prePart, current, postPart, element))
if (matches.length !== elements.length) {
shortened.unshift(optimizePart(prePart, current, postPart, elements))
}
}
shortened.unshift(path[0])
path = shortened

// optimize start + end
path[0] = optimizePart('', path[0], path.slice(1).join(' '), element)
path[path.length-1] = optimizePart(path.slice(0, -1).join(' '), path[path.length-1], '', element)
path[0] = optimizePart('', path[0], path.slice(1).join(' '), elements)
path[path.length-1] = optimizePart(path.slice(0, -1).join(' '), path[path.length-1], '', elements)

if (globalModified) {
delete global.document
Expand All @@ -58,13 +64,13 @@ export default function optimize (selector, element, options = {}) {
/**
* Improve a chunk of the selector
*
* @param {string} prePart - [description]
* @param {string} current - [description]
* @param {string} postPart - [description]
* @param {HTMLElement} element - [description]
* @return {string} - [description]
* @param {string} prePart - [description]
* @param {string} current - [description]
* @param {string} postPart - [description]
* @param {Array.<HTMLElement>} elements - [description]
* @return {string} - [description]
*/
function optimizePart (prePart, current, postPart, element) {
function optimizePart (prePart, current, postPart, elements) {
if (prePart.length) prePart = `${prePart} `
if (postPart.length) postPart = ` ${postPart}`

Expand All @@ -73,17 +79,18 @@ function optimizePart (prePart, current, postPart, element) {
const key = current.replace(/=.*$/, ']')
var pattern = `${prePart}${key}${postPart}`
var matches = document.querySelectorAll(pattern)
if (matches.length === 1 && matches[0] === element) {
if (compareResults(matches, elements)) {
current = key
} else {
// robustness: replace specific key-value with tag (heuristic)
// robustness: replace specific key-value with base tag (heuristic)
const references = document.querySelectorAll(`${prePart}${key}`)
for (var i = 0, l = references.length; i < l; i++) {
if (references[i].contains(element)) {
const description = references[i].tagName.toLowerCase()
const reference = references[i]
if (elements.some((element) => reference.contains(element))) {
const description = reference.tagName.toLowerCase()
var pattern = `${prePart}${description}${postPart}`
var matches = document.querySelectorAll(pattern)
if (matches.length === 1 && matches[0] === element) {
if (compareResults(matches, elements)) {
current = description
}
break
Expand All @@ -97,7 +104,7 @@ function optimizePart (prePart, current, postPart, element) {
const descendant = current.replace(/>/, '')
var pattern = `${prePart}${descendant}${postPart}`
var matches = document.querySelectorAll(pattern)
if (matches.length === 1 && matches[0] === element) {
if (compareResults(matches, elements)) {
current = descendant
}
}
Expand All @@ -108,7 +115,7 @@ function optimizePart (prePart, current, postPart, element) {
const type = current.replace(/nth-child/g, 'nth-of-type')
var pattern = `${prePart}${type}${postPart}`
var matches = document.querySelectorAll(pattern)
if (matches.length === 1 && matches[0] === element) {
if (compareResults(matches, elements)) {
current = type
}
}
Expand All @@ -124,21 +131,22 @@ function optimizePart (prePart, current, postPart, element) {
break
}
var matches = document.querySelectorAll(pattern)
if (matches.length === 1 && matches[0] === element) {
if (compareResults(matches, elements)) {
current = partial
}
}
// robustness: degrade complex classname (heuristic)
if (current && current.match(/\./g).length > 2) {
const references = document.querySelectorAll(`${prePart}${current}`)
for (var i = 0, l = references.length; i < l; i++) {
if (references[i].contains(element)) {
const reference = references[i]
if (elements.some((element) => reference.contains(element) )) {
// TODO:
// - check using attributes + regard excludes
const description = references[i].tagName.toLowerCase()
const description = reference.tagName.toLowerCase()
var pattern = `${prePart}${description}${postPart}`
var matches = document.querySelectorAll(pattern)
if (matches.length === 1 && matches[0] === element) {
if (compareResults(matches, elements)) {
current = description
}
break
Expand All @@ -149,3 +157,22 @@ function optimizePart (prePart, current, postPart, element) {

return current
}

/**
* Evaluate matches with expected elements
*
* @param {Array.<HTMLElement>} matches - [description]
* @param {Array.<HTMLElement>} elements - [description]
* @return {Boolean} - [description]
*/
function compareResults (matches, elements) {
const { length } = matches
return length === elements.length && elements.every((element) => {
for (var i = 0; i < length; i++) {
if (matches[i] === element) {
return true
}
}
return false
})
}

0 comments on commit 33cbb87

Please sign in to comment.