//
//  SelectorTest.swift
//  SwiftSoup
//
//  Created by Nabil Chatbi on 12/11/16.
//

import SwiftSoup
import XCTest

class SelectorTest: XCTestCase {

  func testByTag() throws {
    // should be case insensitive
    let els: Elements = try SwiftSoup.parse(
      "<div id=1><div id=2><p>Hello</p></div></div><DIV id=3>"
    ).select("DIV")
    XCTAssertEqual(3, els.size())
    XCTAssertEqual("1", els.get(0).id())
    XCTAssertEqual("2", els.get(1).id())
    XCTAssertEqual("3", els.get(2).id())

    let none: Elements = try SwiftSoup.parse(
      "<div id=1><div id=2><p>Hello</p></div></div><div id=3>"
    ).select("span")
    XCTAssertEqual(0, none.size())
  }

  func testById() throws {
    let els: Elements = try SwiftSoup.parse("<div><p id=foo>Hello</p><p id=foo>Foo two!</p></div>")
      .select("#foo")
    XCTAssertEqual(2, els.size())
    XCTAssertEqual("Hello", try els.get(0).text())
    XCTAssertEqual("Foo two!", try els.get(1).text())

    let none: Elements = try SwiftSoup.parse("<div id=1></div>").select("#foo")
    XCTAssertEqual(0, none.size())
  }

  func testByClass() throws {
    let els: Elements = try SwiftSoup.parse(
      "<p id=0 class='ONE two'><p id=1 class='one'><p id=2 class='two'>"
    ).select("P.One")
    XCTAssertEqual(2, els.size())
    XCTAssertEqual("0", els.get(0).id())
    XCTAssertEqual("1", els.get(1).id())

    let none: Elements = try SwiftSoup.parse("<div class='one'></div>").select(".foo")
    XCTAssertEqual(0, none.size())

    let els2: Elements = try SwiftSoup.parse("<div class='One-Two'></div>").select(".one-two")
    XCTAssertEqual(1, els2.size())
  }

  func testByAttribute() throws {
    let h: String =
      "<div Title=Foo /><div Title=Bar /><div Style=Qux /><div title=Bam /><div title=SLAM />"
      + "<div data-name='with spaces'/>"
    let doc: Document = try SwiftSoup.parse(h)

    let withTitle: Elements = try doc.select("[title]")
    XCTAssertEqual(4, withTitle.size())

    let foo: Elements = try doc.select("[TITLE=foo]")
    XCTAssertEqual(1, foo.size())

    let foo2: Elements = try doc.select("[title=\"foo\"]")
    XCTAssertEqual(1, foo2.size())

    let foo3: Elements = try doc.select("[title=\"Foo\"]")
    XCTAssertEqual(1, foo3.size())

    let dataName: Elements = try doc.select("[data-name=\"with spaces\"]")
    XCTAssertEqual(1, dataName.size())
    XCTAssertEqual("with spaces", try dataName.first()?.attr("data-name"))

    let not: Elements = try doc.select("div[title!=bar]")
    XCTAssertEqual(5, not.size())
    XCTAssertEqual("Foo", try not.first()?.attr("title"))

    let starts: Elements = try doc.select("[title^=ba]")
    XCTAssertEqual(2, starts.size())
    XCTAssertEqual("Bar", try starts.first()?.attr("title"))
    XCTAssertEqual("Bam", try starts.last()?.attr("title"))

    let ends: Elements = try doc.select("[title$=am]")
    XCTAssertEqual(2, ends.size())
    XCTAssertEqual("Bam", try ends.first()?.attr("title"))
    XCTAssertEqual("SLAM", try ends.last()?.attr("title"))

    let contains: Elements = try doc.select("[title*=a]")
    XCTAssertEqual(3, contains.size())
    XCTAssertEqual("Bar", try contains.first()?.attr("title"))
    XCTAssertEqual("SLAM", try contains.last()?.attr("title"))
  }

  func testDescendantSelectorMatches() throws {
    let html =
      "<div id=one><p class=alpha><span class=item>One</span></p></div>"
      + "<p class=alpha><span class=item>Two</span></p>"
    let doc = try SwiftSoup.parse(html)

    let divSpan = try doc.select("div span")
    XCTAssertEqual(1, divSpan.size())
    XCTAssertEqual("One", try divSpan.get(0).text())

    let divClass = try doc.select("div .item")
    XCTAssertEqual(1, divClass.size())
    XCTAssertEqual("One", try divClass.get(0).text())

    let divIdClass = try doc.select("div#one span.item")
    XCTAssertEqual(1, divIdClass.size())
    XCTAssertEqual("One", try divIdClass.get(0).text())
  }

  func testSelectTagAndClass() throws {
    let doc = try SwiftSoup.parse(
      "<div><p class=lead id=1></p><p class=lead id=2></p><span class=lead id=3></span></div>")
    let els = try doc.select("p.lead")
    XCTAssertEqual(2, els.size())
    XCTAssertEqual("1", els.get(0).id())
    XCTAssertEqual("2", els.get(1).id())
  }

  func testSelectTagAndAttributeValueHot() throws {
    let doc = try SwiftSoup.parse("<a href=one id=1></a><a href=two id=2></a><a href=one id=3></a>")
    let els = try doc.select("a[href=two]")
    XCTAssertEqual(1, els.size())
    XCTAssertEqual("2", els.get(0).id())
  }

  func testSelectTagAndAttributeName() throws {
    let doc = try SwiftSoup.parse("<a href=one id=1></a><div href=two id=2></div><a id=3></a>")
    let els = try doc.select("a[href]")
    XCTAssertEqual(1, els.size())
    XCTAssertEqual("1", els.get(0).id())
  }

  func testFastSelectAndMatchesCollector() throws {
    let html =
      "<div id=wrap>" + "<p id=p1 class=lead href=one>One</p>"
      + "<p id=p2 class=lead href=two>Two</p>" + "<p id=p3 class=body>Three</p>"
      + "<a id=a1 class=lead href=one>Link</a>" + "<span id=s1 class=lead href=one>Span</span>"
      + "<div id=d1 class=card data-x=1></div>" + "<div id=d2 class=card></div>" + "</div>"

    let doc = try SwiftSoup.parse(html)

    func ids(_ elements: Elements) -> [String] {
      return elements.array().map { $0.id() }
    }

    let selectors = [
      "p.lead[href=one]",
      "a.lead[href]",
      "div.card[data-x]",
    ]

    for selector in selectors {
      let eval = try QueryParser.parse(selector)
      let slow = try Collector.collect(eval, doc)
      let fast = try doc.select(selector)
      XCTAssertEqual(ids(slow), ids(fast))
    }
  }

