Skip to content

Commit

Permalink
feat: ignore invalid ID attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
fczbkk committed Nov 26, 2014
1 parent 8c1d6ed commit 0726f6c
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 35 deletions.
11 changes: 10 additions & 1 deletion build/css-selector-generator.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 30 additions & 19 deletions src/css-selector-generator.coffee
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class CssSelectorGenerator
constructor: ->

isElement: (element) ->
!!(element?.nodeType is 1)

Expand All @@ -12,14 +12,25 @@ class CssSelectorGenerator
result.push current_element
current_element = current_element.parentNode
result

getTagSelector: (element) ->
element.tagName.toLowerCase()


validateId: (id) ->
if id?

# ID can not start with number
return true unless /^[0-9]/.exec id

false

getIdSelector: (element) ->
id = element.getAttribute 'id'
if id? then "##{id}" else null

if @validateId id
"##{id}"
else
null

getClassSelectors: (element) ->
result = []
class_string = element.getAttribute 'class'
Expand All @@ -31,15 +42,15 @@ class CssSelectorGenerator
if class_string isnt ''
result = (".#{item}" for item in class_string.split /\s+/)
result

getAttributeSelectors: (element) ->
result = []
blacklist = ['id', 'class']
for attribute in element.attributes
unless attribute.nodeName in blacklist
result.push "[#{attribute.nodeName}=#{attribute.nodeValue}]"
result

getNthChildSelector: (element) ->
parent_element = element.parentNode
if parent_element?
Expand All @@ -50,14 +61,14 @@ class CssSelectorGenerator
counter++
return ":nth-child(#{counter})" if sibling is element
null

testSelector: (element, selector) ->
is_unique = false
if selector? and selector isnt ''
result = element.ownerDocument.querySelectorAll selector
is_unique = true if result.length is 1 and result[0] is element
is_unique

getAllSelectors: (element) ->
{
t: @getTagSelector element # tag
Expand All @@ -66,37 +77,37 @@ class CssSelectorGenerator
a: @getAttributeSelectors element # attributes
n: @getNthChildSelector element # n-th child
}

testUniqueness: (element, selector) ->
parent = element.parentNode
found_elements = parent.querySelectorAll selector
found_elements.length is 1 and found_elements[0] is element

getUniqueSelector: (element) ->
selectors = @getAllSelectors element

# ID selector (no need to check for uniqueness)
return selectors.i if selectors.i?

# tag selector (should return unique for BODY)
return selectors.t if @testUniqueness element, selectors.t

# TODO check each class separately
# class selector
if selectors.c.length isnt 0
all_classes = selectors.c.join ''

# class selector without tag
selector = all_classes
return selector if @testUniqueness element, selector

# class selector with tag
selector = selectors.t + all_classes
return selector if @testUniqueness element, selector

# if anything else fails, return n-th child selector
return selectors.n

getSelector: (element) ->
all_selectors = []
parents = @getParents element
Expand All @@ -109,6 +120,6 @@ class CssSelectorGenerator
result = selectors.join ' > '
return result if @testSelector element, result
null

root = if exports? then exports else this
root.CssSelectorGenerator = CssSelectorGenerator
root.CssSelectorGenerator = CssSelectorGenerator
35 changes: 20 additions & 15 deletions test/src/css-selector-generator.spec.coffee
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
describe 'CSS Selector Generator', ->

root = null
x = null

beforeEach ->
x = new CssSelectorGenerator()
root = document.createElement 'div'
Expand Down Expand Up @@ -57,20 +57,20 @@ describe 'CSS Selector Generator', ->
</ul>
"
document.body.appendChild root

afterEach ->
root.parentNode.removeChild root

it 'should exist', ->
expect(CssSelectorGenerator).toBeDefined()

it 'should check if provided object is HTML element', ->
expect(x.isElement()).toBe false
expect(x.isElement null).toBe false
expect(x.isElement 'aaa').toBe false
expect(x.isElement {aaa: 'bbb'}).toBe false
expect(x.isElement root).toBe true

it 'should return list of all parents of an element', ->
elm = root.querySelector '#linkZero'
result = x.getParents elm
Expand All @@ -79,36 +79,41 @@ describe 'CSS Selector Generator', ->
expect(result[1].tagName).toBe 'LI'
expect(result[2].tagName).toBe 'UL'
expect(result[3].tagName).toBe 'DIV'

it 'should get tag selector for an element', ->
elm = root.querySelector '#linkZero'
expect(x.getTagSelector elm).toBe 'a'

it 'should get ID selector for an element', ->
elm = root.querySelector '#linkZero'
expect(x.getIdSelector elm).toBe '#linkZero'
expect(x.getIdSelector root).toBe null

it 'should ignore invalid ID attributes', ->
root.innerHTML = '<div id="111aaa"></div>'
elm = root.firstChild
expect(x.getIdSelector elm).toBe null

it 'should get class selectors for an element', ->
elm = root.querySelector '#linkZero'
result = x.getClassSelectors elm
expectation = ['.classOne', '.classTwo', '.classThree']
expect(result).toEqual expectation
expect(x.getClassSelectors root).toEqual []

it 'should remove unnecessary whitespace when getting class names', ->
elm = document.createElement 'div'
elm.setAttribute 'class', ' aaa bbb ccc '
expectation = ['.aaa', '.bbb', '.ccc']
result = x.getClassSelectors elm
expect(result).toEqual expectation

it 'should handle elements with empty class name', ->
elm = document.createElement 'div'
elm.setAttribute 'class', ''
result = x.getClassSelectors elm
expect(result).toEqual []

it 'should get attribute selectors for an element', ->
elm = root.querySelector '#linkZero'
result = x.getAttributeSelectors elm
Expand All @@ -126,11 +131,11 @@ describe 'CSS Selector Generator', ->

it 'should test, if selector returns only expected element', ->
elm = root.querySelector '#linkZero'

# ID selector
selector = '#linkZero'
expect(x.testSelector elm, selector, root).toBe true

# non-unique Class selector
selector = '.classOne'
expect(x.testSelector elm, selector, root).toBe false
Expand All @@ -146,13 +151,13 @@ describe 'CSS Selector Generator', ->
# multiple attribute selector
selector = 'a[target=someTarget][rel=someRel]'
expect(x.testSelector elm, selector, root).toBe true

it 'should construct unique selector for any given element', ->
all_elements = root.querySelectorAll '*'
for element in all_elements
selector = x.getSelector element
expect(x.testSelector element, selector).toBe true

it 'should pass the complex test', ->
root = document.createElement 'div'
root.innerHTML = complex_example
Expand Down

0 comments on commit 0726f6c

Please sign in to comment.