/
assert_select.rb
151 lines (132 loc) · 4.72 KB
/
assert_select.rb
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