  func testSelectAbsAttributeMatchesCollector() throws {
    let doc = try SwiftSoup.parse("<a href=/one id=1></a><a href=/two id=2></a>")
    let selector = "[abs:href]"
    let eval = try QueryParser.parse(selector)
    let slow = try Collector.collect(eval, doc)
    let fast = try doc.select(selector)
    XCTAssertEqual(slow.size(), fast.size())
  }

  func testSelectNotAndHasMatchesCollector() throws {
    let html =
      "<div id=wrap>" + "<article id=a1><p class=lead>One</p></article>"
      + "<article id=a2><p class=body>Two</p></article>"
      + "<section id=s1><p class=lead>Three</p></section>" + "</div>"
    let doc = try SwiftSoup.parse(html)
    let selector = "article:has(p.lead):not(.missing)"
    let eval = try QueryParser.parse(selector)
    let slow = try Collector.collect(eval, doc)
    let fast = try doc.select(selector)
    XCTAssertEqual(slow.size(), fast.size())
    XCTAssertEqual(slow.first()?.id(), fast.first()?.id())
  }

  func testSelectAbsAttributeValueMatchesCollector() throws {
    let doc = try SwiftSoup.parse("<a href=/one id=1></a><a href=/two id=2></a>")
    let selector = "[abs:href=/one]"
    let eval = try QueryParser.parse(selector)
    let slow = try Collector.collect(eval, doc)
    let fast = try doc.select(selector)
    XCTAssertEqual(slow.size(), fast.size())
  }

  func testSelectAttributeStartingMatchesCollector() throws {
    let doc = try SwiftSoup.parse(
      "<div data-x=1 id=1></div><div data-y=2 id=2></div><div id=3></div>")
    let selector = "[^data-]"
    let eval = try QueryParser.parse(selector)
    let slow = try Collector.collect(eval, doc)
    let fast = try doc.select(selector)
    XCTAssertEqual(slow.size(), fast.size())
  }

  func testSelectNotAndHasDeepTreeMatchesCollector() throws {
    let html =
      "<div id=wrap>"
      + "<article id=a1><header><h1>Title</h1></header><p class=lead>One</p></article>"
      + "<article id=a2><header><h1>Title</h1></header><p class=body>Two</p></article>"
      + "<article id=a3 class=skip><p class=lead>Three</p></article>"
      + "<section id=s1><p class=lead>Four</p></section>" + "</div>"
    let doc = try SwiftSoup.parse(html)
    let selector = "article:has(header h1):has(p.lead):not(.skip)"
    let eval = try QueryParser.parse(selector)
    let slow = try Collector.collect(eval, doc)
    let fast = try doc.select(selector)
    XCTAssertEqual(slow.size(), fast.size())
    XCTAssertEqual(slow.first()?.id(), fast.first()?.id())
  }

  func testSelectGroupOrMatchesCollector() throws {
    let html =
      "<div id=wrap>" + "<p id=p1 class=lead>One</p>" + "<p id=p2 class=body>Two</p>"
      + "<a id=a1 class=lead href=one>Link</a>" + "<span id=s1 class=lead>Span</span>" + "</div>"
    let doc = try SwiftSoup.parse(html)
    let selector = "p.lead, a[href], span.lead"
    let eval = try QueryParser.parse(selector)
    let slow = try Collector.collect(eval, doc)
    let fast = try doc.select(selector)
    XCTAssertEqual(slow.size(), fast.size())
  }

  func testSelectNotHasWithSiblingCombinatorsMatchesCollector() throws {
    let html =
      "<div id=wrap>" + "<article id=a1><h2>One</h2><p class=lead>Lead</p></article>"
      + "<article id=a2><h2>Two</h2><p class=body>Body</p></article>"
      + "<article id=a3><h2>Three</h2><p class=lead>Lead</p><p class=body>Body</p></article>"
      + "</div>"
    let doc = try SwiftSoup.parse(html)
    let selector = "article:has(h2 + p.lead):not(:has(p.body))"
    let eval = try QueryParser.parse(selector)
    let slow = try Collector.collect(eval, doc)
    let fast = try doc.select(selector)
    XCTAssertEqual(slow.size(), fast.size())
    XCTAssertEqual(slow.first()?.id(), fast.first()?.id())
  }

  func testSelectNotHasDescendantChainMatchesCollector() throws {
    let html =
      "<div id=wrap>" + "<section id=s1><div><p class=lead>Lead</p></div></section>"
      + "<section id=s2><div><p class=body>Body</p></div></section>"
      + "<section id=s3><div><p class=lead>Lead</p><p class=body>Body</p></div></section>"
      + "</div>"
    let doc = try SwiftSoup.parse(html)
    let selector = "section:has(div > p.lead):not(:has(p.body))"
    let eval = try QueryParser.parse(selector)
    let slow = try Collector.collect(eval, doc)
    let fast = try doc.select(selector)
    XCTAssertEqual(slow.size(), fast.size())
    XCTAssertEqual(slow.first()?.id(), fast.first()?.id())
  }

  func testSelectNotHasSiblingChainsLargeMixedTreeMatchesCollector() throws {
    let html =
      "<div id=wrap>"
      + "<article id=a1><h2>One</h2><p class=lead>Lead</p><p class=note>Note</p><p class=tail>Tail</p></article>"
      + "<article id=a2><h2>Two</h2><p class=lead>Lead</p><p class=body>Body</p><p class=note>Note</p></article>"
      + "<article id=a3><h2>Three</h2><p class=lead>Lead</p><p class=note>Note</p><p class=body>Body</p><p class=tail>Tail</p></article>"
      + "<section id=s1><h2>Side</h2><p class=lead>Lead</p><p class=note>Note</p></section>"
      + "</div>"
    let doc = try SwiftSoup.parse(html)
    let selector = "article:has(h2 + p.lead ~ p.note):not(:has(p.body + p.note))"
    let eval = try QueryParser.parse(selector)
    let slow = try Collector.collect(eval, doc)
    let fast = try doc.select(selector)
    XCTAssertEqual(slow.size(), fast.size())
    XCTAssertEqual(["a1", "a3"], slow.array().map { $0.id() })
    XCTAssertEqual(slow.array().map { $0.id() }, fast.array().map { $0.id() })
  }

