stew-select
Version:
CSS selectors that allow regular expressions. Stew is a meatier soup.
1,085 lines (1,020 loc) • 43.2 kB
text/coffeescript
should = require 'should'
fs = require 'fs'
path = require 'path'
HOMEDIR = path.join(__dirname,'..')
LIB_DIR = if fs.existsSync(path.join(HOMEDIR,'lib-cov')) then path.join(HOMEDIR,'lib-cov') else path.join(HOMEDIR,'lib')
#-------------------------------------------------------------------------------
htmlparser = require 'htmlparser'
Stew = require(path.join(LIB_DIR,'stew')).Stew
#-------------------------------------------------------------------------------
HTMLPARSER_OPTIONS =
ignoreWhitespace: false
caseSensitiveTags: false
caseSensitiveAttr: false
TEST_HTML = """
<html>
<body>
<div class="outer odd" id="outer-1">
<div class="inner odd" id="inner-1-1" lang="en-gb"><span width=17>A</span></div>
<div class="inner even" id="inner-1-2" lang="en"><b foo="bar">B</b></div>
</div>
<div class="outer even" id="outer-2">
<div class="inner odd" id="inner-2-1" lang="en-us"><b fact="white space is ok here"><i>C</i></b></div>
<div class="inner even" id="inner-2-2"><em extra="contains spaces, commas and symbols like + and /">D</em></div>
</div>
<section>
<span id="escaped-quote-test" label='this label includes "quote" marks'></span>
</section>
</body>
</html>
"""
describe "Stew",->
beforeEach (done)->
@stew = new Stew()
handler = new htmlparser.DefaultHandler (err, dom)=>
@DOM = dom
done()
parser = new htmlparser.Parser(handler,HTMLPARSER_OPTIONS)
parser.parseComplete(TEST_HTML)
afterEach (done)=>
@stew = null
@DOM = null
done()
describe "(fixed bugs)",->
it "handles colon (:) in unquoted attribute values",(done)->
html = '<html><head><meta property="og:title" content="Stew"/><meta property="og:type" content="githubog:gitrepository"/></head></html>'
@stew.select html, 'meta[property=og:type]', (err,nodeset)->
should.not.exist err
nodeset.length.should.equal 1
nodeset[0].attribs.content.should.equal "githubog:gitrepository"
done()
it 'can parse a selector string into a list of predictes ',(done)->
selector = @stew._parse_selectors('tag .foo #bar [/x/i] [y=/z/]')
# _parse_selectors now returns a single function rather than array of them
(typeof selector).should.equal 'function'
done()
describe "select_first()",->
it 'supports the any-tag selector (`*`)',(done)->
node = @stew.select_first(@DOM,'div *')
node.type.should.equal 'tag'
node.name.should.equal 'div'
node.attribs.id.should.equal 'inner-1-1'
done()
it 'supports the type selector (`E`)',(done)->
node = @stew.select_first(@DOM,'em')
node.type.should.equal 'tag'
node.name.should.equal 'em'
#
node = @stew.select_first(@DOM,'div')
node.type.should.equal 'tag'
node.name.should.equal 'div'
node.attribs.id.should.equal 'outer-1'
# done
done()
it 'invokes a callback, if provided',(done)->
@stew.select_first @DOM, 'em', (err,node)->
node.type.should.equal 'tag'
node.name.should.equal 'em'
done()
it 'can also parse a string into a DOM automatically, if given a callback',(done)->
@stew.select_first TEST_HTML,'em',(err,node)->
node.type.should.equal 'tag'
node.name.should.equal 'em'
done()
it 'throws an exception when given an HTML string but no callback',(done)->
(()=>@stew.select_first(TEST_HTML, 'em')).should.throw(/callback/)
done()
describe "select()",->
it 'can also parse a string into a DOM automatically, if given a callback',(done)->
@stew.select TEST_HTML, 'em', (err,nodeset)->
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'em'
done()
it 'invokes a callback, when provided',(done)->
@stew.select @DOM,'em',(err,nodeset)->
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'em'
done()
it 'throws an exception when given an HTML string but no callback',(done)->
(()=>@stew.select(TEST_HTML, 'em')).should.throw(/callback/)
done()
# * - Matches any tag
it 'supports the any-tag selector (`*`)',(done)->
# `div *`
nodeset = @stew.select(@DOM,'div *')
nodeset.length.should.equal 9
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'inner-1-1'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'span'
nodeset[2].type.should.equal 'tag'
nodeset[2].name.should.equal 'div'
nodeset[2].attribs.id.should.equal 'inner-1-2'
nodeset[3].type.should.equal 'tag'
nodeset[3].name.should.equal 'b'
nodeset[4].type.should.equal 'tag'
nodeset[4].name.should.equal 'div'
nodeset[4].attribs.id.should.equal 'inner-2-1'
nodeset[5].type.should.equal 'tag'
nodeset[5].name.should.equal 'b'
nodeset[6].type.should.equal 'tag'
nodeset[6].name.should.equal 'i'
nodeset[7].type.should.equal 'tag'
nodeset[7].name.should.equal 'div'
nodeset[7].attribs.id.should.equal 'inner-2-2'
nodeset[8].type.should.equal 'tag'
nodeset[8].name.should.equal 'em'
done()
# E - Matches any E element (i.e., an element of type E). - Type selectors
it 'supports the type selector (`E`) (string case)',(done)->
# `em`
nodeset = @stew.select(@DOM,'em')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'em'
# `div`
nodeset = @stew.select(@DOM,'div')
nodeset.length.should.equal 6
for node in nodeset
node.type.should.equal 'tag'
node.name.should.equal 'div'
# done
done()
it 'supports the type selector (`E`) (regexp case)',(done)->
nodeset = @stew.select(@DOM,'/E*x?M/i')
nodeset.length.should.equal 2
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'html'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'em'
done()
# DIV.warning - Language specific. (In HTML, the same as DIV[class~="warning"].) - Class Selector
it 'supports the class selector (`.warning`) (string case)',(done)->
# `div.outer`
nodeset = @stew.select(@DOM,'div.outer')
nodeset.length.should.equal 2
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'outer-1'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'outer-2'
# `.outer`
nodeset = @stew.select(@DOM,'.outer')
nodeset.length.should.equal 2
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'outer-1'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'outer-2'
# `div.outer.odd`
nodeset = @stew.select(@DOM,'div.outer.odd')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'outer-1'
# `.outer.odd`
nodeset = @stew.select(@DOM,'.outer.odd')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'outer-1'
# `.outer.odd`
nodeset = @stew.select(@DOM,'.odd.outer')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'outer-1'
# `.inner.even`
nodeset = @stew.select(@DOM,'.inner.even')
nodeset.length.should.equal 2
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'inner-1-2'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'inner-2-2'
# `*.odd *.even`
nodeset = @stew.select(@DOM,'*.odd *.even')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'inner-1-2'
done()
it 'supports the class selector (`.warning`) (regex case)',(done)->
# `div.outer`
nodeset = @stew.select(@DOM,'div./[ou]+ter/')
nodeset.length.should.equal 2
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'outer-1'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'outer-2'
# `.outer`
nodeset = @stew.select(@DOM,'./[ou]+ter/')
nodeset.length.should.equal 2
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'outer-1'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'outer-2'
# `div./er$/`
nodeset = @stew.select(@DOM,'div./er$/')
nodeset.length.should.equal 6
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'outer-1'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'inner-1-1'
nodeset[2].type.should.equal 'tag'
nodeset[2].name.should.equal 'div'
nodeset[2].attribs.id.should.equal 'inner-1-2'
nodeset[3].type.should.equal 'tag'
nodeset[3].name.should.equal 'div'
nodeset[3].attribs.id.should.equal 'outer-2'
nodeset[4].type.should.equal 'tag'
nodeset[4].name.should.equal 'div'
nodeset[4].attribs.id.should.equal 'inner-2-1'
nodeset[5].type.should.equal 'tag'
nodeset[5].name.should.equal 'div'
nodeset[5].attribs.id.should.equal 'inner-2-2'
# `div./er$/.odd`
nodeset = @stew.select(@DOM,'div./er$/.odd')
nodeset.length.should.equal 3
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'outer-1'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'inner-1-1'
nodeset[2].type.should.equal 'tag'
nodeset[2].name.should.equal 'div'
nodeset[2].attribs.id.should.equal 'inner-2-1'
nodeset[2].type.should.equal 'tag'
done()
# E#myid - Matches any E element with ID equal to "myid" - ID selector
it 'supports the id selector (`E#myid`) (string case)',(done)->
# `div#outer-2`
nodeset = @stew.select(@DOM,'div#outer-2')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'outer-2'
# `#outer-2`
nodeset = @stew.select(@DOM,'#outer-2')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'outer-2'
# `div#outer-2.even`
nodeset = @stew.select(@DOM,'div#outer-2.even')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'outer-2'
# `div#outer-2.odd`
nodeset = @stew.select(@DOM,'div#outer-2.odd')
nodeset.length.should.equal 0
done()
it 'supports the id selector (`E#myid`) (regex case)',(done)->
# `div#/outer-[0-9]/`
nodeset = @stew.select(@DOM,'div#/outer-[0-9]/')
nodeset.length.should.equal 2
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'outer-1'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'outer-2'
# `#/outer-[0-9]/`
nodeset = @stew.select(@DOM,'#/outer-[0-9]/')
nodeset.length.should.equal 2
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'outer-1'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'outer-2'
# `#/outer-[0-9]/ b`
nodeset = @stew.select(@DOM,'#/outer-[0-9]/ b')
nodeset.length.should.equal 2
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'b'
nodeset[0].attribs.foo.should.equal 'bar'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'b'
nodeset[1].attribs.fact.should.equal 'white space is ok here'
done()
# E F - any F element that is a descendant of an E element. - Descendant selectors
it 'supports the descendant selector (`E F`) (string case)',(done)->
# `div span`
nodeset = @stew.select(@DOM,'div span')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'span'
# `html span`
nodeset = @stew.select(@DOM,'html span')
nodeset.length.should.equal 2
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'span'
# `html body div span`
nodeset = @stew.select(@DOM,'html body div span')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'span'
# `div div`
nodeset = @stew.select(@DOM,'div div')
nodeset.length.should.equal 4
for node in nodeset
node.type.should.equal 'tag'
node.name.should.equal 'div'
# `body div`
nodeset = @stew.select(@DOM,'body div')
nodeset.length.should.equal 6
for node in nodeset
node.type.should.equal 'tag'
node.name.should.equal 'div'
# done
done()
it 'supports the descendant selector (`E F`) (regexp case)',(done)->
nodeset = @stew.select(@DOM,'div /s[tp][aeiou]+n/') # select `div span` or `div stan` or `div spin`, etc,
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'span'
done()
# E > F - any F element that is a child of an E element. - Child selectors
it 'supports the child selector (`E > F`) (string case)',(done)->
# `body > div`
nodeset = @stew.select(@DOM,'body div')
nodeset.length.should.equal 6
nodeset = @stew.select(@DOM,'body > div')
nodeset.length.should.equal 2
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'outer-1'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'outer-2'
#
done()
# E > F - any F element that is a child of an E element. - Child selectors
it 'supports the child selector without whitespace (`E>F`) (string case)',(done)->
nodeset = @stew.select(@DOM,'body>div')
nodeset.length.should.equal 2
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'outer-1'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'outer-2'
#
done()
it 'supports the child selector (`E > F`) (regexp case)',(done)->
# `body > /d[aeiou]ve?/`
nodeset = @stew.select(@DOM,'body /d[aeiou]ve?/')
nodeset.length.should.equal 6
nodeset = @stew.select(@DOM,'body > /d[aeiou]ve?/')
nodeset.length.should.equal 2
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'outer-1'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'outer-2'
#
done()
# E[foo] - Matches any E element with the "foo" attribute set (whatever the value). - Attribute selectors
it 'supports the attribute selector (`E[foo]`) (string case)',(done)->
#
nodeset = @stew.select(@DOM,'b[foo]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'b'
#
nodeset = @stew.select(@DOM,'div b[foo]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'b'
done()
it 'supports the attribute selector (`E[foo]`) (regexp case)',(done)->
nodeset = @stew.select(@DOM,'b[/fo+/]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'b'
#
done()
# E[foo="warning"] - Matches any E element whose "foo" attribute value is exactly equal to "warning". - Attribute selectors
it 'supports the attribute-value selector (`E[foo="bar"]`) (unquoted string case)',(done)->
#
nodeset = @stew.select(@DOM,'[width=17]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'span'
#
nodeset = @stew.select(@DOM,'b[foo=bar]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'b'
#
nodeset = @stew.select(@DOM,'div [foo=bar]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'b'
#
nodeset = @stew.select(@DOM,'div b[foo=foo]')
nodeset.length.should.equal 0
#
nodeset = @stew.select(@DOM,'div[id=inner-1-1]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
#
done()
it 'supports the attribute-value selector (`E[foo="bar"]`) (quoted string case)',(done)->
#
nodeset = @stew.select(@DOM,'b[foo="bar"]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'b'
#
nodeset = @stew.select(@DOM,'div[id="inner-1-1"]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
#
done()
it 'supports the attribute-value selector (`E[foo="bar"]`) (quoted string containing whitespace case)',(done)->
nodeset = @stew.select(@DOM,'b[fact="white space is ok here"]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'b'
nodeset[0].attribs.fact.should.equal 'white space is ok here'
done()
it 'supports the attribute-value selector (`E[foo="bar"]`) (regexp containing whitespace case)',(done)->
nodeset = @stew.select(@DOM,'b[fact=/white space is ok here/]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'b'
nodeset[0].attribs.fact.should.equal 'white space is ok here'
nodeset = @stew.select(@DOM,'b[fact=/white space/]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'b'
nodeset[0].attribs.fact.should.equal 'white space is ok here'
nodeset = @stew.select(@DOM,'b[fact=/^white space.*ok here$/]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'b'
nodeset[0].attribs.fact.should.equal 'white space is ok here'
nodeset = @stew.select(@DOM,'b[fact=/"?white\\s*space"? is ok\\s+here/]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'b'
nodeset[0].attribs.fact.should.equal 'white space is ok here'
done()
it 'supports the attribute-value selector (`E[foo="bar"]`) (regexp case)',(done)->
#
nodeset = @stew.select(@DOM,'b[/fo+/=/ba?r/]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'b'
#
nodeset = @stew.select(@DOM,'div[id=/inner-1/]')
nodeset.length.should.equal 2
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
#
nodeset = @stew.select(@DOM,'div[class=inner]')
nodeset.length.should.equal 0
nodeset = @stew.select(@DOM,'div[class=/inner/]')
nodeset.length.should.equal 4
for node in nodeset
node.type.should.equal 'tag'
node.name.should.equal 'div'
#
done()
it 'supports the attribute-value selector (`E[foo="bar"]`) (extra symbols case)',(done)->
nodeset = @stew.select(@DOM,'[extra="contains spaces, commas and symbols like + and /"]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'em'
nodeset[0].attribs.extra.should.equal 'contains spaces, commas and symbols like + and /'
nodeset = @stew.select(@DOM,'[extra=/contains spaces, commas and symbols like \\+/]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'em'
nodeset[0].attribs.extra.should.equal 'contains spaces, commas and symbols like + and /'
nodeset = @stew.select(@DOM,'[extra=/^contains spaces, commas and symbols like \\+ and .$/]') # TODO support `\/` as a way to escape `/` in regexp patterns (replacing `.` here)
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'em'
nodeset[0].attribs.extra.should.equal 'contains spaces, commas and symbols like + and /'
done()
# E[foo~="warning"] - Matches any E element whose "foo" attribute value is a list of space-separated values, one of which is exactly equal to "warning".
it 'supports the ~= operator in the attribute-value selector (`E[foo~="bar"]`) (string case)',(done)->
#
nodeset = @stew.select(@DOM,'div[class~=inner]')
nodeset.length.should.equal 4
for node in nodeset
node.type.should.equal 'tag'
node.name.should.equal 'div'
#
nodeset = @stew.select(@DOM,'div[class~=odd]')
nodeset.length.should.equal 3
for node in nodeset
node.type.should.equal 'tag'
node.name.should.equal 'div'
#
nodeset = @stew.select(@DOM,'[fact~=space]')
nodeset.length.should.equal 1
for node in nodeset
node.type.should.equal 'tag'
node.name.should.equal 'b'
#
nodeset = @stew.select(@DOM,'[foo~=bar]')
nodeset.length.should.equal 1
for node in nodeset
node.type.should.equal 'tag'
node.name.should.equal 'b'
#
nodeset = @stew.select(@DOM,'[zzz~=in]')
nodeset.length.should.equal 0
#
done()
it 'supports the |= operator in the attribute-value selector (`E[lang|="en"]`) (string case)',(done)->
#
nodeset = @stew.select(@DOM,'div[lang|="en"]')
nodeset.length.should.equal 3
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'inner-1-1'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'inner-1-2'
nodeset[2].type.should.equal 'tag'
nodeset[2].name.should.equal 'div'
nodeset[2].attribs.id.should.equal 'inner-2-1'
#
nodeset = @stew.select(@DOM,'div[lang|="en-us"]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'inner-2-1'
#
done()
it 'supports the |= operator in the attribute-value selector (`E[lang|="en"]`) (regexp case)',(done)->
#
nodeset = @stew.select(@DOM,'div[lang|=/EN/i]')
nodeset.length.should.equal 3
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'inner-1-1'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'inner-1-2'
nodeset[2].type.should.equal 'tag'
nodeset[2].name.should.equal 'div'
nodeset[2].attribs.id.should.equal 'inner-2-1'
#
nodeset = @stew.select(@DOM,'div[lang|=/en?/]')
nodeset.length.should.equal 3
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'inner-1-1'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'inner-1-2'
nodeset[2].type.should.equal 'tag'
nodeset[2].name.should.equal 'div'
nodeset[2].attribs.id.should.equal 'inner-2-1'
#
nodeset = @stew.select(@DOM,'div[lang|=/^en/]')
nodeset.length.should.equal 3
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'inner-1-1'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'inner-1-2'
nodeset[2].type.should.equal 'tag'
nodeset[2].name.should.equal 'div'
nodeset[2].attribs.id.should.equal 'inner-2-1'
#
nodeset = @stew.select(@DOM,'div[lang|=/[aeiou]n-[aeioug][sb]/]')
nodeset.length.should.equal 2
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'inner-1-1'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'inner-2-1'
#
done()
# E:first-child - Matches element E when E is the first child of its parent. - The :first-child pseudo-class
it 'supports the :first-child pseudo class (`E:first-child`) (string case)',(done)->
nodeset = @stew.select(@DOM,'div:first-child')
nodeset.length.should.equal 3
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'outer-1'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'inner-1-1'
nodeset[2].type.should.equal 'tag'
nodeset[2].name.should.equal 'div'
nodeset[2].attribs.id.should.equal 'inner-2-1'
#
nodeset = @stew.select(@DOM,'section:first-child')
nodeset.length.should.equal 0
#
nodeset = @stew.select(@DOM,'body section span:first-child')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'span'
nodeset[0].attribs.id.should.equal 'escaped-quote-test'
#
done()
# E + F - any F element immediately preceded by a sibling element E - Adjacent selectors
it 'supports the adjacent selector (`E + F`) (string case)',(done)->
nodeset = @stew.select(@DOM,'div div')
nodeset.length.should.equal 4
nodeset = @stew.select(@DOM,'div + div')
nodeset.length.should.equal 3
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'inner-1-2'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'outer-2'
nodeset[2].type.should.equal 'tag'
nodeset[2].name.should.equal 'div'
nodeset[2].attribs.id.should.equal 'inner-2-2'
#
done()
it 'supports the adjacent selector without whitespace (`E+F`) (string case)',(done)->
nodeset = @stew.select(@DOM,'div+div')
nodeset.length.should.equal 3
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'inner-1-2'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'outer-2'
nodeset[2].type.should.equal 'tag'
nodeset[2].name.should.equal 'div'
nodeset[2].attribs.id.should.equal 'inner-2-2'
#
done()
# E + F - any F element immediately preceded by a sibling element E - Adjacent selectors
it 'supports the adjacent selector (`E + F`) (regexp case)',(done)->
nodeset = @stew.select(@DOM,'/(div)|(dove)/ /(div)|(dave)/')
nodeset.length.should.equal 4
nodeset = @stew.select(@DOM,'/(div)|(dove)/ + /(div)|(dave)/')
nodeset.length.should.equal 3
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'inner-1-2'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'outer-2'
nodeset[2].type.should.equal 'tag'
nodeset[2].name.should.equal 'div'
nodeset[2].attribs.id.should.equal 'inner-2-2'
#
done()
# E , F - all nodes matching E or F
it 'supports the comma (or) operator (`E , F`) (string case)',(done)->
nodeset = @stew.select(@DOM,'b , span')
nodeset.length.should.equal 4
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'span'
nodeset[0].attribs.width.should.equal '17'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'b'
nodeset[1].attribs.foo.should.equal 'bar'
nodeset[2].type.should.equal 'tag'
nodeset[2].name.should.equal 'b'
nodeset[2].attribs.fact.should.equal 'white space is ok here'
nodeset[3].type.should.equal 'tag'
nodeset[3].name.should.equal 'span'
nodeset[3].attribs.id.should.equal 'escaped-quote-test'
#
done()
it 'supports the comma (or) operator without spaces (`E,F`) (string case)',(done)->
nodeset = @stew.select(@DOM,'b,span')
nodeset.length.should.equal 4
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'span'
nodeset[0].attribs.width.should.equal '17'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'b'
nodeset[1].attribs.foo.should.equal 'bar'
nodeset[2].type.should.equal 'tag'
nodeset[2].name.should.equal 'b'
nodeset[2].attribs.fact.should.equal 'white space is ok here'
nodeset[3].type.should.equal 'tag'
nodeset[3].name.should.equal 'span'
nodeset[3].attribs.id.should.equal 'escaped-quote-test'
#
done()
it 'supports escaped quotation marks within quoted strings',(done)->
nodeset = @stew.select(@DOM,'[label="this label includes \\"quote\\" marks"]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'span'
nodeset[0].attribs.id.should.equal 'escaped-quote-test'
#
nodeset = @stew.select(@DOM,'[label=/^this label includes "quote" marks$/]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'span'
nodeset[0].attribs.id.should.equal 'escaped-quote-test'
#
done()
it 'supports multiple attribute-based selectors in series (`E[a=b][c=d]`)',(done)->
#
nodeset = @stew.select(@DOM,'[class~="outer"][class~="odd"]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'outer-1'
#
nodeset = @stew.select(@DOM,'[class=/outer/][class=/odd/]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'outer-1'
done()
# E ~ F - any F element preceded by a sibling element E - Adjacent selectors
it 'supports the preceding sibling selector (`E ~ F`) (string case)',(done)->
nodeset = @stew.select(@DOM,'.odd section')
nodeset.length.should.equal 0
nodeset = @stew.select(@DOM,'.odd + section')
nodeset.length.should.equal 0
nodeset = @stew.select(@DOM,'.odd ~ section')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'section'
nodeset = @stew.select(@DOM,'.odd ~ section span')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'span'
nodeset[0].attribs.id.should.equal 'escaped-quote-test'
done()
## TODO FIXME make `E~F` work just like `E ~ F`
# it 'supports the preceding sibling selector without whitesapce (`E~F`) (string case)',(done)->
# nodeset = @stew.select(@DOM,'.odd section')
# nodeset.length.should.equal 0
# nodeset = @stew.select(@DOM,'.odd+section')
# nodeset.length.should.equal 0
# nodeset = @stew.select(@DOM,'.odd~section')
# nodeset.length.should.equal 1
# nodeset[0].type.should.equal 'tag'
# nodeset[0].name.should.equal 'section'
# nodeset = @stew.select(@DOM,'.odd~section span')
# nodeset.length.should.equal 1
# nodeset[0].type.should.equal 'tag'
# nodeset[0].name.should.equal 'span'
# nodeset[0].attribs.id.should.equal 'escaped-quote-test'
# done()
it 'supports the preceding adjacent selector (`E ~ F`) (regexp case)',(done)->
nodeset = @stew.select(@DOM,'.odd /section/')
nodeset.length.should.equal 0
nodeset = @stew.select(@DOM,'.odd + /s[aeiou]ction/')
nodeset.length.should.equal 0
nodeset = @stew.select(@DOM,'.odd ~ /s[aeiou]ction/')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'section'
nodeset = @stew.select(@DOM,'.odd ~ /s[aeiou]ction/ span')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'span'
nodeset[0].attribs.id.should.equal 'escaped-quote-test'
done()
it 'supports the ^= (starts-with) operator in the attribute-value selector (`E[foo^="bar"]`) (string case)',(done)->
#
nodeset = @stew.select(@DOM,'div[lang^="en-"]')
nodeset.length.should.equal 2
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'inner-1-1'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'inner-2-1'
#
nodeset = @stew.select(@DOM,'span[width^=1]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'span'
nodeset[0].attribs.width.should.equal '17'
#
done()
it 'supports the ^= (starts-with) operator in the attribute-value selector (`E[foo^="bar"]`) (regexp case)',(done)->
nodeset = @stew.select(@DOM,'div[lang=/n/]')
nodeset.length.should.equal 3
nodeset = @stew.select(@DOM,'div[lang^=/n/]')
nodeset.length.should.equal 0
#
nodeset = @stew.select(@DOM,'div[lang^=/en-/]')
nodeset.length.should.equal 2
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'inner-1-1'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'inner-2-1'
#
nodeset = @stew.select(@DOM,'div[lang^=/^en-/]')
nodeset.length.should.equal 2
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'inner-1-1'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'inner-2-1'
#
nodeset = @stew.select(@DOM,'span[width^=/1/]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'span'
nodeset[0].attribs.width.should.equal '17'
#
nodeset = @stew.select(@DOM,'span[width^=/17$/]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'span'
nodeset[0].attribs.width.should.equal '17'
#
done()
it 'supports the $= (ends-with) operator in the attribute-value selector (`E[foo$="bar"]`) (string case)',(done)->
#
nodeset = @stew.select(@DOM,'div[id$="-2"]')
nodeset.length.should.equal 3
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'inner-1-2'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'outer-2'
nodeset[2].type.should.equal 'tag'
nodeset[2].name.should.equal 'div'
nodeset[2].attribs.id.should.equal 'inner-2-2'
#
nodeset = @stew.select(@DOM,'span[width$=7]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'span'
nodeset[0].attribs.width.should.equal '17'
#
nodeset = @stew.select(@DOM,'span[width$=17]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'span'
nodeset[0].attribs.width.should.equal '17'
#
nodeset = @stew.select(@DOM,'span[width$=1]')
nodeset.length.should.equal 0
#
done()
it 'supports the $= (ends-with) operator in the attribute-value selector (`E[foo$="bar"]`) (regexp case)',(done)->
nodeset = @stew.select(@DOM,'div[lang=/n/]')
nodeset.length.should.equal 3
#
nodeset = @stew.select(@DOM,'div[lang$=/n/]')
nodeset.length.should.equal 1
#
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'inner-1-2'
#
nodeset = @stew.select(@DOM,'div[lang$=/-((gb)|(us))/]')
nodeset.length.should.equal 2
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'inner-1-1'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'inner-2-1'
#
nodeset = @stew.select(@DOM,'div[lang$=/-(g|u)./]')
nodeset.length.should.equal 2
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'inner-1-1'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'inner-2-1'
#
nodeset = @stew.select(@DOM,'span[width$=/[0-9]7/]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'span'
nodeset[0].attribs.width.should.equal '17'
#
nodeset = @stew.select(@DOM,'span[width$=/7/]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'span'
nodeset[0].attribs.width.should.equal '17'
#
nodeset = @stew.select(@DOM,'span[width$=/^17/]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'span'
nodeset[0].attribs.width.should.equal '17'
#
nodeset = @stew.select(@DOM,'span[width$=/17$/]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'span'
nodeset[0].attribs.width.should.equal '17'
#
nodeset = @stew.select(@DOM,'span[width$=/^17$/]')
nodeset.length.should.equal 1
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'span'
nodeset[0].attribs.width.should.equal '17'
#
done()
it 'supports the *= (contains) operator in the attribute-value selector (`E[foo*="bar"]`) (string case)',(done)->
#
nodeset = @stew.select(@DOM,'div[class*="er odd"]')
nodeset.length.should.equal 3
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'outer-1'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'inner-1-1'
nodeset[2].type.should.equal 'tag'
nodeset[2].name.should.equal 'div'
nodeset[2].attribs.id.should.equal 'inner-2-1'
#
nodeset = @stew.select(@DOM,'div[class*="er"]')
nodeset.length.should.equal 6
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'outer-1'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'inner-1-1'
nodeset[2].type.should.equal 'tag'
nodeset[2].name.should.equal 'div'
nodeset[2].attribs.id.should.equal 'inner-1-2'
nodeset[3].type.should.equal 'tag'
nodeset[3].name.should.equal 'div'
nodeset[3].attribs.id.should.equal 'outer-2'
nodeset[4].type.should.equal 'tag'
nodeset[4].name.should.equal 'div'
nodeset[4].attribs.id.should.equal 'inner-2-1'
nodeset[5].type.should.equal 'tag'
nodeset[5].name.should.equal 'div'
nodeset[5].attribs.id.should.equal 'inner-2-2'
#
done()
it 'supports the *= (contains) operator in the attribute-value selector (`E[foo*="bar"]`) (regexp case)',(done)->
#
nodeset = @stew.select(@DOM,'div[class*=/er odd/]')
nodeset.length.should.equal 3
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'outer-1'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'inner-1-1'
nodeset[2].type.should.equal 'tag'
nodeset[2].name.should.equal 'div'
nodeset[2].attribs.id.should.equal 'inner-2-1'
#
nodeset = @stew.select(@DOM,'div[class*=/er/]')
nodeset.length.should.equal 6
nodeset[0].type.should.equal 'tag'
nodeset[0].name.should.equal 'div'
nodeset[0].attribs.id.should.equal 'outer-1'
nodeset[1].type.should.equal 'tag'
nodeset[1].name.should.equal 'div'
nodeset[1].attribs.id.should.equal 'inner-1-1'
nodeset[2].type.should.equal 'tag'
nodeset[2].name.should.equal 'div'
nodeset[2].attribs.id.should.equal 'inner-1-2'
nodeset[3].type.should.equal 'tag'
nodeset[3].name.should.equal 'div'
nodeset[3].attribs.id.should.equal 'outer-2'
nodeset[4].type.should.equal 'tag'
nodeset[4].name.should.equal 'div'
nodeset[4].attribs.id.should.equal 'inner-2-1'
nodeset[5].type.should.equal 'tag'
nodeset[5].name.should.equal 'div'
nodeset[5].attribs.id.should.equal 'inner-2-2'
#
done()