From 649bc1449acb44e7606c16e9936a5124de552020 Mon Sep 17 00:00:00 2001 From: Dakshinamurthy Karra Date: Wed, 17 Oct 2018 10:56:25 +0530 Subject: [PATCH] feat: added prefix_tag option --- build/css-selector-generator.js | 58 +++++++++++----- src/css-selector-generator.coffee | 28 +++++--- test/src/css-selector-generator.spec.coffee | 74 ++++++++++++++++++++- 3 files changed, 135 insertions(+), 25 deletions(-) diff --git a/build/css-selector-generator.js b/build/css-selector-generator.js index a2016c3d0..5dcef3b35 100644 --- a/build/css-selector-generator.js +++ b/build/css-selector-generator.js @@ -4,7 +4,9 @@ CssSelectorGenerator = (function() { CssSelectorGenerator.prototype.default_options = { - selectors: ['id', 'class', 'tag', 'nthchild'] + selectors: ['id', 'class', 'tag', 'nthchild'], + prefix_tag: false, + log: false }; function CssSelectorGenerator(options) { @@ -69,10 +71,11 @@ }; CssSelectorGenerator.prototype.getIdSelector = function(element) { - var id, sanitized_id; + var id, prefix, sanitized_id; + prefix = this.options.prefix_tag ? this.getTagSelector(element) : ''; id = element.getAttribute('id'); if ((id != null) && (id !== '') && !(/\s/.exec(id)) && !(/^\d/.exec(id))) { - sanitized_id = "#" + (this.sanitizeItem(id)); + sanitized_id = prefix + ("#" + (this.sanitizeItem(id))); if (element.ownerDocument.querySelectorAll(sanitized_id).length === 1) { return sanitized_id; } @@ -118,8 +121,9 @@ }; CssSelectorGenerator.prototype.getNthChildSelector = function(element) { - var counter, k, len, parent_element, sibling, siblings; + var counter, k, len, parent_element, prefix, sibling, siblings; parent_element = element.parentNode; + prefix = this.options.prefix_tag ? this.getTagSelector(element) : ''; if (parent_element != null) { counter = 0; siblings = parent_element.childNodes; @@ -128,7 +132,7 @@ if (this.isElement(sibling)) { counter++; if (sibling === element) { - return ":nth-child(" + counter + ")"; + return prefix + (":nth-child(" + counter + ")"); } } } @@ -150,24 +154,28 @@ CssSelectorGenerator.prototype.testUniqueness = function(element, selector) { var found_elements, parent; + if (this.options.log) { + console.log("selector", element, selector); + } parent = element.parentNode; found_elements = parent.querySelectorAll(selector); return found_elements.length === 1 && found_elements[0] === element; }; CssSelectorGenerator.prototype.testCombinations = function(element, items, tag) { - var item, k, l, len, len1, ref, ref1; - ref = this.getCombinations(items); - for (k = 0, len = ref.length; k < len; k++) { - item = ref[k]; - if (this.testUniqueness(element, item)) { - return item; - } + var item, k, l, len, len1, len2, len3, m, n, ref, ref1, ref2, ref3; + if (tag == null) { + tag = this.getTagSelector(element); } - if (tag != null) { - ref1 = items.map(function(item) { - return tag + item; - }); + if (!this.options.prefix_tag) { + ref = this.getCombinations(items); + for (k = 0, len = ref.length; k < len; k++) { + item = ref[k]; + if (this.testSelector(element, item)) { + return item; + } + } + ref1 = this.getCombinations(items); for (l = 0, len1 = ref1.length; l < len1; l++) { item = ref1[l]; if (this.testUniqueness(element, item)) { @@ -175,6 +183,24 @@ } } } + ref2 = this.getCombinations(items).map(function(item) { + return tag + item; + }); + for (m = 0, len2 = ref2.length; m < len2; m++) { + item = ref2[m]; + if (this.testSelector(element, item)) { + return item; + } + } + ref3 = this.getCombinations(items).map(function(item) { + return tag + item; + }); + for (n = 0, len3 = ref3.length; n < len3; n++) { + item = ref3[n]; + if (this.testUniqueness(element, item)) { + return item; + } + } return null; }; diff --git a/src/css-selector-generator.coffee b/src/css-selector-generator.coffee index ab9ec56ce..0541e3861 100644 --- a/src/css-selector-generator.coffee +++ b/src/css-selector-generator.coffee @@ -2,7 +2,8 @@ class CssSelectorGenerator default_options: # choose from 'tag', 'id', 'class', 'nthchild', 'attribute' - selectors: ['id', 'class', 'tag', 'nthchild'] + selectors: ['id', 'class', 'tag', 'nthchild'], + prefix_tag: false, log: false constructor: (options = {}) -> @options = {} @@ -45,6 +46,7 @@ class CssSelectorGenerator getIdSelector: (element) -> + prefix = if @options.prefix_tag then @getTagSelector element else '' id = element.getAttribute 'id' # ID must... exist, not to be empty and not to contain whitespace @@ -58,7 +60,7 @@ class CssSelectorGenerator # ...not start with a number not (/^\d/.exec id) ) - sanitized_id = "##{@sanitizeItem id}" + sanitized_id = prefix + "##{@sanitizeItem id}" # ID must match single element if element.ownerDocument.querySelectorAll(sanitized_id).length is 1 return sanitized_id @@ -88,13 +90,14 @@ class CssSelectorGenerator getNthChildSelector: (element) -> parent_element = element.parentNode + prefix = if @options.prefix_tag then @getTagSelector element else '' if parent_element? counter = 0 siblings = parent_element.childNodes for sibling in siblings if @isElement sibling counter++ - return ":nth-child(#{counter})" if sibling is element + return prefix + ":nth-child(#{counter})" if sibling is element null testSelector: (element, selector) -> @@ -106,6 +109,8 @@ class CssSelectorGenerator testUniqueness: (element, selector) -> + if @options.log + console.log("selector", element, selector) parent = element.parentNode found_elements = parent.querySelectorAll selector found_elements.length is 1 and found_elements[0] is element @@ -113,14 +118,21 @@ class CssSelectorGenerator # helper function that tests all combinations for uniqueness testCombinations: (element, items, tag) -> - for item in @getCombinations items - return item if @testUniqueness element, item + if not tag? + tag = @getTagSelector element - # if tag selector is enabled, try attaching it - if tag? - for item in (items.map (item) -> tag + item) + if not @options.prefix_tag + for item in @getCombinations items + return item if @testSelector element, item + for item in @getCombinations items return item if @testUniqueness element, item + # if tag selector is enabled, try attaching it + for item in (@getCombinations(items).map (item) -> tag + item) + return item if @testSelector element, item + for item in (@getCombinations(items).map (item) -> tag + item) + return item if @testUniqueness element, item + return null diff --git a/test/src/css-selector-generator.spec.coffee b/test/src/css-selector-generator.spec.coffee index 9176b132b..4e3c3ea7f 100644 --- a/test/src/css-selector-generator.spec.coffee +++ b/test/src/css-selector-generator.spec.coffee @@ -130,6 +130,12 @@ describe 'CSS Selector Generator', -> expect(x.getIdSelector elm).toBe '#linkZero' expect(x.getIdSelector root).toBe null + it 'should get ID selector for an element with tag', -> + x.setOptions prefix_tag: true + elm = root.querySelector '#linkZero' + expect(x.getIdSelector elm).toBe 'a#linkZero' + expect(x.getIdSelector root).toBe null + it 'should escape special characters in ID selector', -> special_characters = '*+-./;' for special_character in special_characters.split '' @@ -203,6 +209,11 @@ describe 'CSS Selector Generator', -> root.innerHTML = '
' expect(x.getSelector root.firstChild).toEqual '.aaa' + it 'should get element by class selector with tag', -> + x.setOptions selectors: ['class'], prefix_tag: true + root.innerHTML = '
' + expect(x.getSelector root.firstChild).toEqual 'div.aaa' + it 'should combine class selector with tag selector if needed', -> x.setOptions selectors: ['tag', 'class'] root.innerHTML = ' @@ -217,6 +228,11 @@ describe 'CSS Selector Generator', -> root.innerHTML = '

' expect(x.getSelector root.firstChild).toEqual '.bbb' + it 'should use single unique class when applicable with tag', -> + x.setOptions selectors: ['class'], prefix_tag: true + root.innerHTML = '

' + expect(x.getSelector root.firstChild).toEqual 'p.bbb' + it 'should use combination of classes when applicable', -> x.setOptions selectors: ['class'] root.innerHTML = ' @@ -226,6 +242,15 @@ describe 'CSS Selector Generator', -> ' expect(x.getSelector root.firstChild).toEqual '.aaa.bbb' + it 'should use combination of classes when applicable with tag', -> + x.setOptions selectors: ['class' ], prefix_tag: true + root.innerHTML = ' +
+
+
+ ' + expect(x.getSelector root.firstChild).toEqual 'div.aaa.bbb' + describe 'attribute', -> it 'should get attribute selectors for an element', -> @@ -244,11 +269,23 @@ describe 'CSS Selector Generator', -> result = x.getSelector root.firstChild expect(result).toEqual '[rel=aaa]' + it 'should use attribute selector when enabled with tag', -> + x.setOptions selectors: ['tag', 'id', 'class', + 'attribute', 'nthchild'], prefix_tag: true + root.innerHTML = '' + result = x.getSelector root.firstChild + expect(result).toEqual 'a[rel=aaa]' + it 'should get element by attribute selector', -> x.setOptions selectors: ['attribute'] root.innerHTML = '' expect(x.getSelector root.firstChild).toEqual '[href=aaa]' + it 'should get element by attribute selector with tag', -> + x.setOptions selectors: ['attribute'], prefix_tag: true + root.innerHTML = '' + expect(x.getSelector root.firstChild).toEqual 'a[href=aaa]' + it 'should combine attribute selector with tag selector if needed', -> x.setOptions selectors: ['attribute', 'tag'] root.innerHTML = ' @@ -266,7 +303,15 @@ describe 'CSS Selector Generator', -> ' expect(x.getSelector root.firstChild).toEqual '[rel=bbb]' - it 'should use combination of classes when applicable', -> + it 'should use single unique attribute when applicable with tag', -> + x.setOptions selectors: ['attribute'], prefix_tag: true + root.innerHTML = ' + + + ' + expect(x.getSelector root.firstChild).toEqual 'a[rel=bbb]' + + it 'should use combination of attributes when applicable', -> x.setOptions selectors: ['attribute'] root.innerHTML = ' @@ -275,6 +320,27 @@ describe 'CSS Selector Generator', -> ' expect(x.getSelector root.firstChild).toEqual '[href=aaa][rel=aaa]' + it 'should use combination of attributes when applicable with tag', -> + x.setOptions selectors: ['attribute'], prefix_tag: true + root.innerHTML = ' + + + + ' + expect(x.getSelector root.firstChild).toEqual 'a[href=aaa][rel=aaa]' + + it 'should use unique attribute from document', -> + x.setOptions selectors: ['attribute'] + x.setOptions log:true + root.innerHTML = ' +
link1
+
link2
+ ' + + elm = root.querySelector '#aaa' + expect(x.getSelector elm). + toEqual '[rel=bbb]' + describe 'n-th child', -> it 'should get n-th child selector for an element', -> @@ -282,6 +348,12 @@ describe 'CSS Selector Generator', -> result = x.getNthChildSelector elm expect(result).toBe ':nth-child(7)' + it 'should get n-th child selector for an element with tag', -> + x.setOptions prefix_tag: true + elm = root.querySelector '#linkZero' + result = x.getNthChildSelector elm + expect(result).toBe 'a:nth-child(7)' + describe 'selector test', -> elm = null