  func testSelectNotHasAdjacentChainsLargeTreeMatchesCollector() throws {
    let html =
      "<div id=wrap>"
      + "<section id=s1><header></header><p class=lead>Lead</p><p class=note>Note</p></section>"
      + "<section id=s2><header></header><p class=lead>Lead</p><p class=body>Body</p><p class=note>Note</p></section>"
      + "<section id=s3><header></header><p class=lead>Lead</p><p class=note>Note</p><p class=body>Body</p></section>"
      + "<section id=s4><header></header><div><p class=lead>Lead</p><p class=note>Note</p></div></section>"
      + "</div>"
    let doc = try SwiftSoup.parse(html)
    let selector = "section:has(header + p.lead + p.note):not(:has(header + p.body + p.note))"
    let eval = try QueryParser.parse(selector)
    let slow = try Collector.collect(eval, doc)
    let fast = try doc.select(selector)
    XCTAssertEqual(slow.size(), fast.size())
    XCTAssertEqual(["s1", "s3"], slow.array().map { $0.id() })
    XCTAssertEqual(slow.array().map { $0.id() }, fast.array().map { $0.id() })
  }

  func testSelectorsMatchCollectorAfterComplexMutations() throws {
    let html =
      "<div id=wrap>"
      + "<article id=a1 class=card data-x=1><h2 id=h1>One</h2><p id=p1 class=lead>Lead</p><p id=p2 class=note>Note</p></article>"
      + "<article id=a2 class=card><h2 id=h2>Two</h2><p id=p3 class=lead>Lead</p><p id=p4 class=body>Body</p><p id=p5 class=note>Note</p></article>"
      + "<section id=s1 class=box><p id=p6 class=note>Note</p></section>"
      + "<nav id=n1><a id=l1 href=/one>One</a><a id=l2>Two</a></nav>"
      + "<ul id=u1><li id=li1>Alpha</li><li id=li2>Beta</li><li id=li3>Gamma</li></ul>" + "</div>"
    let doc = try SwiftSoup.parse(html)

    let a1 = try doc.getElementById("a1")!
    try a1.removeAttr("data-x")
    try a1.addClass("featured")

    let a2 = try doc.getElementById("a2")!
    try a2.tagName("section")

    let p4 = try doc.getElementById("p4")!
    try p4.remove()

    let p3 = try doc.getElementById("p3")!
    try p3.after("<p id=p3b class=note>Note2</p>")

    let p5 = try doc.getElementById("p5")!
    let replacement = try SwiftSoup.parse("<span id=p5r class=note>Note</span>").select("span")
      .first()!
    try p5.replaceWith(replacement)

    let l2 = try doc.getElementById("l2")!
    try l2.attr("href", "/two")

    let s1 = try doc.getElementById("s1")!
    try s1.append("<p id=p6b class=lead>Lead2</p>")

    let p6 = try doc.getElementById("p6")!
    _ = try p6.wrap("<span id=sp1></span>")
    _ = try doc.getElementById("sp1")!.unwrap()

    let li2 = try doc.getElementById("li2")!
    try li2.remove()

    func ids(_ elements: Elements) -> [String] {
      return elements.array().map { $0.id() }
    }

    let selectors = [
      "article.featured:has(h2 + p.lead ~ p.note)",
      "section.card:has(p.lead + p.note)",
      "section.box:has(p.note) + nav",
      "nav > a[href]",
      "ul > li + li",
      "[data-x]",
      "section:has(p.lead):not(:has(p.body + p.note))",
      ".note",
    ]

    for selector in selectors {
      let eval = try QueryParser.parse(selector)
      let slow = try Collector.collect(eval, doc)
      let fast = try doc.select(selector)
      XCTAssertEqual(ids(slow), ids(fast))
    }
  }

  func testSelectTagClassAndId() throws {
    let doc = try SwiftSoup.parse(
      "<div class=card id=hit></div><div class=card id=miss></div><span class=card id=hit2></span>")
    let els = try doc.select("div.card#hit")
    XCTAssertEqual(1, els.size())
    XCTAssertEqual("hit", els.get(0).id())
  }

  func testSelectAttributeValueHotKeyOrder() throws {
    let doc = try SwiftSoup.parse("<a href=one id=1></a><a href=two id=2></a><a href=one id=3></a>")
    let els: Elements = try doc.select("[href=one]")
    XCTAssertEqual(2, els.size())
    XCTAssertEqual("1", els.get(0).id())
    XCTAssertEqual("3", els.get(1).id())
  }

  func testSelectorCacheInvalidatesOnMutation() throws {
    let doc = try SwiftSoup.parse("<div id=one class=hit></div><div id=two></div>")
    let root = doc.body()!
    XCTAssertEqual(1, try root.select(".hit").size())
    // Second call should hit selector cache.
    XCTAssertEqual(1, try root.select(".hit").size())
    try doc.getElementById("two")?.addClass("hit")
    // Cache should be invalidated after mutation.
    XCTAssertEqual(2, try root.select(".hit").size())
  }

  func testSelectorCacheInvalidatesOnTextMutation() throws {
    let doc = try SwiftSoup.parse("<div id=one>foo</div><div id=two>bar</div>")
    let root = doc.body()!
    XCTAssertEqual(1, try root.select("div:contains(foo)").size())
    // Second call should hit selector cache.
    XCTAssertEqual(1, try root.select("div:contains(foo)").size())
    try doc.getElementById("two")?.text("foo")
    // Cache should be invalidated after text mutation.
    XCTAssertEqual(2, try root.select("div:contains(foo)").size())
  }

  func testSelectorCacheSingleRootForSelectRoots() throws {
    let doc = try SwiftSoup.parse("<div id=one class=hit></div><div id=two></div>")
    let root = doc.body()!
    XCTAssertEqual(1, try CssSelector.select(".hit", [root]).size())
    XCTAssertEqual(1, try CssSelector.select(".hit", [root]).size())
    try doc.getElementById("two")?.addClass("hit")
    XCTAssertEqual(2, try CssSelector.select(".hit", [root]).size())
  }

  func testSelectAttributeAbsFallback() throws {
    let doc = try SwiftSoup.parse("<a href=/one id=1></a><a href=/two id=2></a>")
    let els: Elements = try doc.select("[abs:href]")
    XCTAssertEqual(0, els.size())
  }

