-
Notifications
You must be signed in to change notification settings - Fork 10
/
tag_matcher.rb
142 lines (123 loc) · 4.32 KB
/
tag_matcher.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
require 'rubygems'
require 'active_support'
require 'action_controller'
require 'rexml/document'
require 'action_controller/vendor/html-scanner'
module TagMatcher
class IncludeTag
def initialize(*expected)
@expected = expected.size > 1 ? expected.last.merge({ :tag => expected.first.to_s }) : expected.first
end
def matches?(target)
@target = HTML::Document.new(target)
!@target.find(@expected).nil?
end
def failure_message
"expected tag, but no tag found matching #{@expected.inspect} in #{@target.root.to_s}"
end
def negative_failure_message
"expected no tag, but tag was found matching #{@expected.inspect} in #{@target.root.to_s}"
end
end
def include_tag(*opts)
IncludeTag.new(*opts)
end
end
module SelectorMatcher
class SelectorTag
def initialize(*expected)
# Then get mandatory selector.
arg = expected.shift
# string and we pass all remaining arguments.
# Array and we pass the argument. Also accepts selector itself.
case arg
when String
selector = HTML::Selector.new(arg, expected)
when Array
selector = HTML::Selector.new(*arg)
when HTML::Selector
selector = arg
else raise ArgumentError, "Expecting a selector as the first argument"
end
# Next argument is used for equality tests.
equals = {}
case arg = expected.shift
when Hash
equals = arg
when String, Regexp
equals[:text] = arg
when Integer
equals[:count] = arg
when Range
equals[:minimum] = arg.begin
equals[:maximum] = arg.end
when FalseClass
equals[:count] = 0
when NilClass, TrueClass
equals[:minimum] = 1
else raise ArgumentError, "I don't understand what you're trying to match"
end
# By default we're looking for at least one match.
if equals[:count]
equals[:minimum] = equals[:maximum] = equals[:count]
else
equals[:minimum] = 1 unless equals[:minimum]
end
@expected = {:selector => selector, :equals => equals}
end
def matches?(target)
@target = HTML::Document.new(target, false, false).root
matches = @expected[:selector].select(@target)
# If text/html, narrow down to those elements that match it.
content_mismatch = nil
if match_with = @expected[: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
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)
true
end
end
elsif match_with = @expected[:equals][:html]
matches.delete_if do |match|
html = match.children.map(&:to_s).join
html.strip! unless NO_STRIP.include?(match.name)
unless match_with.is_a?(Regexp) ? (html =~ match_with) : (html == match_with.to_s)
true
end
end
end
# Test minimum/maximum occurrence.
min, max = @expected[:equals][:minimum], @expected[:equals][:maximum]
if min && !max
return matches.size >= min
elsif max && !min
return matches.size <= max
elsif min && max
return (matches.size >= min) && (matches.size <= max)
else
return true
end
end
def failure_message
"expected tag, but no tag found matching #{@expected.inspect} in #{@target.to_s}"
end
def negative_failure_message
"expected no tag, but tag was found matching #{@expected.inspect} in #{@target.to_s}"
end
end
def selector_tag(*opts)
SelectorTag.new(*opts)
end
end