vapir-common 1.7.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +5 -0
- data/README.txt +11 -0
- data/lib/vapir/common.rb +1 -0
- data/lib/vapir-common/browser.rb +184 -0
- data/lib/vapir-common/browsers.rb +9 -0
- data/lib/vapir-common/container.rb +163 -0
- data/lib/vapir-common/element.rb +1078 -0
- data/lib/vapir-common/element_collection.rb +83 -0
- data/lib/vapir-common/elements/elements.rb +858 -0
- data/lib/vapir-common/elements.rb +2 -0
- data/lib/vapir-common/exceptions.rb +56 -0
- data/lib/vapir-common/handle_options.rb +15 -0
- data/lib/vapir-common/modal_dialog.rb +27 -0
- data/lib/vapir-common/options.rb +49 -0
- data/lib/vapir-common/specifier.rb +322 -0
- data/lib/vapir-common/testcase.rb +89 -0
- data/lib/vapir-common/waiter.rb +141 -0
- data/lib/vapir-common/win_window.rb +1227 -0
- data/lib/vapir-common.rb +7 -0
- data/lib/vapir.rb +1 -0
- data/lib/watir-vapir.rb +15 -0
- metadata +89 -0
@@ -0,0 +1,56 @@
|
|
1
|
+
module Vapir
|
2
|
+
module Exception
|
3
|
+
|
4
|
+
# Root class for all Vapir Exceptions
|
5
|
+
class VapirException < RuntimeError
|
6
|
+
def initialize(message="")
|
7
|
+
super(message)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class NoBrowserException < VapirException; end
|
12
|
+
|
13
|
+
# This exception is thrown if an attempt is made to access an object that doesn't exist
|
14
|
+
class UnknownObjectException < VapirException; end
|
15
|
+
|
16
|
+
# This exception is raised if attempting to relocate an Element that was located in a way that does not support relocating
|
17
|
+
class UnableToRelocateException < UnknownObjectException; end
|
18
|
+
|
19
|
+
# This exception is thrown if an attempt is made to access a frame that cannot be found
|
20
|
+
class UnknownFrameException< UnknownObjectException; end
|
21
|
+
|
22
|
+
# This exception is thrown if an attempt is made to access an object that is in a disabled state
|
23
|
+
class ObjectDisabledException < VapirException; end
|
24
|
+
|
25
|
+
# This exception is thrown if an attempt is made to access an object that is in a read only state
|
26
|
+
class ObjectReadOnlyException < VapirException; end
|
27
|
+
|
28
|
+
# This exception is thrown if an attempt is made to access an object when the specified value cannot be found
|
29
|
+
class NoValueFoundException < VapirException; end
|
30
|
+
|
31
|
+
# This exception gets raised if part of finding an object is missing
|
32
|
+
class MissingWayOfFindingObjectException < VapirException; end
|
33
|
+
|
34
|
+
class WindowException < VapirException; end
|
35
|
+
# This exception is thrown if the window cannot be found
|
36
|
+
class NoMatchingWindowFoundException < WindowException; end
|
37
|
+
class WindowFailedToCloseException < WindowException; end
|
38
|
+
|
39
|
+
# This exception is thrown if an attemp is made to acces the status bar of the browser when it doesnt exist
|
40
|
+
class NoStatusBarException < VapirException; end
|
41
|
+
|
42
|
+
# This exception is thrown if an http error, such as a 404, 500 etc is encountered while navigating
|
43
|
+
class NavigationException < VapirException; end
|
44
|
+
|
45
|
+
# This exception is raised if an element does not have a container defined, and needs one.
|
46
|
+
class MissingContainerException < VapirException; end
|
47
|
+
|
48
|
+
# This exception is raised if a timeout is exceeded
|
49
|
+
class TimeOutException < VapirException
|
50
|
+
def initialize(duration, timeout)
|
51
|
+
@duration, @timeout = duration, timeout
|
52
|
+
end
|
53
|
+
attr_reader :duration, :timeout
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# takes given options and default options, and optionally a list of additional allowed keys not specified in default options
|
2
|
+
# (this is useful when you want to pass options along to another function but don't want to specify a default that will
|
3
|
+
# clobber that function's default)
|
4
|
+
# raises ArgumentError if the given options have an invalid key (defined as one not
|
5
|
+
# specified in default options or other_allowed_keys), and sets default values in given options where nothing is set.
|
6
|
+
def handle_options(given_options, default_options, other_allowed_keys=[])
|
7
|
+
given_options=given_options.dup
|
8
|
+
unless (unknown_keys=(given_options.keys-default_options.keys-other_allowed_keys)).empty?
|
9
|
+
raise ArgumentError, "Unknown options: #{(given_options.keys-default_options.keys).map(&:inspect).join(', ')}. Known options are #{(default_options.keys+other_allowed_keys).map(&:inspect).join(', ')}"
|
10
|
+
end
|
11
|
+
(default_options.keys-given_options.keys).each do |key|
|
12
|
+
given_options[key]=default_options[key]
|
13
|
+
end
|
14
|
+
given_options
|
15
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Vapir
|
2
|
+
module ModalDialog
|
3
|
+
DEFAULT_TIMEOUT=4
|
4
|
+
def default_initialize(browser, options={})
|
5
|
+
options=handle_options(options, :timeout => ModalDialog::DEFAULT_TIMEOUT, :error => true)
|
6
|
+
@browser=browser
|
7
|
+
::Waiter.try_for(options[:timeout], :exception => (options[:error] && Vapir::Exception::NoMatchingWindowFoundException.new("No popup was found on the browser"))) do
|
8
|
+
locate
|
9
|
+
end
|
10
|
+
end
|
11
|
+
alias initialize default_initialize
|
12
|
+
|
13
|
+
def locate!(options={})
|
14
|
+
exists? || raise(Vapir::Exception::NoMatchingWindowFoundException, "The modal dialog seems to have stopped existing.")
|
15
|
+
end
|
16
|
+
alias assert_exists locate!
|
17
|
+
|
18
|
+
attr_reader :browser
|
19
|
+
attr_reader :modal_window
|
20
|
+
|
21
|
+
[:locate, :exists?, :text, :set_text_field, :click_button, :close, :document].each do |virtual_method|
|
22
|
+
define_method(virtual_method) do
|
23
|
+
raise NotImplementedError, "The method \##{virtual method} should be defined on the class #{self.class}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'user-choices'
|
2
|
+
|
3
|
+
module Vapir
|
4
|
+
@@options_file = nil
|
5
|
+
@@options = nil
|
6
|
+
class << self
|
7
|
+
# Specify the location of a yaml file containing Vapir options. Must be
|
8
|
+
# specified before the options are parsed.
|
9
|
+
def options_file= file
|
10
|
+
@@options_file = file
|
11
|
+
end
|
12
|
+
def options_file
|
13
|
+
@@options_file
|
14
|
+
end
|
15
|
+
def options= x
|
16
|
+
@@options = x
|
17
|
+
end
|
18
|
+
# Return the Vapir options, as a hash. If they haven't been parsed yet,
|
19
|
+
# they will be now.
|
20
|
+
def options
|
21
|
+
@@options ||= Vapir::Options.new.execute
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Options < UserChoices::Command
|
26
|
+
include UserChoices
|
27
|
+
def add_sources builder
|
28
|
+
builder.add_source EnvironmentSource, :with_prefix, 'watir_'
|
29
|
+
if Vapir.options_file
|
30
|
+
builder.add_source YamlConfigFileSource, :from_complete_path,
|
31
|
+
Vapir.options_file
|
32
|
+
end
|
33
|
+
end
|
34
|
+
def add_choices builder
|
35
|
+
builder.add_choice :browser,
|
36
|
+
:type => Vapir::Browser.browser_names,
|
37
|
+
:default => Vapir::Browser.default
|
38
|
+
builder.add_choice :speed,
|
39
|
+
:type => ['slow', 'fast', 'zippy'],
|
40
|
+
:default => 'fast'
|
41
|
+
builder.add_choice :visible,
|
42
|
+
:type => :boolean
|
43
|
+
end
|
44
|
+
def execute
|
45
|
+
@user_choices[:speed] = @user_choices[:speed].to_sym
|
46
|
+
@user_choices
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,322 @@
|
|
1
|
+
module Vapir
|
2
|
+
# This module is included in ElementCollection and Element. it
|
3
|
+
# it expects the includer to have defined:
|
4
|
+
# - @container
|
5
|
+
# - @extra
|
6
|
+
module ElementObjectCandidates
|
7
|
+
private
|
8
|
+
|
9
|
+
# raises an error unless @container is set
|
10
|
+
def assert_container
|
11
|
+
unless @container
|
12
|
+
raise Vapir::Exception::MissingContainerException, "No container is defined for this #{self.class.inspect}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# raises an error unless @container is set and exists
|
17
|
+
def assert_container_exists
|
18
|
+
assert_container
|
19
|
+
@container.locate!
|
20
|
+
end
|
21
|
+
|
22
|
+
# this returns an Enumerable of element objects that _may_ (not necessarily do) match the
|
23
|
+
# the given specifier. sometimes specifier is completely ignored. behavor depends on
|
24
|
+
# @extra[:candidates]. when the value of @extra[:candidates] is:
|
25
|
+
# - nil (default), this uses #get_elements_by_specifiers which uses one of getElementById,
|
26
|
+
# getElementsByTagName, getElementsByName, getElementsByClassName.
|
27
|
+
# - a symbol - this is assumed to be a method of the containing_object (@container.containing_object).
|
28
|
+
# this is called, made to be an enumerable, and returned.
|
29
|
+
# - a proc - this is yielded @container and the proc is trusted to return an enumerable
|
30
|
+
# of whatever candidate element objects are desired.
|
31
|
+
# this is used by #locate in Element, and by ElementCollection.
|
32
|
+
def element_object_candidates(specifiers, aliases)
|
33
|
+
@container.assert_exists(:force => true) do
|
34
|
+
case @extra[:candidates]
|
35
|
+
when nil
|
36
|
+
get_elements_by_specifiers(@container, specifiers, aliases, respond_to?(:index_is_first) ? index_is_first : false)
|
37
|
+
when Symbol
|
38
|
+
Vapir::Element.object_collection_to_enumerable(@container.containing_object.send(@extra[:candidates]))
|
39
|
+
when Proc
|
40
|
+
@extra[:candidates].call(@container)
|
41
|
+
else
|
42
|
+
raise Vapir::Exception::MissingWayOfFindingObjectException, "Unknown method of specifying candidates: #{@extra[:candidates].inspect} (#{@extra[:candidates].class})"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
# returns an enumerable of
|
47
|
+
def matched_candidates(specifiers, aliases, &block)
|
48
|
+
match_candidates(element_object_candidates(specifiers, aliases), specifiers, aliases, &block)
|
49
|
+
end
|
50
|
+
|
51
|
+
# this is a list of what users can specify (there are additional possible hows that may be given
|
52
|
+
# to the Element constructor, but not generally for use by users, such as :element_object or :label
|
53
|
+
HowList=[:attributes, :xpath, :custom, :element_object, :label]
|
54
|
+
|
55
|
+
# returns an Enumerable of element objects that _may_ match (note, not do match, necessarily)
|
56
|
+
# the given specifiers on the given container. these are obtained from the container's containing_object
|
57
|
+
# using one of getElementById, getElementsByName, getElementsByClassName, or getElementsByTagName.
|
58
|
+
def get_elements_by_specifiers(container, specifiers, aliases, want_first=false)
|
59
|
+
if container.nil?
|
60
|
+
raise ArgumentError, "no container specified!"
|
61
|
+
end
|
62
|
+
unless specifiers.is_a?(Enumerable) && specifiers.all?{|spec| spec.is_a?(Hash)}
|
63
|
+
raise ArgumentError, "specifiers should be a list of Hashes!"
|
64
|
+
end
|
65
|
+
attributes_in_specifiers=proc do |attr|
|
66
|
+
specifiers.inject([]) do |arr, spec|
|
67
|
+
spec.each_pair do |spec_attr, spec_val|
|
68
|
+
if (aliases[attr] || []).include?(spec_attr) && !arr.include?(spec_val)
|
69
|
+
arr << spec_val
|
70
|
+
end
|
71
|
+
end
|
72
|
+
arr
|
73
|
+
end
|
74
|
+
end
|
75
|
+
ids=attributes_in_specifiers.call(:id)
|
76
|
+
tags=attributes_in_specifiers.call(:tagName)
|
77
|
+
names=attributes_in_specifiers.call(:name)
|
78
|
+
classNames=attributes_in_specifiers.call(:className)
|
79
|
+
|
80
|
+
# we can only use getElementById if:
|
81
|
+
# - id is a string, as getElementById doesn't do regexp
|
82
|
+
# - index is 1 or nil; otherwise even though it's not really valid, other identical ids won't get searched
|
83
|
+
# - id is the _only_ specifier, otherwise if the same id is used multiple times but the first one doesn't match
|
84
|
+
# the given specifiers, the element won't be found
|
85
|
+
# - container has getElementById defined (that is, it's a Browser or a Frame), otherwise if we called
|
86
|
+
# container.containing_object.getElementById we wouldn't know if what's returned is below container in the DOM heirarchy or not
|
87
|
+
# since this is almost always called with specifiers including tag name, input type, etc, getElementById is basically never used.
|
88
|
+
# TODO: have a user-settable flag somewhere that specifies that IDs are unique in pages they use. then getElementById
|
89
|
+
# could be used a lot more than it is limited to here, and stuff would be faster.
|
90
|
+
can_use_getElementById= ids.size==1 &&
|
91
|
+
ids.first.is_a?(String) &&
|
92
|
+
want_first &&
|
93
|
+
!specifiers.any?{|s| s.keys.any?{|k|k!=:id}} &&
|
94
|
+
container.containing_object.object_respond_to?(:getElementById)
|
95
|
+
|
96
|
+
# we can only use getElementsByName if:
|
97
|
+
# - name is a string; getElementsByName doesn't do regexp
|
98
|
+
# - we're only looking for elements that have a valid name attribute. those are BUTTON TEXTAREA APPLET SELECT FORM FRAME IFRAME IMG A INPUT OBJECT MAP PARAM META
|
99
|
+
# getElementsByTagName doesn't return elements that have a name attribute if name isn't supported on that type of element;
|
100
|
+
# it's treated as expando. see http://jszen.blogspot.com/2004/07/whats-in-name.html
|
101
|
+
# and http://www.w3.org/TR/html401/index/attributes.html
|
102
|
+
# this only applies to IE, and firefox could use getElementsByName more liberally, but not going to bother detecting that here.
|
103
|
+
#
|
104
|
+
# TODO/FIX: account for other bugginess in IE's getElementById / getElementsByName ?
|
105
|
+
# - http://www.romantika.name/v2/javascripts-getelementsbyname-ie-vs-firefox/
|
106
|
+
# - http://webbugtrack.blogspot.com/2007/08/bug-411-getelementsbyname-doesnt-work.html
|
107
|
+
# - http://webbugtrack.blogspot.com/2007/08/bug-152-getelementbyid-returns.html
|
108
|
+
can_use_getElementsByName=names.size==1 &&
|
109
|
+
names.first.is_a?(String) &&
|
110
|
+
container.containing_object.object_respond_to?(:getElementsByName) &&
|
111
|
+
specifiers.all?{|specifier| specifier[:tagName].is_a?(String) && %w(BUTTON TEXTAREA APPLET SELECT FORM FRAME IFRAME IMG A INPUT OBJECT MAP PARAM META).include?(specifier[:tagName].upcase) }
|
112
|
+
if can_use_getElementById
|
113
|
+
candidates= if by_id=container.containing_object.getElementById(ids.first)
|
114
|
+
[by_id]
|
115
|
+
else
|
116
|
+
[]
|
117
|
+
end
|
118
|
+
elsif can_use_getElementsByName
|
119
|
+
candidates=container.containing_object.getElementsByName(names.first)#.to_array
|
120
|
+
elsif classNames.size==1 && classNames.first.is_a?(String) && container.containing_object.object_respond_to?(:getElementsByClassName)
|
121
|
+
candidates=container.containing_object.getElementsByClassName(classNames.first)
|
122
|
+
elsif tags.size==1 && tags.first.is_a?(String)
|
123
|
+
candidates=container.containing_object.getElementsByTagName(tags.first)
|
124
|
+
else # would be nice to use getElementsByTagName for each tag name, but we can't because then we don't know the ordering for index
|
125
|
+
candidates=container.containing_object.getElementsByTagName('*')
|
126
|
+
end
|
127
|
+
# return:
|
128
|
+
if candidates.is_a?(Array)
|
129
|
+
candidates
|
130
|
+
elsif Object.const_defined?('JsshObject') && candidates.is_a?(JsshObject)
|
131
|
+
candidates.to_array
|
132
|
+
elsif Object.const_defined?('WIN32OLE') && candidates.is_a?(WIN32OLE)
|
133
|
+
candidates.send :extend, Enumerable
|
134
|
+
else
|
135
|
+
raise RuntimeError, "candidates ended up unexpectedly being #{candidates.inspect} (#{candidates.class}) - don't know what to do with this" # this shouldn't happen
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
module_function
|
140
|
+
def match_candidates(candidates, specifiers_list, aliases)
|
141
|
+
unless specifiers_list.is_a?(Enumerable) && specifiers_list.all?{|spec| spec.is_a?(Hash)}
|
142
|
+
raise ArgumentError, "specifiers_list should be a list of Hashes!"
|
143
|
+
end
|
144
|
+
if candidates.length != 0 && Object.const_defined?('JsshObject') && (candidates.is_a?(JsshObject) || candidates.all?{|c| c.is_a?(JsshObject)})
|
145
|
+
# optimize for JSSH by moving code to the other side of the socket, rather than talking across it a whole lot
|
146
|
+
# this javascript should be exactly the same as the ruby in the else (minus WIN32OLE optimization) -
|
147
|
+
# just written in javascript instead of ruby.
|
148
|
+
#
|
149
|
+
# Note that the else block works perfectly fine, but is much much slower due to the amount of
|
150
|
+
# socket activity.
|
151
|
+
jssh_socket= candidates.is_a?(JsshObject) ? candidates.jssh_socket : candidates.first.jssh_socket
|
152
|
+
match_candidates_js=JsshObject.new("
|
153
|
+
(function(candidates, specifiers_list, aliases)
|
154
|
+
{ candidates=$A(candidates);
|
155
|
+
specifiers_list=$A(specifiers_list);
|
156
|
+
var matched_candidates=[];
|
157
|
+
var fuzzy_match=function(attr, what)
|
158
|
+
{ if(typeof what=='string')
|
159
|
+
{ if(typeof attr=='string')
|
160
|
+
{ return attr.toLowerCase().strip()==what.toLowerCase().strip();
|
161
|
+
}
|
162
|
+
else
|
163
|
+
{ return attr==what;
|
164
|
+
}
|
165
|
+
}
|
166
|
+
else if(typeof what=='number')
|
167
|
+
{ return attr==what || attr==what.toString();
|
168
|
+
}
|
169
|
+
else
|
170
|
+
{ if(typeof attr=='string')
|
171
|
+
{ return attr.match(what);
|
172
|
+
}
|
173
|
+
else
|
174
|
+
{ return attr==what;
|
175
|
+
}
|
176
|
+
}
|
177
|
+
};
|
178
|
+
candidates.each(function(candidate)
|
179
|
+
{ var candidate_attributes=function(attr)
|
180
|
+
{ var attrs=[];
|
181
|
+
if(candidate.hasAttribute && candidate.hasAttribute(attr))
|
182
|
+
{ attrs.push(candidate.getAttributeNode(attr).value);
|
183
|
+
}
|
184
|
+
if(candidate[attr])
|
185
|
+
{ attrs.push(candidate[attr]);
|
186
|
+
}
|
187
|
+
return $A(attrs);
|
188
|
+
};
|
189
|
+
var match=true;
|
190
|
+
match= match && candidate.nodeType==1;
|
191
|
+
match= match && specifiers_list.any(function(specifier)
|
192
|
+
{ return $H(specifier).all(function(howwhat)
|
193
|
+
{ how=howwhat.key;
|
194
|
+
what=howwhat.value;
|
195
|
+
if(how=='types')
|
196
|
+
{ return what.any(function(type)
|
197
|
+
{ return candidate_attributes('type').any(function(attr){ return fuzzy_match(attr, type); });
|
198
|
+
});
|
199
|
+
}
|
200
|
+
else
|
201
|
+
{ var matched_aliases=$H(aliases).reject(function(dom_attr_alias_list)
|
202
|
+
{ var alias_list=$A(dom_attr_alias_list.value);
|
203
|
+
return !alias_list.include(how);
|
204
|
+
}).pluck('key');
|
205
|
+
return $A([how].concat(matched_aliases)).any(function(how_alias)
|
206
|
+
{ return candidate_attributes(how_alias).any(function(attr){ return fuzzy_match(attr, what); });
|
207
|
+
});
|
208
|
+
}
|
209
|
+
})
|
210
|
+
});
|
211
|
+
if(match)
|
212
|
+
{ matched_candidates.push(candidate);
|
213
|
+
}
|
214
|
+
});
|
215
|
+
return matched_candidates;
|
216
|
+
})
|
217
|
+
", jssh_socket, :debug_name => 'match_candidates_function')
|
218
|
+
matched_candidates=match_candidates_js.call(candidates, specifiers_list, aliases)
|
219
|
+
if block_given?
|
220
|
+
matched_candidates.to_array.each do |matched_candidate|
|
221
|
+
yield matched_candidate
|
222
|
+
end
|
223
|
+
end
|
224
|
+
return matched_candidates.to_array
|
225
|
+
else
|
226
|
+
# IF YOU CHANGE THIS CODE CHANGE THE CORRESPONDING JAVASCRIPT ABOVE TOO
|
227
|
+
matched_candidates=[]
|
228
|
+
candidates.each do |candidate|
|
229
|
+
candidate_attributes=proc do |attr|
|
230
|
+
attrs=[]
|
231
|
+
if Object.const_defined?('WIN32OLE') && candidate.is_a?(WIN32OLE)
|
232
|
+
# ie & WIN32OLE optimization: hasAttribute does not exist on IE, and also avoid respond_to? on WIN32OLE; it is slow.
|
233
|
+
begin
|
234
|
+
attr_node=candidate.getAttributeNode(attr.to_s)
|
235
|
+
attrs << attr_node.value if attr_node
|
236
|
+
rescue WIN32OLERuntimeError
|
237
|
+
end
|
238
|
+
begin
|
239
|
+
attrs << candidate.invoke(attr.to_s)
|
240
|
+
rescue WIN32OLERuntimeError
|
241
|
+
end
|
242
|
+
else
|
243
|
+
# this doesn't actually get called anymore, since there are optimizations for both IE and firefox.
|
244
|
+
# leaving it here anyway - maybe someday a different browser will have an object this code can use,
|
245
|
+
# or maybe someday IE or firefox or both will not need the optimizations above.
|
246
|
+
if candidate.object_respond_to?(:hasAttribute) && candidate.hasAttribute(attr)
|
247
|
+
attrs << candidate.getAttributeNode(attr.to_s).value
|
248
|
+
end
|
249
|
+
if candidate.object_respond_to?(attr)
|
250
|
+
attrs << candidate.invoke(attr.to_s)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
attrs
|
254
|
+
end
|
255
|
+
match=true
|
256
|
+
if Object.const_defined?('WIN32OLE') && candidate.is_a?(WIN32OLE)
|
257
|
+
begin
|
258
|
+
match &&= candidate.nodeType==1
|
259
|
+
rescue WIN32OLERuntimeError
|
260
|
+
match=false
|
261
|
+
end
|
262
|
+
else
|
263
|
+
if candidate.object_respond_to?(:nodeType)
|
264
|
+
match &&= candidate.candidate.nodeType==1
|
265
|
+
else
|
266
|
+
match=false
|
267
|
+
end
|
268
|
+
end
|
269
|
+
match &&= specifiers_list.any? do |specifier|
|
270
|
+
specifier.all? do |(how, what)|
|
271
|
+
if how==:types
|
272
|
+
what.any? do |type|
|
273
|
+
candidate_attributes.call(:type).any?{|attr| Vapir::fuzzy_match(attr, type)}
|
274
|
+
end
|
275
|
+
else
|
276
|
+
matched_aliases = aliases.reject do |dom_attr, alias_list|
|
277
|
+
!alias_list.include?(how)
|
278
|
+
end.keys
|
279
|
+
(matched_aliases+[how]).any? do |how_alias|
|
280
|
+
candidate_attributes.call(how_alias).any?{|attr| Vapir::fuzzy_match(attr, what)}
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
if match
|
286
|
+
if block_given?
|
287
|
+
yield candidate
|
288
|
+
end
|
289
|
+
matched_candidates << candidate
|
290
|
+
end
|
291
|
+
end
|
292
|
+
return matched_candidates
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
# This is on the Vapir module itself because it's used in a number of other places, should be in a broad namespace.
|
298
|
+
module_function
|
299
|
+
def fuzzy_match(attr, what)
|
300
|
+
# IF YOU CHANGE THIS, CHANGE THE JAVASCRIPT REIMPLEMENTATION IN match_candidates
|
301
|
+
case what
|
302
|
+
when String, Symbol
|
303
|
+
case attr
|
304
|
+
when String, Symbol
|
305
|
+
attr.to_s.downcase.strip==what.to_s.downcase.strip
|
306
|
+
else
|
307
|
+
attr==what
|
308
|
+
end
|
309
|
+
when Regexp
|
310
|
+
case attr
|
311
|
+
when Regexp
|
312
|
+
attr==what
|
313
|
+
else
|
314
|
+
attr =~ what
|
315
|
+
end
|
316
|
+
when Numeric
|
317
|
+
attr==what || attr==what.to_s
|
318
|
+
else
|
319
|
+
attr==what
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'test/unit/assertions'
|
3
|
+
|
4
|
+
module Vapir
|
5
|
+
# Verification methods
|
6
|
+
module Assertions
|
7
|
+
include Test::Unit::Assertions
|
8
|
+
|
9
|
+
# Log a failure if the boolean is true. The message is the failure
|
10
|
+
# message logged.
|
11
|
+
# Whether true or false, the assertion count is incremented.
|
12
|
+
def verify boolean, message = 'verify failed.'
|
13
|
+
add_assertion
|
14
|
+
add_failure message.to_s, caller unless boolean
|
15
|
+
end
|
16
|
+
|
17
|
+
def verify_equal expected, actual, message=nil
|
18
|
+
full_message = build_message(message, <<EOT, expected, actual)
|
19
|
+
<?> expected but was
|
20
|
+
<?>.
|
21
|
+
EOT
|
22
|
+
verify(expected == actual, full_message)
|
23
|
+
end
|
24
|
+
def verify_match pattern, string, message=nil
|
25
|
+
pattern = case(pattern)
|
26
|
+
when String
|
27
|
+
Regexp.new(Regexp.escape(pattern))
|
28
|
+
else
|
29
|
+
pattern
|
30
|
+
end
|
31
|
+
full_message = build_message(message, "<?> expected to be =~\n<?>.", string, pattern)
|
32
|
+
verify(string =~ pattern, full_message)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
class TestCase < Test::Unit::TestCase
|
38
|
+
include Vapir::Assertions
|
39
|
+
@@order = :sequentially
|
40
|
+
def initialize name
|
41
|
+
throw :invalid_test if name == :default_test && self.class == Vapir::TestCase
|
42
|
+
super
|
43
|
+
end
|
44
|
+
class << self
|
45
|
+
attr_accessor :test_methods, :order
|
46
|
+
def test_methods
|
47
|
+
@test_methods ||= []
|
48
|
+
end
|
49
|
+
def order
|
50
|
+
@order || @@order
|
51
|
+
end
|
52
|
+
def default_order= order
|
53
|
+
@@order = order
|
54
|
+
end
|
55
|
+
def sorted_test_methods
|
56
|
+
case order
|
57
|
+
when :alphabetically then test_methods.sort
|
58
|
+
when :sequentially then test_methods
|
59
|
+
when :reversed_sequentially then test_methods.reverse
|
60
|
+
when :reversed_alphabetically then test_methods.sort.reverse
|
61
|
+
else raise ArgumentError, "Execute option not supported: #{@order}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
def suite
|
65
|
+
suite = Test::Unit::TestSuite.new(name)
|
66
|
+
sorted_test_methods.each do |test|
|
67
|
+
catch :invalid_test do
|
68
|
+
suite << new(test)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
if (suite.empty?)
|
72
|
+
catch :invalid_test do
|
73
|
+
suite << new(:default_test)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
return suite
|
77
|
+
end
|
78
|
+
def method_added id
|
79
|
+
name = id.id2name
|
80
|
+
test_methods << name if name =~ /^test./
|
81
|
+
end
|
82
|
+
def execute order
|
83
|
+
@order = order
|
84
|
+
end
|
85
|
+
end
|
86
|
+
public :add_assertion
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|