  func testNamespacedTag() throws {
    let doc: Document = try SwiftSoup.parse(
      "<div><abc:def id=1>Hello</abc:def></div> <abc:def class=bold id=2>There</abc:def>")
    let byTag: Elements = try doc.select("abc|def")
    XCTAssertEqual(2, byTag.size())
    XCTAssertEqual("1", byTag.first()?.id())
    XCTAssertEqual("2", byTag.last()?.id())

    let byAttr: Elements = try doc.select(".bold")
    XCTAssertEqual(1, byAttr.size())
    XCTAssertEqual("2", byAttr.last()?.id())

    let byTagAttr: Elements = try doc.select("abc|def.bold")
    XCTAssertEqual(1, byTagAttr.size())
    XCTAssertEqual("2", byTagAttr.last()?.id())

    let byContains: Elements = try doc.select("abc|def:contains(e)")
    XCTAssertEqual(2, byContains.size())
    XCTAssertEqual("1", byContains.first()?.id())
    XCTAssertEqual("2", byContains.last()?.id())
  }

  func testWildcardNamespacedTag() throws {
    let doc: Document = try SwiftSoup.parse(
      "<div><abc:def id=1>Hello</abc:def></div> <abc:def class=bold id=2>There</abc:def>")
    let byTag: Elements = try doc.select("*|def")
    XCTAssertEqual(2, byTag.size())
    XCTAssertEqual("1", byTag.first()?.id())
    XCTAssertEqual("2", byTag.last()?.id())

    let byAttr: Elements = try doc.select(".bold")
    XCTAssertEqual(1, byAttr.size())
    XCTAssertEqual("2", byAttr.last()?.id())

    let byTagAttr: Elements = try doc.select("*|def.bold")
    XCTAssertEqual(1, byTagAttr.size())
    XCTAssertEqual("2", byTagAttr.last()?.id())

    let byContains: Elements = try doc.select("*|def:contains(e)")
    XCTAssertEqual(2, byContains.size())
    XCTAssertEqual("1", byContains.first()?.id())
    XCTAssertEqual("2", byContains.last()?.id())
  }

  func testByAttributeStarting() throws {
    let doc: Document = try SwiftSoup.parse(
      "<div id=1 data-name=SwiftSoup>Hello</div><p data-val=5 id=2>There</p><p id=3>No</p>")
    var withData: Elements = try doc.select("[^data-]")
    XCTAssertEqual(2, withData.size())
    XCTAssertEqual("1", withData.first()?.id())
    XCTAssertEqual("2", withData.last()?.id())

    withData = try doc.select("p[^data-]")
    XCTAssertEqual(1, withData.size())
    XCTAssertEqual("2", withData.first()?.id())
  }

  func testByAttributeRegex() throws {
    let doc: Document = try SwiftSoup.parse(
      "<p><img src=foo.png id=1><img src=bar.jpg id=2><img src=qux.JPEG id=3><img src=old.gif><img></p>"
    )
    let imgs: Elements = try doc.select("img[src~=(?i)\\.(png|jpe?g)]")
    XCTAssertEqual(3, imgs.size())
    XCTAssertEqual("1", imgs.get(0).id())
    XCTAssertEqual("2", imgs.get(1).id())
    XCTAssertEqual("3", imgs.get(2).id())
  }

  func testByAttributeRegexCharacterClass() throws {
    let doc: Document = try SwiftSoup.parse(
      "<p><img src=foo.png id=1><img src=bar.jpg id=2><img src=qux.JPEG id=3><img src=old.gif id=4></p>"
    )
    let imgs: Elements = try doc.select("img[src~=[o]]")
    XCTAssertEqual(2, imgs.size())
    XCTAssertEqual("1", imgs.get(0).id())
    XCTAssertEqual("4", imgs.get(1).id())
  }

  func testByAttributeRegexCombined() throws {
    let doc: Document = try SwiftSoup.parse("<div><table class=x><td>Hello</td></table></div>")
    let els: Elements = try doc.select("div table[class~=x|y]")
    XCTAssertEqual(1, els.size())
    try XCTAssertEqual("Hello", els.text())
  }

  func testCombinedWithContains() throws {
    let doc: Document = try SwiftSoup.parse("<p id=1>One</p><p>Two +</p><p>Three +</p>")
    let els: Elements = try doc.select("p#1 + :contains(+)")
    XCTAssertEqual(1, els.size())
    try XCTAssertEqual("Two +", els.text())
    XCTAssertEqual("p", els.first()?.tagName())
  }

  func testAllElements() throws {
    let h: String = "<div><p>Hello</p><p><b>there</b></p></div>"
    let doc: Document = try SwiftSoup.parse(h)
    let allDoc: Elements = try doc.select("*")
    let allUnderDiv: Elements = try doc.select("div *")
    XCTAssertEqual(8, allDoc.size())
    XCTAssertEqual(3, allUnderDiv.size())
    XCTAssertEqual("p", allUnderDiv.first()?.tagName())
  }

  func testAllWithClass() throws {
    let h: String = "<p class=first>One<p class=first>Two<p>Three"
    let doc: Document = try SwiftSoup.parse(h)
    let ps: Elements = try doc.select("*.first")
    XCTAssertEqual(2, ps.size())
  }

  func testGroupOr() throws {
    let h: String = "<div title=foo /><div title=bar /><div /><p></p><img /><span title=qux>"
    let doc: Document = try SwiftSoup.parse(h)
    let els: Elements = try doc.select("p,div,[title]")

    XCTAssertEqual(5, els.size())
    XCTAssertEqual("div", els.get(0).tagName())
    try XCTAssertEqual("foo", els.get(0).attr("title"))
    XCTAssertEqual("div", els.get(1).tagName())
    try XCTAssertEqual("bar", els.get(1).attr("title"))
    XCTAssertEqual("div", els.get(2).tagName())
    try XCTAssertTrue(els.get(2).attr("title").count == 0)  // missing attributes come back as empty string
    XCTAssertFalse(els.get(2).hasAttr("title"))
    XCTAssertEqual("p", els.get(3).tagName())
    XCTAssertEqual("span", els.get(4).tagName())
  }

  func testGroupOrAttribute() throws {
    let h: String = "<div id=1 /><div id=2 /><div title=foo /><div title=bar />"
    let els: Elements = try SwiftSoup.parse(h).select("[id],[title=foo]")

    XCTAssertEqual(3, els.size())
    XCTAssertEqual("1", els.get(0).id())
    XCTAssertEqual("2", els.get(1).id())
    try XCTAssertEqual("foo", els.get(2).attr("title"))
  }

