lib/jquery/assert_select.rb



require 'rails/dom/testing/assertions/selector_assertions'

module Rails::Dom::Testing::Assertions::SelectorAssertions
  # Selects content from a JQuery response.  Patterned loosely on
  # assert_select_rjs.
  #
  # === Narrowing down
  #
  # With no arguments, asserts that one or more method calls are made.
  #
  # Use the +method+ argument to narrow down the assertion to only
  # statements that call that specific method.
  #
  # Use the +opt+ argument to narrow down the assertion to only statements
  # that pass +opt+ as the first argument.
  #
  # Use the +id+ argument to narrow down the assertion to only statements
  # that invoke methods on the result of using that identifier as a
  # selector.
  #
  # === Using blocks
  #
  # Without a block, +assert_select_jquery_ merely asserts that the
  # response contains one or more statements that match the conditions
  # specified above
  #
  # With a block +assert_select_jquery_ also asserts that the method call
  # passes a javascript escaped string containing HTML.  All such HTML
  # fragments are selected and passed to the block.  Nested assertions are
  # supported.
  #
  # === Examples
  #
  # # asserts that the #notice element is hidden
  # assert_select :hide, '#notice'
  #
  # # asserts that the #cart element is shown with a blind parameter
  # assert_select :show, :blind, '#cart'
  #
  # # asserts that #cart content contains a #current_item
  # assert_select :html, '#cart' do
  #   assert_select '#current_item'
  # end

  PATTERN_HTML  = "\"((\\\\\"|[^\"])*)\""
  PATTERN_UNICODE_ESCAPED_CHAR = /\\u([0-9a-zA-Z]{4})/

  def assert_select_jquery(*args, &block)
    jquery_method = args.first.is_a?(Symbol) ? args.shift : nil
    jquery_opt    = args.first.is_a?(Symbol) ? args.shift : nil
    id            = args.first.is_a?(String) ? args.shift : nil

    pattern = "\\s*\\.#{jquery_method || '\\w+'}\\("
    pattern = "#{pattern}['\"]#{jquery_opt}['\"],?\\s*" if jquery_opt
    pattern = "#{pattern}#{PATTERN_HTML}"
    pattern = "(?:jQuery|\\$)\\(['\"]#{id}['\"]\\)#{pattern}" if id

    fragments = Nokogiri::HTML::Document.new
    response.body.scan(Regexp.new(pattern)).each do |match|
      doc = Nokogiri::HTML::Document.parse(unescape_js(match.first))
      doc.root.children.each do |child|
        fragments << child if child.element?
      end
    end

    unless fragments.children.any? { |child| child.element? }
      opts = [jquery_method, jquery_opt, id].compact
      flunk "No JQuery call matches #{opts.inspect}"
    end

    if block
      begin
        in_scope, @selected = @selected, fragments
        yield
      ensure
        @selected = in_scope
      end
    end
  end

  private

    # Unescapes a JS string.
    def unescape_js(js_string)
      # js encodes double quotes and line breaks.
      unescaped= js_string.gsub('\"', '"')
      unescaped.gsub!('\\\'', "'")
      unescaped.gsub!(/\\\//, '/')
      unescaped.gsub!('\n', "\n")
      unescaped.gsub!('\076', '>')
      unescaped.gsub!('\074', '<')
      # js encodes non-ascii characters.
      unescaped.gsub!(PATTERN_UNICODE_ESCAPED_CHAR) {|u| [$1.hex].pack('U*')}
      unescaped
    end
end