diff --git a/__tests__/integration/index.test.js b/__tests__/integration/index.test.js
index 5fa3c8f..5167f56 100644
--- a/__tests__/integration/index.test.js
+++ b/__tests__/integration/index.test.js
@@ -22,6 +22,16 @@ const installSimmerOnWindow = windowScope => {
const createWindow = (dom = '') =>
installSimmerOnWindow(new JSDOM(`${dom}`).window)
+const testExpectSelector = (bodySelector, resultSelector) => {
+ const windowScope = createWindow(fixture)
+ var elements = compareElementsAndSimmer(windowScope, bodySelector)
+ expect(elements).not.toBe(undefined)
+ expect(elements.SimmerEl).not.toBe(undefined)
+ expect(elements.el).not.toBe(undefined)
+ expect(elements.el).toBe(elements.SimmerEl)
+ expect(elements.selector).toEqual(resultSelector)
+}
+
test(`can analyze an element with an ID`, function () {
const windowScope = createWindow(fixture)
var elements = compareElementsAndSimmer(windowScope, '#BodyDiv')
@@ -290,13 +300,16 @@ test(`can analyze an element with a class that contains a colon`, function () {
})
test(`can analyze an element with data-attr`, function () {
- const windowScope = createWindow(fixture)
- var elements = compareElementsAndSimmer(windowScope, '#somestuff')
- expect(elements).not.toBe(undefined)
- expect(elements.SimmerEl).not.toBe(undefined)
- expect(elements.el).not.toBe(undefined)
- expect(elements.el).toBe(elements.SimmerEl)
- expect(elements.selector).toEqual("[data-attr='blue']")
+ testExpectSelector('.data-attrs div:nth-child(1)', "[data-attr='blue']")
+})
+
+test(`can analyze an element with data attributes`, function () {
+ testExpectSelector('.data-attrs div:nth-child(2)', "[data-greek-letter='alpha']")
+})
+
+test(`can analyze an element with non-unique data attributes`, function () {
+ // :TODO: This is worse than could actually be generated
+ testExpectSelector('.data-attrs [data-test-attribute-foo] div', "[data-test-attribute-foo] > *")
})
test(`can't analyze an element which is longer than the selectorMaxLength chars`, function () {
diff --git a/modules/methods/inspectDataAttr.js b/modules/methods/inspectDataAttr.js
index 064e574..830e0db 100644
--- a/modules/methods/inspectDataAttr.js
+++ b/modules/methods/inspectDataAttr.js
@@ -1,5 +1,5 @@
-import { isUniqueDataAttr } from '../queryEngine'
-import { attr } from './validationHelpers'
+import { isUniqueSelector } from '../queryEngine'
+import { validDataSelector } from './validationHelpers'
/**
* Inspect the elements' IDs and add them to the CSS Selector
* @param {array} hierarchy. The hierarchy of elements
@@ -8,31 +8,37 @@ import { attr } from './validationHelpers'
export default function (hierarchy, state, validateSelector, config, query) {
return hierarchy.reduce((selectorState, currentElem, index) => {
if (!selectorState.verified) {
- const [validatedState] = [currentElem.el.getAttribute('data-attr')]
- .filter(id => attr(id))
- .map(dataAttr => {
- const isUnique = isUniqueDataAttr(query, dataAttr)
- selectorState.stack[index].push(`[data-attr='${dataAttr}']`)
- selectorState.specificity += isUnique ? 100 : 50
+ const [bestAttribute] = currentElem.el.getAttributeNames()
+ .filter((attrName) => attrName.indexOf('data-') === 0)
+ .map((attrName) => {
+ const value = currentElem.el.getAttribute(attrName)
+ return value !== '' ? `[${attrName}='${value}']` : `[${attrName}]`
+ })
+ .filter(validDataSelector)
+ .sort()
+ .map((selector) => ({ selector, specificity: isUniqueSelector(query, selector) ? 100 : 50 }))
+ .sort((a, b) => a.specificity - b.specificity)
- if (selectorState.specificity >= config.specificityThreshold) {
- // we have reached the minimum specificity, lets try verifying now, as this will save us having to add more IDs to the selector
- if (validateSelector(selectorState)) {
- // The ID worked like a charm - mark this state as verified and move on!
- selectorState.verified = true
- }
- }
+ if (bestAttribute) {
+ selectorState.stack[index].push(bestAttribute.selector)
+ selectorState.specificity += bestAttribute.specificity
- if (!selectorState.verified && index === 0) {
- // if the index is 0 then this is the data-attr of the actual element! Which means we have found our selector!
- // The ID wasn't enough, this means the page, this should never happen as we tested for the data-attr's uniquness, but just incase
- // we will pop it from the stack as it only adds noise
- selectorState.stack[index].pop()
- selectorState.specificity -= isUnique ? 100 : 50
+ if (selectorState.specificity >= config.specificityThreshold) {
+ // we have reached the minimum specificity, lets try verifying now, as this will save us having to add more IDs to the selector
+ if (validateSelector(selectorState)) {
+ // The ID worked like a charm - mark this state as verified and move on!
+ selectorState.verified = true
}
- return selectorState
- })
- return validatedState || selectorState
+ }
+
+ if (!selectorState.verified && index === 0) {
+ // if the index is 0 then this is the data-attr of the actual element! Which means we have found our selector!
+ // The ID wasn't enough, this means the page, this should never happen as we tested for the data-attr's uniquness, but just incase
+ // we will pop it from the stack as it only adds noise
+ selectorState.stack[index].pop()
+ selectorState.specificity -= bestAttribute.specificity
+ }
+ }
}
return selectorState
}, state)
diff --git a/modules/methods/validationHelpers.js b/modules/methods/validationHelpers.js
index 809fb95..712e676 100644
--- a/modules/methods/validationHelpers.js
+++ b/modules/methods/validationHelpers.js
@@ -13,6 +13,7 @@ export function tagName (tagName) {
}
return false
}
+
/**
* Validate the syntax of an attribute to make sure that it has a valid syntax for the query engine.
* @param {string} attribute. The element's attribute's value
@@ -27,6 +28,16 @@ export function attr (attribute) {
return false
}
+export function validDataSelector(selector) {
+ if (
+ typeof selector === 'string' &&
+ selector.match(/^\[data-[0-9a-zA-Z_\-:.]+(='[0-9a-zA-Z_\-:.]*')?\]$/gi) !== null
+ ) {
+ return selector
+ }
+ return false
+}
+
/**
* Validate the syntax of an attribute to make sure that it has a valid syntax for the query engine.
* @param {string} attribute. The element's attribute's value
diff --git a/modules/queryEngine.js b/modules/queryEngine.js
index 06fdc00..1ee74ca 100644
--- a/modules/queryEngine.js
+++ b/modules/queryEngine.js
@@ -6,14 +6,11 @@ import { querySelectorAllDeep } from '@mariusandra/query-selector-shadow-dom'
* @param {object} state. The current selector state (has the stack and specificity sum)
*/
export function isUniqueElementID (query, elementID) {
- // use selector to query an element and see if it is a one-to-one selection
- var results = query(`[id="${elementID}"]`) || []
- return results.length === 1
+ return isUniqueSelector(query, `[id="${elementID}"]`)
}
-export function isUniqueDataAttr (query, dataAttr) {
- // use selector to query an element and see if it is a one-to-one selection
- var results = query(`[data-attr="${dataAttr}"]`) || []
+export function isUniqueSelector(query, selector) {
+ var results = query(selector) || []
return results.length === 1
}