  func testDescendant() throws {
    let h: String = "<div class=head><p class=first>Hello</p><p>There</p></div><p>None</p>"
    let doc: Document = try SwiftSoup.parse(h)
    let root: Element = try doc.getElementsByClass("HEAD").first()!

    let els: Elements = try root.select(".head p")
    XCTAssertEqual(2, els.size())
    try XCTAssertEqual("Hello", els.get(0).text())
    try XCTAssertEqual("There", els.get(1).text())

    let p: Elements = try root.select("p.first")
    XCTAssertEqual(1, p.size())
    try XCTAssertEqual("Hello", p.get(0).text())

    let empty: Elements = try root.select("p .first")  // self, not descend, should not match
    XCTAssertEqual(0, empty.size())

    let aboveRoot: Elements = try root.select("body div.head")
    XCTAssertEqual(0, aboveRoot.size())
  }

  func testAnd() throws {
    let h: String =
      "<div id=1 class='foo bar' title=bar name=qux><p class=foo title=bar>Hello</p></div"
    let doc: Document = try SwiftSoup.parse(h)

    let div: Elements = try doc.select("div.foo")
    XCTAssertEqual(1, div.size())
    XCTAssertEqual("div", div.first()?.tagName())

    let p: Elements = try doc.select("div .foo")  // space indicates like "div *.foo"
    XCTAssertEqual(1, p.size())
    XCTAssertEqual("p", p.first()?.tagName())

    let div2: Elements = try doc.select("div#1.foo.bar[title=bar][name=qux]")  // very specific!
    XCTAssertEqual(1, div2.size())
    XCTAssertEqual("div", div2.first()?.tagName())

    let p2: Elements = try doc.select("div *.foo")  // space indicates like "div *.foo"
    XCTAssertEqual(1, p2.size())
    XCTAssertEqual("p", p2.first()?.tagName())
  }

  func testDeeperDescendant() throws {
    let h: String =
      "<div class=head><p><span class=first>Hello</div><div class=head><p class=first><span>Another</span><p>Again</div>"
    let doc: Document = try SwiftSoup.parse(h)
    let root: Element = try doc.getElementsByClass("head").first()!

    let els: Elements = try root.select("div p .first")
    XCTAssertEqual(1, els.size())
    try XCTAssertEqual("Hello", els.first()?.text())
    XCTAssertEqual("span", els.first()?.tagName())

    let aboveRoot: Elements = try root.select("body p .first")
    XCTAssertEqual(0, aboveRoot.size())
  }

  func testParentChildElement() throws {
    let h: String = "<div id=1><div id=2><div id = 3></div></div></div><div id=4></div>"
    let doc: Document = try SwiftSoup.parse(h)

    let divs: Elements = try doc.select("div > div")
    XCTAssertEqual(2, divs.size())
    XCTAssertEqual("2", divs.get(0).id())  // 2 is child of 1
    XCTAssertEqual("3", divs.get(1).id())  // 3 is child of 2

    let div2: Elements = try doc.select("div#1 > div")
    XCTAssertEqual(1, div2.size())
    XCTAssertEqual("2", div2.get(0).id())
  }

  func testParentWithClassChild() throws {
    let h: String =
      "<h1 class=foo><a href=1 /></h1><h1 class=foo><a href=2 class=bar /></h1><h1><a href=3 /></h1>"
    let doc: Document = try SwiftSoup.parse(h)

    let allAs: Elements = try doc.select("h1 > a")
    XCTAssertEqual(3, allAs.size())
    XCTAssertEqual("a", allAs.first()?.tagName())

    let fooAs: Elements = try doc.select("h1.foo > a")
    XCTAssertEqual(2, fooAs.size())
    XCTAssertEqual("a", fooAs.first()?.tagName())

    let barAs: Elements = try doc.select("h1.foo > a.bar")
    XCTAssertEqual(1, barAs.size())
  }

  func testParentChildStar() throws {
    let h: String = "<div id=1><p>Hello<p><b>there</b></p></div><div id=2><span>Hi</span></div>"
    let doc: Document = try SwiftSoup.parse(h)
    let divChilds: Elements = try doc.select("div > *")
    XCTAssertEqual(3, divChilds.size())
    XCTAssertEqual("p", divChilds.get(0).tagName())
    XCTAssertEqual("p", divChilds.get(1).tagName())
    XCTAssertEqual("span", divChilds.get(2).tagName())
  }

  func testMultiChildDescent() throws {
    let h: String = "<div id=foo><h1 class=bar><a href=http://example.com/>One</a></h1></div>"
    let doc: Document = try SwiftSoup.parse(h)
    let els: Elements = try doc.select("div#foo > h1.bar > a[href*=example]")
    XCTAssertEqual(1, els.size())
    XCTAssertEqual("a", els.first()?.tagName())
  }

  func testCaseInsensitive() throws {
    let h: String = "<dIv tItle=bAr><div>"  // mixed case so a simple toLowerCase() on value doesn't catch
    let doc: Document = try SwiftSoup.parse(h)

    XCTAssertEqual(2, try doc.select("DIV").size())
    XCTAssertEqual(1, try doc.select("DIV[TITLE]").size())
    XCTAssertEqual(1, try doc.select("DIV[TITLE=BAR]").size())
    XCTAssertEqual(0, try doc.select("DIV[TITLE=BARBARELLA").size())
  }

  func testAdjacentSiblings() throws {
    let h: String = "<ol><li>One<li>Two<li>Three</ol>"
    let doc: Document = try SwiftSoup.parse(h)
    let sibs: Elements = try doc.select("li + li")
    XCTAssertEqual(2, sibs.size())
    try XCTAssertEqual("Two", sibs.get(0).text())
    try XCTAssertEqual("Three", sibs.get(1).text())
  }

  func testAdjacentSiblingsWithId() throws {
    let h: String = "<ol><li id=1>One<li id=2>Two<li id=3>Three</ol>"
    let doc: Document = try SwiftSoup.parse(h)
    let sibs: Elements = try doc.select("li#1 + li#2")
    XCTAssertEqual(1, sibs.size())
    try XCTAssertEqual("Two", sibs.get(0).text())
  }

