File: assert_select.rb

package info (click to toggle)
ruby-jquery-rails 4.6.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,712 kB
  • sloc: javascript: 20,590; ruby: 184; sh: 49; makefile: 4
file content (151 lines) | stat: -rw-r--r-- 4,832 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
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
  #
  # # asserts that #product append to a #product_list
  # assert_select_jquery :appendTo, '#product_list' do
  #   assert_select '.product'
  # end

  PATTERN_HTML  = "['\"]((\\\\\"|\\\\'|[^\"'])*)['\"]"
  PATTERN_UNICODE_ESCAPED_CHAR = /\\u([0-9a-zA-Z]{4})/
  SKELETAL_PATTERN = "(?:jQuery|\\$)\\(%s\\)\\.%s\\(%s\\)[;]?"

  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) ? escape_id(args.shift) : nil

    target_pattern   = "['\"]#{id || '.*'}['\"]"
    method_pattern   = "#{jquery_method || '\\w+'}"
    argument_pattern = jquery_opt ? "['\"]#{jquery_opt}['\"].*" : PATTERN_HTML

    # $("#id").show('blind', 1000);
    # $("#id").html("<div>something</div>");
    # $("#id").replaceWith("<div>something</div>");
    target_as_receiver_pattern = SKELETAL_PATTERN % [target_pattern, method_pattern, argument_pattern]

    # $("<div>something</div>").appendTo("#id");
    # $("<div>something</div>").prependTo("#id");
    target_as_argument_pattern = SKELETAL_PATTERN % [argument_pattern, method_pattern, target_pattern]

    # $("#id").remove();
    # $("#id").hide();
    argumentless_pattern = SKELETAL_PATTERN % [target_pattern, method_pattern, '']

    patterns = [target_as_receiver_pattern, target_as_argument_pattern]
    patterns << argumentless_pattern unless jquery_opt

    matched_pattern = nil
    patterns.each do |pattern|
      if response.body.match(Regexp.new(pattern))
        matched_pattern = pattern
        break
      end
    end

    unless matched_pattern
      opts = [jquery_method, jquery_opt, id].compact
      flunk "No JQuery call matches #{opts.inspect}"
    end

    if block_given?
      @selected ||= nil
      fragments = Nokogiri::HTML::Document.new.fragment

      if matched_pattern
        response.body.scan(Regexp.new(matched_pattern)).each do |match|
          flunk 'This function can\'t have HTML argument' if match.is_a?(String)

          doc = Nokogiri::HTML::DocumentFragment.parse(unescape_js(match.first))
          doc.children.each do |child|
            fragments << child if child.element?
          end
        end
      end

      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', '<')
      unescaped.gsub!(/\\\$/, '$')
      unescaped.gsub!(/\\`/, '`')
      # js encodes non-ascii characters.
      unescaped.gsub!(PATTERN_UNICODE_ESCAPED_CHAR) {|u| [$1.hex].pack('U*')}
      unescaped
    end

    def escape_id(selector)
      return unless selector

      id = selector.gsub('[', '\[')
      id.gsub!(']', '\]')
      id.gsub!('*', '\*')
      id.gsub!('(', '\(')
      id.gsub!(')', '\)')
      id.gsub!('.', '\.')
      id.gsub!('|', '\|')
      id.gsub!('^', '\^')
      id.gsub!('$', '\$')
      id.gsub!('+', "\\\\+")
      id.gsub!(',', '\,')

      id
    end
end