vendor/rails/actionpack/lib/action_controller/assertions/selector_assertions.rb in radiant-0.6.9 vs vendor/rails/actionpack/lib/action_controller/assertions/selector_assertions.rb in radiant-0.7.0

- old
+ new

@@ -10,24 +10,24 @@ module Assertions unless const_defined?(:NO_STRIP) NO_STRIP = %w{pre script style textarea} end - # Adds the #assert_select method for use in Rails functional + # Adds the +assert_select+ method for use in Rails functional # test cases, which can be used to make assertions on the response HTML of a controller - # action. You can also call #assert_select within another #assert_select to + # action. You can also call +assert_select+ within another +assert_select+ to # make assertions on elements selected by the enclosing assertion. # - # Use #css_select to select elements without making an assertions, either + # Use +css_select+ to select elements without making an assertions, either # from the response HTML or elements selected by the enclosing assertion. # # In addition to HTML responses, you can make the following assertions: - # * #assert_select_rjs -- Assertions on HTML content of RJS update and + # * +assert_select_rjs+ - Assertions on HTML content of RJS update and # insertion operations. - # * #assert_select_encoded -- Assertions on HTML encoded inside XML, + # * +assert_select_encoded+ - Assertions on HTML encoded inside XML, # for example for dealing with feed item descriptions. - # * #assert_select_email -- Assertions on the HTML body of an e-mail. + # * +assert_select_email+ - Assertions on the HTML body of an e-mail. # # Also see HTML::Selector to learn how to use selectors. module SelectorAssertions # :call-seq: # css_select(selector) => array @@ -42,12 +42,12 @@ # If called with two arguments, uses the first argument as the base # element and the second argument as the selector. Attempts to match the # base element and any of its children. Returns an empty array if no # match is found. # - # The selector may be a CSS selector expression (+String+), an expression - # with substitution values (+Array+) or an HTML::Selector object. + # The selector may be a CSS selector expression (String), an expression + # with substitution values (Array) or an HTML::Selector object. # # ==== Examples # # Selects all div tags # divs = css_select("div") # @@ -112,12 +112,12 @@ # # If the first argument is an element, selects all matching elements # starting from (and including) that element and all its children in # depth-first order. # - # If no element if specified, calling #assert_select will select from the - # response HTML. Calling #assert_select inside an #assert_select block will + # If no element if specified, calling +assert_select+ will select from the + # response HTML. Calling #assert_select inside an +assert_select+ block will # run the assertion for each element selected by the enclosing assertion. # # ==== Example # assert_select "ol>li" do |elements| # elements.each do |element| @@ -128,37 +128,37 @@ # Or for short: # assert_select "ol>li" do # assert_select "li" # end # - # The selector may be a CSS selector expression (+String+), an expression + # The selector may be a CSS selector expression (String), an expression # with substitution values, or an HTML::Selector object. # # === Equality Tests # # The equality test may be one of the following: - # * <tt>true</tt> -- Assertion is true if at least one element selected. - # * <tt>false</tt> -- Assertion is true if no element selected. - # * <tt>String/Regexp</tt> -- Assertion is true if the text value of at least + # * <tt>true</tt> - Assertion is true if at least one element selected. + # * <tt>false</tt> - Assertion is true if no element selected. + # * <tt>String/Regexp</tt> - Assertion is true if the text value of at least # one element matches the string or regular expression. - # * <tt>Integer</tt> -- Assertion is true if exactly that number of + # * <tt>Integer</tt> - Assertion is true if exactly that number of # elements are selected. - # * <tt>Range</tt> -- Assertion is true if the number of selected + # * <tt>Range</tt> - Assertion is true if the number of selected # elements fit the range. # If no equality test specified, the assertion is true if at least one # element selected. # # To perform more than one equality tests, use a hash with the following keys: - # * <tt>:text</tt> -- Narrow the selection to elements that have this text + # * <tt>:text</tt> - Narrow the selection to elements that have this text # value (string or regexp). - # * <tt>:html</tt> -- Narrow the selection to elements that have this HTML + # * <tt>:html</tt> - Narrow the selection to elements that have this HTML # content (string or regexp). - # * <tt>:count</tt> -- Assertion is true if the number of selected elements + # * <tt>:count</tt> - Assertion is true if the number of selected elements # is equal to this value. - # * <tt>:minimum</tt> -- Assertion is true if the number of selected + # * <tt>:minimum</tt> - Assertion is true if the number of selected # elements is at least this value. - # * <tt>:maximum</tt> -- Assertion is true if the number of selected + # * <tt>:maximum</tt> - Assertion is true if the number of selected # elements is at most this value. # # If the method is called with a block, once all equality tests are # evaluated the block is called with an array of all matched elements. # @@ -261,16 +261,19 @@ # If text/html, narrow down to those elements that match it. content_mismatch = nil if match_with = equals[:text] matches.delete_if do |match| text = "" + text.force_encoding(match_with.encoding) if text.respond_to?(:force_encoding) stack = match.children.reverse while node = stack.pop if node.tag? stack.concat node.children.reverse else - text << node.content + content = node.content + content.force_encoding(match_with.encoding) if content.respond_to?(:force_encoding) + text << content end end text.strip! unless NO_STRIP.include?(match.name) unless match_with.is_a?(Regexp) ? (text =~ match_with) : (text == match_with.to_s) content_mismatch ||= build_message(message, "<?> expected but was\n<?>.", match_with, text) @@ -351,20 +354,20 @@ # Using the <tt>:remove</tt> statement, you will be able to pass a block, but it will # be ignored as there is no HTML passed for this statement. # # === Using blocks # - # Without a block, #assert_select_rjs merely asserts that the response + # Without a block, +assert_select_rjs+ merely asserts that the response # contains one or more RJS statements that replace or update content. # - # With a block, #assert_select_rjs also selects all elements used in + # With a block, +assert_select_rjs+ also selects all elements used in # these statements and passes them to the block. Nested assertions are # supported. # - # Calling #assert_select_rjs with no arguments and using nested asserts + # Calling +assert_select_rjs+ with no arguments and using nested asserts # asserts that the HTML content is returned by one or more RJS statements. - # Using #assert_select directly makes the same assertion on the content, + # Using +assert_select+ directly makes the same assertion on the content, # but without distinguishing whether the content is returned in an HTML # or JavaScript. # # ==== Examples # @@ -393,60 +396,44 @@ # end # # # The same, but shorter. # assert_select "ol>li", 4 def assert_select_rjs(*args, &block) - rjs_type = nil - arg = args.shift + rjs_type = args.first.is_a?(Symbol) ? args.shift : nil + id = args.first.is_a?(String) ? args.shift : nil # If the first argument is a symbol, it's the type of RJS statement we're looking # for (update, replace, insertion, etc). Otherwise, we're looking for just about # any RJS statement. - if arg.is_a?(Symbol) - rjs_type = arg - + if rjs_type if rjs_type == :insert - arg = args.shift - insertion = "insert_#{arg}".to_sym - raise ArgumentError, "Unknown RJS insertion type #{arg}" unless RJS_STATEMENTS[insertion] + position = args.shift + insertion = "insert_#{position}".to_sym + raise ArgumentError, "Unknown RJS insertion type #{position}" unless RJS_STATEMENTS[insertion] statement = "(#{RJS_STATEMENTS[insertion]})" else raise ArgumentError, "Unknown RJS statement type #{rjs_type}" unless RJS_STATEMENTS[rjs_type] statement = "(#{RJS_STATEMENTS[rjs_type]})" end - arg = args.shift else statement = "#{RJS_STATEMENTS[:any]}" end # Next argument we're looking for is the element identifier. If missing, we pick - # any element. - if arg.is_a?(String) - id = Regexp.quote(arg) - arg = args.shift - else - id = "[^\"]*" - end + # any element, otherwise we replace it in the statement. + pattern = Regexp.new( + id ? statement.gsub(RJS_ANY_ID, "\"#{id}\"") : statement + ) - pattern = - case rjs_type - when :chained_replace, :chained_replace_html - Regexp.new("\\$\\(\"#{id}\"\\)#{statement}\\(#{RJS_PATTERN_HTML}\\)", Regexp::MULTILINE) - when :remove, :show, :hide, :toggle - Regexp.new("#{statement}\\(\"#{id}\"\\)") - else - Regexp.new("#{statement}\\(\"#{id}\", #{RJS_PATTERN_HTML}\\)", Regexp::MULTILINE) - end - # Duplicate the body since the next step involves destroying it. matches = nil case rjs_type when :remove, :show, :hide, :toggle matches = @response.body.match(pattern) else @response.body.gsub(pattern) do |match| - html = unescape_rjs($2) + html = unescape_rjs(match) matches ||= [] matches.concat HTML::Document.new(html).root.children.select { |n| n.tag? } "" end end @@ -572,45 +559,41 @@ end end protected unless const_defined?(:RJS_STATEMENTS) - RJS_STATEMENTS = { - :replace => /Element\.replace/, - :replace_html => /Element\.update/, - :chained_replace => /\.replace/, - :chained_replace_html => /\.update/, - :remove => /Element\.remove/, - :show => /Element\.show/, - :hide => /Element\.hide/, - :toggle => /Element\.toggle/ + RJS_PATTERN_HTML = "\"((\\\\\"|[^\"])*)\"" + RJS_ANY_ID = "\"([^\"])*\"" + RJS_STATEMENTS = { + :chained_replace => "\\$\\(#{RJS_ANY_ID}\\)\\.replace\\(#{RJS_PATTERN_HTML}\\)", + :chained_replace_html => "\\$\\(#{RJS_ANY_ID}\\)\\.update\\(#{RJS_PATTERN_HTML}\\)", + :replace_html => "Element\\.update\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)", + :replace => "Element\\.replace\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)" } - RJS_INSERTIONS = [:top, :bottom, :before, :after] + [:remove, :show, :hide, :toggle].each do |action| + RJS_STATEMENTS[action] = "Element\\.#{action}\\(#{RJS_ANY_ID}\\)" + end + RJS_INSERTIONS = ["top", "bottom", "before", "after"] RJS_INSERTIONS.each do |insertion| - RJS_STATEMENTS["insert_#{insertion}".to_sym] = Regexp.new(Regexp.quote("new Insertion.#{insertion.to_s.camelize}")) + RJS_STATEMENTS["insert_#{insertion}".to_sym] = "Element.insert\\(#{RJS_ANY_ID}, \\{ #{insertion}: #{RJS_PATTERN_HTML} \\}\\)" end + RJS_STATEMENTS[:insert_html] = "Element.insert\\(#{RJS_ANY_ID}, \\{ (#{RJS_INSERTIONS.join('|')}): #{RJS_PATTERN_HTML} \\}\\)" RJS_STATEMENTS[:any] = Regexp.new("(#{RJS_STATEMENTS.values.join('|')})") - RJS_STATEMENTS[:insert_html] = Regexp.new(RJS_INSERTIONS.collect do |insertion| - Regexp.quote("new Insertion.#{insertion.to_s.camelize}") - end.join('|')) - RJS_PATTERN_HTML = /"((\\"|[^"])*)"/ - RJS_PATTERN_EVERYTHING = Regexp.new("#{RJS_STATEMENTS[:any]}\\(\"([^\"]*)\", #{RJS_PATTERN_HTML}\\)", - Regexp::MULTILINE) RJS_PATTERN_UNICODE_ESCAPED_CHAR = /\\u([0-9a-zA-Z]{4})/ end - # #assert_select and #css_select call this to obtain the content in the HTML + # +assert_select+ and +css_select+ call this to obtain the content in the HTML # page, or from all the RJS statements, depending on the type of response. def response_from_page_or_rjs() content_type = @response.content_type if content_type && content_type =~ /text\/javascript/ body = @response.body.dup root = HTML::Node.new(nil) while true - next if body.sub!(RJS_PATTERN_EVERYTHING) do |match| - html = unescape_rjs($3) + next if body.sub!(RJS_STATEMENTS[:any]) do |match| + html = unescape_rjs(match) matches = HTML::Document.new(html).root.children.select { |n| n.tag? } root.children.concat matches "" end break