  func testNotAdjacent() throws {
    let h: String = "<ol><li id=1>One<li id=2>Two<li id=3>Three</ol>"
    let doc: Document = try SwiftSoup.parse(h)
    let sibs: Elements = try doc.select("li#1 + li#3")
    XCTAssertEqual(0, sibs.size())
  }

  func testMixCombinator() throws {
    let h: String = "<div class=foo><ol><li>One<li>Two<li>Three</ol></div>"
    let doc: Document = try SwiftSoup.parse(h)
    let sibs: Elements = try doc.select("body > div.foo li + li")

    XCTAssertEqual(2, sibs.size())
    try XCTAssertEqual("Two", sibs.get(0).text())
    try XCTAssertEqual("Three", sibs.get(1).text())
  }

  func testMixCombinatorGroup() throws {
    let h: String = "<div class=foo><ol><li>One<li>Two<li>Three</ol></div>"
    let doc: Document = try SwiftSoup.parse(h)
    let els: Elements = try doc.select(".foo > ol, ol > li + li")

    XCTAssertEqual(3, els.size())
    XCTAssertEqual("ol", els.get(0).tagName())
    try XCTAssertEqual("Two", els.get(1).text())
    try XCTAssertEqual("Three", els.get(2).text())
  }

  func testGeneralSiblings() throws {
    let h: String = "<ol><li id=1>One<li id=2>Two<li id=3>Three</ol>"
    let doc: Document = try SwiftSoup.parse(h)
    let els: Elements = try doc.select("#1 ~ #3")
    XCTAssertEqual(1, els.size())
    try XCTAssertEqual("Three", els.first()?.text())
  }

  // for http://github.com/jhy/jsoup/issues#issue/10
  func testCharactersInIdAndClass() throws {
    // using CSS spec for identifiers (id and class): a-z0-9, -, _. NOT . (which is OK in html spec, but not css)
    let h: String = "<div><p id='a1-foo_bar'>One</p><p class='b2-qux_bif'>Two</p></div>"
    let doc: Document = try SwiftSoup.parse(h)

    let el1: Element = try doc.getElementById("a1-foo_bar")!
    try XCTAssertEqual("One", el1.text())
    let el2: Element = try doc.getElementsByClass("b2-qux_bif").first()!
    XCTAssertEqual("Two", try el2.text())

    let el3: Element = try doc.select("#a1-foo_bar").first()!
    XCTAssertEqual("One", try el3.text())
    let el4: Element = try doc.select(".b2-qux_bif").first()!
    XCTAssertEqual("Two", try el4.text())
  }

  // for http://github.com/jhy/jsoup/issues#issue/13
  func testSupportsLeadingCombinator() throws {
    var h: String = "<div><p><span>One</span><span>Two</span></p></div>"
    var doc: Document = try SwiftSoup.parse(h)

    let p: Element = try doc.select("div > p").first()!
    let spans: Elements = try p.select("> span")
    XCTAssertEqual(2, spans.size())
    try XCTAssertEqual("One", spans.first()?.text())

    // make sure doesn't get nested
    h = "<div id=1><div id=2><div id=3></div></div></div>"
    doc = try SwiftSoup.parse(h)
    let div: Element = try doc.select("div").select(" > div").first()!
    XCTAssertEqual("2", div.id())
  }

  func testPseudoLessThan() throws {
    let doc: Document = try SwiftSoup.parse(
      "<div><p>One</p><p>Two</p><p>Three</>p></div><div><p>Four</p>")
    let ps: Elements = try doc.select("div p:lt(2)")
    XCTAssertEqual(3, ps.size())
    try XCTAssertEqual("One", ps.get(0).text())
    try XCTAssertEqual("Two", ps.get(1).text())
    try XCTAssertEqual("Four", ps.get(2).text())
  }

  func testPseudoGreaterThan() throws {
    let doc: Document = try SwiftSoup.parse(
      "<div><p>One</p><p>Two</p><p>Three</p></div><div><p>Four</p>")
    let ps: Elements = try doc.select("div p:gt(0)")
    XCTAssertEqual(2, ps.size())
    try XCTAssertEqual("Two", ps.get(0).text())
    try XCTAssertEqual("Three", ps.get(1).text())
  }

  func testPseudoEquals() throws {
    let doc: Document = try SwiftSoup.parse(
      "<div><p>One</p><p>Two</p><p>Three</>p></div><div><p>Four</p>")
    let ps: Elements = try doc.select("div p:eq(0)")
    XCTAssertEqual(2, ps.size())
    try XCTAssertEqual("One", ps.get(0).text())
    try XCTAssertEqual("Four", ps.get(1).text())

    let ps2: Elements = try doc.select("div:eq(0) p:eq(0)")
    XCTAssertEqual(1, ps2.size())
    try XCTAssertEqual("One", ps2.get(0).text())
    XCTAssertEqual("p", ps2.get(0).tagName())
  }

  func testPseudoBetween() throws {
    let doc: Document = try SwiftSoup.parse(
      "<div><p>One</p><p>Two</p><p>Three</>p></div><div><p>Four</p>")
    let ps: Elements = try doc.select("div p:gt(0):lt(2)")
    XCTAssertEqual(1, ps.size())
    try XCTAssertEqual("Two", ps.get(0).text())
  }

  func testPseudoCombined() throws {
    let doc: Document = try SwiftSoup.parse(
      "<div class='foo'><p>One</p><p>Two</p></div><div><p>Three</p><p>Four</p></div>")
    let ps: Elements = try doc.select("div.foo p:gt(0)")
    XCTAssertEqual(1, ps.size())
    try XCTAssertEqual("Two", ps.get(0).text())
  }

  func testPseudoHas() throws {
    let doc: Document = try SwiftSoup.parse(
      "<div id=0><p><span>Hello</span></p></div> <div id=1><span class=foo>There</span></div> <div id=2><p>Not</p></div>"
    )

    let divs1: Elements = try doc.select("div:has(span)")
    XCTAssertEqual(2, divs1.size())
    XCTAssertEqual("0", divs1.get(0).id())
    XCTAssertEqual("1", divs1.get(1).id())

    let divs2: Elements = try doc.select("div:has([class]")
    XCTAssertEqual(1, divs2.size())
    XCTAssertEqual("1", divs2.get(0).id())

    let divs3: Elements = try doc.select("div:has(span, p)")
    XCTAssertEqual(3, divs3.size())
    XCTAssertEqual("0", divs3.get(0).id())
    XCTAssertEqual("1", divs3.get(1).id())
    XCTAssertEqual("2", divs3.get(2).id())

    let els1: Elements = try doc.body()!.select(":has(p)")
    XCTAssertEqual(3, els1.size())  // body, div, dib
    XCTAssertEqual("body", els1.first()?.tagName())
    XCTAssertEqual("0", els1.get(1).id())
    XCTAssertEqual("2", els1.get(2).id())
  }

