/actionpack/lib/action_dispatch/testing/assertions/tag.rb

https://github.com/royratcliffe/rails · Ruby · 135 lines · 27 code · 5 blank · 103 comment · 0 complexity · daa73aae68debcd781d739306e60abdf MD5 · raw file

  1. require 'action_view/vendor/html-scanner'
  2. module ActionDispatch
  3. module Assertions
  4. # Pair of assertions to testing elements in the HTML output of the response.
  5. module TagAssertions
  6. # Asserts that there is a tag/node/element in the body of the response
  7. # that meets all of the given conditions. The +conditions+ parameter must
  8. # be a hash of any of the following keys (all are optional):
  9. #
  10. # * <tt>:tag</tt>: the node type must match the corresponding value
  11. # * <tt>:attributes</tt>: a hash. The node's attributes must match the
  12. # corresponding values in the hash.
  13. # * <tt>:parent</tt>: a hash. The node's parent must match the
  14. # corresponding hash.
  15. # * <tt>:child</tt>: a hash. At least one of the node's immediate children
  16. # must meet the criteria described by the hash.
  17. # * <tt>:ancestor</tt>: a hash. At least one of the node's ancestors must
  18. # meet the criteria described by the hash.
  19. # * <tt>:descendant</tt>: a hash. At least one of the node's descendants
  20. # must meet the criteria described by the hash.
  21. # * <tt>:sibling</tt>: a hash. At least one of the node's siblings must
  22. # meet the criteria described by the hash.
  23. # * <tt>:after</tt>: a hash. The node must be after any sibling meeting
  24. # the criteria described by the hash, and at least one sibling must match.
  25. # * <tt>:before</tt>: a hash. The node must be before any sibling meeting
  26. # the criteria described by the hash, and at least one sibling must match.
  27. # * <tt>:children</tt>: a hash, for counting children of a node. Accepts
  28. # the keys:
  29. # * <tt>:count</tt>: either a number or a range which must equal (or
  30. # include) the number of children that match.
  31. # * <tt>:less_than</tt>: the number of matching children must be less
  32. # than this number.
  33. # * <tt>:greater_than</tt>: the number of matching children must be
  34. # greater than this number.
  35. # * <tt>:only</tt>: another hash consisting of the keys to use
  36. # to match on the children, and only matching children will be
  37. # counted.
  38. # * <tt>:content</tt>: the textual content of the node must match the
  39. # given value. This will not match HTML tags in the body of a
  40. # tag--only text.
  41. #
  42. # Conditions are matched using the following algorithm:
  43. #
  44. # * if the condition is a string, it must be a substring of the value.
  45. # * if the condition is a regexp, it must match the value.
  46. # * if the condition is a number, the value must match number.to_s.
  47. # * if the condition is +true+, the value must not be +nil+.
  48. # * if the condition is +false+ or +nil+, the value must be +nil+.
  49. #
  50. # # Assert that there is a "span" tag
  51. # assert_tag tag: "span"
  52. #
  53. # # Assert that there is a "span" tag with id="x"
  54. # assert_tag tag: "span", attributes: { id: "x" }
  55. #
  56. # # Assert that there is a "span" tag using the short-hand
  57. # assert_tag :span
  58. #
  59. # # Assert that there is a "span" tag with id="x" using the short-hand
  60. # assert_tag :span, attributes: { id: "x" }
  61. #
  62. # # Assert that there is a "span" inside of a "div"
  63. # assert_tag tag: "span", parent: { tag: "div" }
  64. #
  65. # # Assert that there is a "span" somewhere inside a table
  66. # assert_tag tag: "span", ancestor: { tag: "table" }
  67. #
  68. # # Assert that there is a "span" with at least one "em" child
  69. # assert_tag tag: "span", child: { tag: "em" }
  70. #
  71. # # Assert that there is a "span" containing a (possibly nested)
  72. # # "strong" tag.
  73. # assert_tag tag: "span", descendant: { tag: "strong" }
  74. #
  75. # # Assert that there is a "span" containing between 2 and 4 "em" tags
  76. # # as immediate children
  77. # assert_tag tag: "span",
  78. # children: { count: 2..4, only: { tag: "em" } }
  79. #
  80. # # Get funky: assert that there is a "div", with an "ul" ancestor
  81. # # and an "li" parent (with "class" = "enum"), and containing a
  82. # # "span" descendant that contains text matching /hello world/
  83. # assert_tag tag: "div",
  84. # ancestor: { tag: "ul" },
  85. # parent: { tag: "li",
  86. # attributes: { class: "enum" } },
  87. # descendant: { tag: "span",
  88. # child: /hello world/ }
  89. #
  90. # <b>Please note</b>: +assert_tag+ and +assert_no_tag+ only work
  91. # with well-formed XHTML. They recognize a few tags as implicitly self-closing
  92. # (like br and hr and such) but will not work correctly with tags
  93. # that allow optional closing tags (p, li, td). <em>You must explicitly
  94. # close all of your tags to use these assertions.</em>
  95. def assert_tag(*opts)
  96. opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first
  97. tag = find_tag(opts)
  98. assert tag, "expected tag, but no tag found matching #{opts.inspect} in:\n#{@response.body.inspect}"
  99. end
  100. # Identical to +assert_tag+, but asserts that a matching tag does _not_
  101. # exist. (See +assert_tag+ for a full discussion of the syntax.)
  102. #
  103. # # Assert that there is not a "div" containing a "p"
  104. # assert_no_tag tag: "div", descendant: { tag: "p" }
  105. #
  106. # # Assert that an unordered list is empty
  107. # assert_no_tag tag: "ul", descendant: { tag: "li" }
  108. #
  109. # # Assert that there is not a "p" tag with between 1 to 3 "img" tags
  110. # # as immediate children
  111. # assert_no_tag tag: "p",
  112. # children: { count: 1..3, only: { tag: "img" } }
  113. def assert_no_tag(*opts)
  114. opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first
  115. tag = find_tag(opts)
  116. assert !tag, "expected no tag, but found tag matching #{opts.inspect} in:\n#{@response.body.inspect}"
  117. end
  118. def find_tag(conditions)
  119. html_document.find(conditions)
  120. end
  121. def find_all_tag(conditions)
  122. html_document.find_all(conditions)
  123. end
  124. def html_document
  125. xml = @response.content_type =~ /xml$/
  126. @html_document ||= HTML::Document.new(@response.body, false, xml)
  127. end
  128. end
  129. end
  130. end