  func testNestedHas() throws {
    let doc: Document = try SwiftSoup.parse(
      "<div><p><span>One</span></p></div> <div><p>Two</p></div>")
    var divs: Elements = try doc.select("div:has(p:has(span))")
    XCTAssertEqual(1, divs.size())
    try XCTAssertEqual("One", divs.first()?.text())

    // test matches in has
    divs = try doc.select("div:has(p:matches((?i)two))")
    XCTAssertEqual(1, divs.size())
    XCTAssertEqual("div", divs.first()?.tagName())
    try XCTAssertEqual("Two", divs.first()?.text())

    // test contains in has
    divs = try doc.select("div:has(p:contains(two))")
    XCTAssertEqual(1, divs.size())
    XCTAssertEqual("div", divs.first()?.tagName())
    try XCTAssertEqual("Two", divs.first()?.text())
  }

  func testPseudoContains() throws {
    let doc: Document = try SwiftSoup.parse(
      "<div><p>The Rain.</p> <p class=light>The <i>rain</i>.</p> <p>Rain, the.</p></div>")

    let ps1: Elements = try doc.select("p:contains(Rain)")
    XCTAssertEqual(3, ps1.size())

    let ps2: Elements = try doc.select("p:contains(the rain)")
    XCTAssertEqual(2, ps2.size())
    try XCTAssertEqual("The Rain.", ps2.first()?.html())
    try XCTAssertEqual("The <i>rain</i>.", ps2.last()?.html())

    let ps3: Elements = try doc.select("p:contains(the Rain):has(i)")
    XCTAssertEqual(1, ps3.size())
    try XCTAssertEqual("light", ps3.first()?.className())

    let ps4: Elements = try doc.select(".light:contains(rain)")
    XCTAssertEqual(1, ps4.size())
    try XCTAssertEqual("light", ps3.first()?.className())

    let ps5: Elements = try doc.select(":contains(rain)")
    XCTAssertEqual(8, ps5.size())  // html, body, div,...
  }

  func testPsuedoContainsWithParentheses() throws {
    let doc: Document = try SwiftSoup.parse(
      "<div><p id=1>This (is good)</p><p id=2>This is bad)</p>")

    let ps1: Elements = try doc.select("p:contains(this (is good))")
    XCTAssertEqual(1, ps1.size())
    XCTAssertEqual("1", ps1.first()?.id())

    let ps2: Elements = try doc.select("p:contains(this is bad\\))")
    XCTAssertEqual(1, ps2.size())
    XCTAssertEqual("2", ps2.first()?.id())
  }

  func testContainsOwn() throws {
    let doc: Document = try SwiftSoup.parse("<p id=1>Hello <b>there</b> now</p>")
    let ps: Elements = try doc.select("p:containsOwn(Hello now)")
    XCTAssertEqual(1, ps.size())
    XCTAssertEqual("1", ps.first()?.id())

    XCTAssertEqual(0, try doc.select("p:containsOwn(there)").size())
  }

  func testContainsData() throws {
    let doc: Document = try SwiftSoup.parse(
      "<div><p>Some text</p><script>var foo = 'bar';</script><style>.red { color: red; }</style></div>"
    )

    // script data
    let scripts: Elements = try doc.select("script:containsData(foo)")
    XCTAssertEqual(1, scripts.size())
    XCTAssertEqual("var foo = 'bar';", scripts.first()?.data())

    // case insensitive
    let scriptsCI: Elements = try doc.select("script:containsData(FOO)")
    XCTAssertEqual(1, scriptsCI.size())

    // style data
    let styles: Elements = try doc.select("style:containsData(red)")
    XCTAssertEqual(1, styles.size())

    // no match
    let noMatch: Elements = try doc.select("script:containsData(baz)")
    XCTAssertEqual(0, noMatch.size())

    // :containsData does not match text nodes
    let textMatch: Elements = try doc.select("p:containsData(Some text)")
    XCTAssertEqual(0, textMatch.size())
  }

  func testContainsDataInDescendants() throws {
    let doc: Document = try SwiftSoup.parse("<div><script>alert('hello');</script></div>")
    let divs: Elements = try doc.select("div:containsData(hello)")
    XCTAssertEqual(1, divs.size())
    XCTAssertEqual("div", divs.first()?.tagName())
  }

  func testMatches() throws {
    let doc: Document = try SwiftSoup.parse(
      "<p id=1>The <i>Rain</i></p> <p id=2>There are 99 bottles.</p> <p id=3>Harder (this)</p> <p id=4>Rain</p>"
    )

    let p1: Elements = try doc.select("p:matches(The rain)")  // no match, case sensitive
    XCTAssertEqual(0, p1.size())

    let p2: Elements = try doc.select("p:matches((?i)the rain)")  // case insense. should include root, html, body
    XCTAssertEqual(1, p2.size())
    XCTAssertEqual("1", p2.first()?.id())

    let p4: Elements = try doc.select("p:matches((?i)^rain$)")  // bounding
    XCTAssertEqual(1, p4.size())
    XCTAssertEqual("4", p4.first()?.id())

    let p5: Elements = try doc.select("p:matches(\\d+)")
    XCTAssertEqual(1, p5.size())
    XCTAssertEqual("2", p5.first()?.id())

    let p6: Elements = try doc.select("p:matches(\\w+\\s+\\(\\w+\\))")  // test bracket matching
    XCTAssertEqual(1, p6.size())
    XCTAssertEqual("3", p6.first()?.id())

    let p7: Elements = try doc.select("p:matches((?i)the):has(i)")  // multi
    XCTAssertEqual(1, p7.size())
    XCTAssertEqual("1", p7.first()?.id())
  }

  func testMatchesOwn() throws {
    let doc: Document = try SwiftSoup.parse("<p id=1>Hello <b>there</b> now</p>")

    let p1: Elements = try doc.select("p:matchesOwn((?i)hello now)")
    XCTAssertEqual(1, p1.size())
    XCTAssertEqual("1", p1.first()?.id())

    XCTAssertEqual(0, try doc.select("p:matchesOwn(there)").size())
  }

  func testRelaxedTags() throws {
    let doc: Document = try SwiftSoup.parse(
      "<abc_def id=1>Hello</abc_def> <abc-def id=2>There</abc-def>")

    let el1: Elements = try doc.select("abc_def")
    XCTAssertEqual(1, el1.size())
    XCTAssertEqual("1", el1.first()?.id())

    let el2: Elements = try doc.select("abc-def")
    XCTAssertEqual(1, el2.size())
    XCTAssertEqual("2", el2.first()?.id())
  }

  func testNotParas() throws {
    let doc: Document = try SwiftSoup.parse("<p id=1>One</p> <p>Two</p> <p><span>Three</span></p>")

    let el1: Elements = try doc.select("p:not([id=1])")
    XCTAssertEqual(2, el1.size())
    try XCTAssertEqual("Two", el1.first()?.text())
    try XCTAssertEqual("Three", el1.last()?.text())

    let el2: Elements = try doc.select("p:not(:has(span))")
    XCTAssertEqual(2, el2.size())
    try XCTAssertEqual("One", el2.first()?.text())
    try XCTAssertEqual("Two", el2.last()?.text())
  }

  func testNotAll() throws {
    let doc: Document = try SwiftSoup.parse("<p>Two</p> <p><span>Three</span></p>")

    let el1: Elements = try doc.body()!.select(":not(p)")  // should just be the span
    XCTAssertEqual(2, el1.size())
    XCTAssertEqual("body", el1.first()?.tagName())
    XCTAssertEqual("span", el1.last()?.tagName())
  }

  func testNotClass() throws {
    let doc: Document = try SwiftSoup.parse(
      "<div class=left>One</div><div class=right id=1><p>Two</p></div>")

    let el1: Elements = try doc.select("div:not(.left)")
    XCTAssertEqual(1, el1.size())
    XCTAssertEqual("1", el1.first()?.id())
  }

  func testHandlesCommasInSelector() throws {
    let doc: Document = try SwiftSoup.parse(
      "<p name='1,2'>One</p><div>Two</div><ol><li>123</li><li>Text</li></ol>")

    let ps: Elements = try doc.select("[name=1,2]")
    XCTAssertEqual(1, ps.size())

    let containers: Elements = try doc.select("div, li:matches([0-9,]+)")
    XCTAssertEqual(2, containers.size())
    XCTAssertEqual("div", containers.get(0).tagName())
    XCTAssertEqual("li", containers.get(1).tagName())
    try XCTAssertEqual("123", containers.get(1).text())
  }

  func testSelectSupplementaryCharacter() throws {
    #if !os(Linux)
      let s = String(Character(UnicodeScalar(135361)!))
      let doc: Document = try SwiftSoup.parse("<div k" + s + "='" + s + "'>^" + s + "$/div>")
      XCTAssertEqual("div", try doc.select("div[k" + s + "]").first()?.tagName())
      XCTAssertEqual("div", try doc.select("div:containsOwn(" + s + ")").first()?.tagName())
    #endif
  }

  func testSelectClassWithSpace() throws {
    let html: String =
      "<div class=\"value\">class without space</div>\n"
      + "<div class=\"value \">class with space</div>"

    let doc: Document = try SwiftSoup.parse(html)

    var found: Elements = try doc.select("div[class=value ]")
    XCTAssertEqual(2, found.size())
    try XCTAssertEqual("class without space", found.get(0).text())
    try XCTAssertEqual("class with space", found.get(1).text())

    found = try doc.select("div[class=\"value \"]")
    XCTAssertEqual(2, found.size())
    try XCTAssertEqual("class without space", found.get(0).text())
    try XCTAssertEqual("class with space", found.get(1).text())

    found = try doc.select("div[class=\"value\\ \"]")
    XCTAssertEqual(0, found.size())
  }

  func testSelectSameElements() throws {
    let html: String = "<div>one</div><div>one</div>"

    let doc: Document = try SwiftSoup.parse(html)
    let els: Elements = try doc.select("div")
    XCTAssertEqual(2, els.size())

    let subSelect: Elements = try els.select(":contains(one)")
    XCTAssertEqual(2, subSelect.size())
  }

  func testAttributeWithBrackets() throws {
    let html: String = "<div data='End]'>One</div> <div data='[Another)]]'>Two</div>"
    let doc: Document = try SwiftSoup.parse(html)
    try _ = doc.select("div[data='End]'")
    XCTAssertEqual("One", try doc.select("div[data='End]'").first()?.text())
    XCTAssertEqual("Two", try doc.select("div[data='[Another)]]'").first()?.text())
    XCTAssertEqual("One", try doc.select("div[data=\"End]\"").first()?.text())
    XCTAssertEqual("Two", try doc.select("div[data=\"[Another)]]\"").first()?.text())
  }

  // Verify compound attribute selectors work on simple HTML
  func testCompoundAttributeSelectorSimple() throws {
    let html = "<div id='info-id' data-type='info-data'><p>Hello</p></div>"
    let doc = try SwiftSoup.parse(html)
    let result = try doc.select("div[id='info-id'][data-type='info-data']")
    XCTAssertEqual(1, result.size(), "Compound selector on simple HTML should work")
  }

  // https://github.com/scinfu/SwiftSoup/issues/390
  func testCompoundAttributeSelectorWithSpecialBodyTags() throws {
    let html = """
      <!doctype html>
      <html>
          <head>
              <title></title>
              <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
              </meta>
              <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=3, minimum-scale=1, user-scalable=yes">
              </meta>
          </head>
          <body>
              <link>I'm link</link>
              <a>I'm a</a>
              <image>I'm image</image>

              <div id="info-id" data-type="info-data">
                  <img src="cid:f269cce5-0cff-4041-81f4-d78865425c3c"/>
              </div>
          </body>
      </html>
      """

    let document = try SwiftSoup.parse(html)

    // Single attribute selectors should work
    let byId = try document.select("div[id='info-id']")
    XCTAssertEqual(1, byId.size(), "Single [id] selector should match")

    let byData = try document.select("div[data-type='info-data']")
    XCTAssertEqual(1, byData.size(), "Single [data-type] selector should match")

    // Compound attribute selector should also work
    let compound = try document.select("div[id='info-id'][data-type='info-data']")
    XCTAssertEqual(1, compound.size(), "Compound attribute selector should match one element")
  }
}
