dill 0.5.2 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/dill.rb +3 -15
- data/lib/dill/capybara.rb +7 -0
- data/lib/dill/checkpoint.rb +52 -34
- data/lib/dill/conversions.rb +2 -0
- data/lib/dill/dsl.rb +15 -5
- data/lib/dill/version.rb +1 -1
- data/lib/dill/widgets.rb +56 -0
- data/lib/dill/{auto_table.rb → widgets/auto_table.rb} +5 -3
- data/lib/dill/{base_table.rb → widgets/base_table.rb} +0 -0
- data/lib/dill/widgets/check_box.rb +26 -0
- data/lib/dill/widgets/document.rb +13 -0
- data/lib/dill/widgets/field.rb +26 -0
- data/lib/dill/{field_group.rb → widgets/field_group.rb} +0 -86
- data/lib/dill/{form.rb → widgets/form.rb} +9 -0
- data/lib/dill/{list.rb → widgets/list.rb} +40 -6
- data/lib/dill/{list_item.rb → widgets/list_item.rb} +0 -0
- data/lib/dill/widgets/parts/container.rb +29 -0
- data/lib/dill/widgets/parts/struct.rb +117 -0
- data/lib/dill/widgets/select.rb +29 -0
- data/lib/dill/{table.rb → widgets/table.rb} +1 -1
- data/lib/dill/widgets/text_field.rb +23 -0
- data/lib/dill/widgets/widget.rb +404 -0
- data/lib/dill/{widget_class.rb → widgets/widget_class.rb} +0 -0
- data/lib/dill/{widget_name.rb → widgets/widget_name.rb} +0 -0
- metadata +53 -16
- data/lib/dill/document.rb +0 -14
- data/lib/dill/node_text.rb +0 -9
- data/lib/dill/widget.rb +0 -516
- data/lib/dill/widget_checkpoint.rb +0 -30
- data/lib/dill/widget_container.rb +0 -23
@@ -78,7 +78,7 @@ module Dill
|
|
78
78
|
class List < Widget
|
79
79
|
include Enumerable
|
80
80
|
|
81
|
-
def_delegators :items, :
|
81
|
+
def_delegators :items, :each, :first, :last
|
82
82
|
|
83
83
|
class << self
|
84
84
|
# Configures the List item selector and class.
|
@@ -142,19 +142,53 @@ module Dill
|
|
142
142
|
end
|
143
143
|
|
144
144
|
def selector
|
145
|
-
|
146
|
-
|
147
|
-
|
145
|
+
begin
|
146
|
+
super
|
147
|
+
rescue Widget::MissingSelector
|
148
|
+
root 'ul'
|
148
149
|
|
149
|
-
|
150
|
-
|
150
|
+
super
|
151
|
+
end
|
151
152
|
end
|
152
153
|
end
|
153
154
|
|
155
|
+
def count
|
156
|
+
items.count
|
157
|
+
end
|
158
|
+
|
159
|
+
# TODO: Convert value to primitive data structures.
|
160
|
+
def empty?
|
161
|
+
items.empty?
|
162
|
+
end
|
163
|
+
|
164
|
+
def exclude?(element)
|
165
|
+
! include?(element)
|
166
|
+
end
|
167
|
+
|
168
|
+
def include?(element)
|
169
|
+
value.include?(element)
|
170
|
+
end
|
171
|
+
|
172
|
+
def length
|
173
|
+
items.length
|
174
|
+
end
|
175
|
+
|
176
|
+
def size
|
177
|
+
items.size
|
178
|
+
end
|
179
|
+
|
180
|
+
def to_row
|
181
|
+
items.map(&:to_cell)
|
182
|
+
end
|
183
|
+
|
154
184
|
def to_table
|
155
185
|
items.map(&:to_row)
|
156
186
|
end
|
157
187
|
|
188
|
+
def value
|
189
|
+
items.map(&:value)
|
190
|
+
end
|
191
|
+
|
158
192
|
protected
|
159
193
|
|
160
194
|
def_delegator 'self.class', :item_factory
|
File without changes
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Dill
|
2
|
+
module WidgetParts
|
3
|
+
module Container
|
4
|
+
def has_no_widget?(name)
|
5
|
+
widget(name).absent?
|
6
|
+
end
|
7
|
+
|
8
|
+
def has_widget?(name, *args)
|
9
|
+
widget(name, *args).present?
|
10
|
+
end
|
11
|
+
|
12
|
+
def widget(name, *args)
|
13
|
+
widget_class(name).find_in(self, *args)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
attr_writer :widget_lookup_scope
|
19
|
+
|
20
|
+
def widget_class(name)
|
21
|
+
WidgetName.new(name).to_class(widget_lookup_scope)
|
22
|
+
end
|
23
|
+
|
24
|
+
def widget_lookup_scope
|
25
|
+
@widget_lookup_scope || self.class
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module Dill
|
2
|
+
module WidgetParts
|
3
|
+
module Struct
|
4
|
+
def self.included(target)
|
5
|
+
target.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def attribute(name, selector, &block)
|
10
|
+
child = widget(name, selector, &block)
|
11
|
+
|
12
|
+
class_eval <<-WIDGET
|
13
|
+
def #{name}
|
14
|
+
widget(:#{name}).value
|
15
|
+
end
|
16
|
+
WIDGET
|
17
|
+
|
18
|
+
child
|
19
|
+
end
|
20
|
+
|
21
|
+
def boolean(name, selector, &block)
|
22
|
+
child = widget(name, selector, &block)
|
23
|
+
|
24
|
+
class_eval <<-WIDGET
|
25
|
+
def #{name}?
|
26
|
+
widget(:#{name}).value
|
27
|
+
end
|
28
|
+
WIDGET
|
29
|
+
|
30
|
+
child.class_eval <<-VALUE
|
31
|
+
def value
|
32
|
+
Dill::Conversions::Boolean(text)
|
33
|
+
end
|
34
|
+
VALUE
|
35
|
+
|
36
|
+
child
|
37
|
+
end
|
38
|
+
|
39
|
+
def date(name, selector, &block)
|
40
|
+
child = attribute(name, selector, &block)
|
41
|
+
|
42
|
+
child.class_eval <<-VALUE
|
43
|
+
def value
|
44
|
+
Date.parse(text)
|
45
|
+
end
|
46
|
+
VALUE
|
47
|
+
|
48
|
+
child
|
49
|
+
end
|
50
|
+
|
51
|
+
def float(name, selector, &block)
|
52
|
+
child = attribute(name, selector, &block)
|
53
|
+
|
54
|
+
child.class_eval <<-VALUE
|
55
|
+
def value
|
56
|
+
Float(text)
|
57
|
+
end
|
58
|
+
VALUE
|
59
|
+
|
60
|
+
child
|
61
|
+
end
|
62
|
+
|
63
|
+
def integer(name, selector, &block)
|
64
|
+
child = attribute(name, selector, &block)
|
65
|
+
|
66
|
+
child.class_eval <<-VALUE
|
67
|
+
def value
|
68
|
+
Integer(text)
|
69
|
+
end
|
70
|
+
VALUE
|
71
|
+
|
72
|
+
child
|
73
|
+
end
|
74
|
+
|
75
|
+
def list(name, selector, options = {}, &block)
|
76
|
+
child = widget(name, selector, Dill::List) do
|
77
|
+
item options[:item_selector], options[:item_class] || ListItem
|
78
|
+
end
|
79
|
+
|
80
|
+
class_eval <<-WIDGET
|
81
|
+
def #{name}
|
82
|
+
widget(:#{name}).value
|
83
|
+
end
|
84
|
+
WIDGET
|
85
|
+
|
86
|
+
child.class_eval(&block) if block_given?
|
87
|
+
|
88
|
+
child
|
89
|
+
end
|
90
|
+
|
91
|
+
def string(name, *args, &block)
|
92
|
+
child = attribute(name, *args, &block)
|
93
|
+
|
94
|
+
child.class_eval <<-VALUE
|
95
|
+
def value
|
96
|
+
text
|
97
|
+
end
|
98
|
+
VALUE
|
99
|
+
|
100
|
+
child
|
101
|
+
end
|
102
|
+
|
103
|
+
def time(name, *args, &block)
|
104
|
+
child = attribute(name, *args, &block)
|
105
|
+
|
106
|
+
child.class_eval <<-VALUE
|
107
|
+
def value
|
108
|
+
Time.parse(text)
|
109
|
+
end
|
110
|
+
VALUE
|
111
|
+
|
112
|
+
child
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Dill
|
2
|
+
# A select.
|
3
|
+
class Select < Field
|
4
|
+
# @return [String] The text of the selected option.
|
5
|
+
def get
|
6
|
+
option = root.find('[selected]') rescue nil
|
7
|
+
|
8
|
+
option && option.text
|
9
|
+
end
|
10
|
+
|
11
|
+
# Selects the given +option+.
|
12
|
+
#
|
13
|
+
# You may pass in the option text or value.
|
14
|
+
def set(option)
|
15
|
+
root.
|
16
|
+
find(:xpath, "option[@value = '#{option}' or . = '#{option}']").
|
17
|
+
select_option
|
18
|
+
end
|
19
|
+
|
20
|
+
# @!method to_s
|
21
|
+
# @return the text of the selected option, or the empty string if
|
22
|
+
# no option is selected.
|
23
|
+
def_delegator :get, :to_s
|
24
|
+
|
25
|
+
def to_cell
|
26
|
+
get
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Dill
|
2
|
+
# A text field.
|
3
|
+
class TextField < Field
|
4
|
+
# @!method get
|
5
|
+
# @return The text field value.
|
6
|
+
def_delegator :root, :value, :get
|
7
|
+
|
8
|
+
# @!method set(value)
|
9
|
+
# Sets the text field value.
|
10
|
+
#
|
11
|
+
# @param value [String] the value to set.
|
12
|
+
def_delegator :root, :set
|
13
|
+
|
14
|
+
# @!method to_s
|
15
|
+
# @return the text field value, or the empty string if the field is
|
16
|
+
# empty.
|
17
|
+
def_delegator :get, :to_s
|
18
|
+
|
19
|
+
def to_cell
|
20
|
+
get
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,404 @@
|
|
1
|
+
module Dill
|
2
|
+
class Widget
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
include WidgetParts::Struct
|
6
|
+
include WidgetParts::Container
|
7
|
+
|
8
|
+
class Removed < StandardError; end
|
9
|
+
|
10
|
+
# @!group Widget macros
|
11
|
+
|
12
|
+
# Defines a new action.
|
13
|
+
#
|
14
|
+
# This is a shortcut to help defining a widget and a method that clicks
|
15
|
+
# on that widget. You can then send a widget instance the message given
|
16
|
+
# by +name+.
|
17
|
+
#
|
18
|
+
# You can access the underlying widget by appending "_widget" to the
|
19
|
+
# action name.
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
# # Consider the widget will encapsulate the following HTML
|
23
|
+
# #
|
24
|
+
# # <div id="profile">
|
25
|
+
# # <a href="/profiles/1/edit" rel="edit">Edit</a>
|
26
|
+
# # </div>
|
27
|
+
# class PirateProfile < Dill::Widget
|
28
|
+
# root "#profile"
|
29
|
+
#
|
30
|
+
# # Declare the action
|
31
|
+
# action :edit, '[rel = edit]'
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# pirate_profile = widget(:pirate_profile)
|
35
|
+
#
|
36
|
+
# # Access the action widget
|
37
|
+
# action_widget = pirate_profile.widget(:edit_widget)
|
38
|
+
# action_widget = pirate_profile.edit_widget
|
39
|
+
#
|
40
|
+
# # Click the link
|
41
|
+
# pirate_profile.edit
|
42
|
+
#
|
43
|
+
# @param name the name of the action
|
44
|
+
# @param selector the selector for the widget that will be clicked
|
45
|
+
def self.action(name, selector)
|
46
|
+
wname = :"#{name}_widget"
|
47
|
+
|
48
|
+
widget wname, selector
|
49
|
+
|
50
|
+
define_method name do
|
51
|
+
widget(wname).click
|
52
|
+
|
53
|
+
self
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Declares a new child widget.
|
58
|
+
#
|
59
|
+
# Child widgets are accessible inside the container widget using the
|
60
|
+
# {#widget} message, or by sending a message +name+. They
|
61
|
+
# are automatically scoped to the parent widget's root node.
|
62
|
+
#
|
63
|
+
# @example Defining a widget
|
64
|
+
# # Given the following HTML:
|
65
|
+
# #
|
66
|
+
# # <div id="root">
|
67
|
+
# # <span id="child">Child</span>
|
68
|
+
# # </div>
|
69
|
+
# class Container < Dill::Widget
|
70
|
+
# root '#root'
|
71
|
+
#
|
72
|
+
# widget :my_widget, '#child'
|
73
|
+
# end
|
74
|
+
#
|
75
|
+
# container = widget(:container)
|
76
|
+
#
|
77
|
+
# # accessing using #widget
|
78
|
+
# my_widget = container.widget(:my_widget)
|
79
|
+
#
|
80
|
+
# # accessing using #my_widget
|
81
|
+
# my_widget = container.my_widget
|
82
|
+
#
|
83
|
+
# @overload widget(name, selector, type = Widget)
|
84
|
+
#
|
85
|
+
# The most common form, it allows you to pass in a selector as well as a
|
86
|
+
# type for the child widget. The selector will override +type+'s
|
87
|
+
# root selector, if +type+ has one defined.
|
88
|
+
#
|
89
|
+
# @param name the child widget's name.
|
90
|
+
# @param selector the child widget's selector. You can pass either a
|
91
|
+
# String or, if you want to use a composite selector, an Array.
|
92
|
+
# @param type the child widget's parent class.
|
93
|
+
#
|
94
|
+
# @overload widget(name, type)
|
95
|
+
#
|
96
|
+
# This form allows you to omit +selector+ from the arguments. It will
|
97
|
+
# reuse +type+'s root selector.
|
98
|
+
#
|
99
|
+
# @param name the child widget's name.
|
100
|
+
# @param type the child widget's parent class.
|
101
|
+
#
|
102
|
+
# @raise ArgumentError if +type+ has no root selector defined.
|
103
|
+
#
|
104
|
+
# @yield A block allowing you to further customize the widget behavior.
|
105
|
+
#
|
106
|
+
# @see #widget
|
107
|
+
def self.widget(name, *rest, &block)
|
108
|
+
raise ArgumentError, "`#{name}' is a reserved name" \
|
109
|
+
if WidgetParts::Container.instance_methods.include?(name.to_sym)
|
110
|
+
|
111
|
+
case rest.first
|
112
|
+
when Class
|
113
|
+
arg_count = rest.size + 1
|
114
|
+
raise ArgumentError, "wrong number of arguments (#{arg_count} for 2)" \
|
115
|
+
unless arg_count == 2
|
116
|
+
|
117
|
+
type = rest.first
|
118
|
+
raise TypeError, "can't convert `#{type}' to Widget" \
|
119
|
+
unless type.methods.include?(:selector)
|
120
|
+
raise ArgumentError, "missing root selector for `#{type}'" \
|
121
|
+
unless type.selector
|
122
|
+
|
123
|
+
selector = type.selector
|
124
|
+
when String, Array, Proc
|
125
|
+
arg_count = rest.size + 1
|
126
|
+
|
127
|
+
case arg_count
|
128
|
+
when 0, 1
|
129
|
+
raise ArgumentError, "wrong number of arguments (#{arg_count} for 2)"
|
130
|
+
when 2
|
131
|
+
selector, type = [*rest, Widget]
|
132
|
+
when 3
|
133
|
+
selector, type = rest
|
134
|
+
|
135
|
+
raise TypeError, "can't convert `#{type}' to Widget" \
|
136
|
+
unless Class === type
|
137
|
+
else
|
138
|
+
raise ArgumentError, "wrong number of arguments (#{arg_count} for 3)"
|
139
|
+
end
|
140
|
+
else
|
141
|
+
raise ArgumentError, "unknown method signature: #{rest.inspect}"
|
142
|
+
end
|
143
|
+
|
144
|
+
child = WidgetClass.new(selector, type, &block)
|
145
|
+
|
146
|
+
const_set(Dill::WidgetName.new(name).to_sym, child)
|
147
|
+
|
148
|
+
child
|
149
|
+
end
|
150
|
+
|
151
|
+
# Creates a delegator for one child widget message.
|
152
|
+
#
|
153
|
+
# Since widgets are accessed through {WidgetParts::Container#widget}, we
|
154
|
+
# can't use {Forwardable} to delegate messages to widgets.
|
155
|
+
#
|
156
|
+
# @param name the name of the receiver child widget
|
157
|
+
# @param widget_message the name of the message to be sent to the child widget
|
158
|
+
# @param method_name the name of the delegator. If +nil+ the method will
|
159
|
+
# have the same name as the message it will send.
|
160
|
+
def self.widget_delegator(name, widget_message, method_name = nil)
|
161
|
+
method_name = method_name || widget_message
|
162
|
+
|
163
|
+
class_eval <<-RUBY
|
164
|
+
def #{method_name}(*args)
|
165
|
+
if args.size == 1
|
166
|
+
widget(:#{name}).#{widget_message} args.first
|
167
|
+
else
|
168
|
+
widget(:#{name}).#{widget_message} *args
|
169
|
+
end
|
170
|
+
end
|
171
|
+
RUBY
|
172
|
+
end
|
173
|
+
|
174
|
+
# @!endgroup
|
175
|
+
|
176
|
+
# Finds a single instance of the current widget in +node+.
|
177
|
+
#
|
178
|
+
# @param node the node we want to search in
|
179
|
+
#
|
180
|
+
# @return a new instance of the current widget class.
|
181
|
+
#
|
182
|
+
# @raise [Capybara::ElementNotFoundError] if the widget can't be found
|
183
|
+
def self.find_in(parent, *args)
|
184
|
+
new { parent.root.find(*selector(*args)) }
|
185
|
+
end
|
186
|
+
|
187
|
+
# Determines if an instance of this widget class exists in
|
188
|
+
# +parent_node+.
|
189
|
+
#
|
190
|
+
# @param parent_node [Capybara::Node] the node we want to search in
|
191
|
+
#
|
192
|
+
# @return +true+ if a widget instance is found, +false+ otherwise.
|
193
|
+
def self.present_in?(parent)
|
194
|
+
find_in(parent).present?
|
195
|
+
end
|
196
|
+
|
197
|
+
# Sets this widget's default selector.
|
198
|
+
#
|
199
|
+
# You can pass more than one argument to it, or a single Array. Any valid
|
200
|
+
# Capybara selector accepted by Capybara::Node::Finders#find will work.
|
201
|
+
#
|
202
|
+
# === Examples
|
203
|
+
#
|
204
|
+
# Most of the time, your selectors will be Strings:
|
205
|
+
#
|
206
|
+
# class MyWidget < Dill::Widget
|
207
|
+
# root '.selector'
|
208
|
+
# end
|
209
|
+
#
|
210
|
+
# This will match any element with a class of "selector". For example:
|
211
|
+
#
|
212
|
+
# <span class="selector">Pick me!</span>
|
213
|
+
#
|
214
|
+
# ==== Composite selectors
|
215
|
+
#
|
216
|
+
# If you're using CSS as the query language, it's useful to be able to use
|
217
|
+
# +text: 'Some text'+ to zero in on a specific node:
|
218
|
+
#
|
219
|
+
# class MySpecificWidget < Dill::Widget
|
220
|
+
# root '.selector', text: 'Pick me!'
|
221
|
+
# end
|
222
|
+
#
|
223
|
+
# This is especially useful, e.g., when you want to create a widget
|
224
|
+
# to match a specific error or notification:
|
225
|
+
#
|
226
|
+
# class NoFreeSpace < Dill::Widget
|
227
|
+
# root '.error', text: 'No free space left!'
|
228
|
+
# end
|
229
|
+
#
|
230
|
+
# So, given the following HTML:
|
231
|
+
#
|
232
|
+
# <body>
|
233
|
+
# <div class="error">No free space left!</div>
|
234
|
+
#
|
235
|
+
# <!-- ... -->
|
236
|
+
# </body>
|
237
|
+
#
|
238
|
+
# You can test for the error's present using the following code:
|
239
|
+
#
|
240
|
+
# document.has_widget?(:no_free_space) #=> true
|
241
|
+
#
|
242
|
+
# Note: When you want to match text, consider using +I18n.t+ instead of
|
243
|
+
# hard-coding the text, so that your tests don't break when the text changes.
|
244
|
+
#
|
245
|
+
# Finally, you may want to override the query language:
|
246
|
+
#
|
247
|
+
# class MyWidgetUsesXPath < Dill::Widget
|
248
|
+
# root :xpath, '//some/node'
|
249
|
+
# end
|
250
|
+
def self.root(*selector, &block)
|
251
|
+
@selector = block ? [block] : selector.flatten
|
252
|
+
end
|
253
|
+
|
254
|
+
class MissingSelector < StandardError
|
255
|
+
end
|
256
|
+
|
257
|
+
# Returns the selector specified with +root+.
|
258
|
+
def self.selector(*args)
|
259
|
+
if @selector
|
260
|
+
fst = @selector.first
|
261
|
+
|
262
|
+
fst.respond_to?(:call) ? fst.call(*args) : @selector
|
263
|
+
else
|
264
|
+
if superclass.respond_to?(:selector)
|
265
|
+
superclass.selector
|
266
|
+
else
|
267
|
+
raise MissingSelector, 'no selector defined'
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def initialize(node = nil, &query)
|
273
|
+
self.node = node
|
274
|
+
self.query = query
|
275
|
+
end
|
276
|
+
|
277
|
+
# Alias for #gone?
|
278
|
+
def absent?
|
279
|
+
gone?
|
280
|
+
end
|
281
|
+
|
282
|
+
# Clicks the current widget, or the child widget given by +name+.
|
283
|
+
#
|
284
|
+
# === Usage
|
285
|
+
#
|
286
|
+
# Given the following widget definition:
|
287
|
+
#
|
288
|
+
# class Container < Dill::Widget
|
289
|
+
# root '#container'
|
290
|
+
#
|
291
|
+
# widget :link, 'a'
|
292
|
+
# end
|
293
|
+
#
|
294
|
+
# Send +click+ with no arguments to trigger a +click+ event on +#container+.
|
295
|
+
#
|
296
|
+
# widget(:container).click
|
297
|
+
#
|
298
|
+
# This is the equivalent of doing the following using Capybara:
|
299
|
+
#
|
300
|
+
# find('#container').click
|
301
|
+
#
|
302
|
+
# Send +click :link+ to trigger a +click+ event on +a+:
|
303
|
+
#
|
304
|
+
# widget(:container).click :link
|
305
|
+
#
|
306
|
+
# This is the equivalent of doing the following using Capybara:
|
307
|
+
#
|
308
|
+
# find('#container a').click
|
309
|
+
def click(*args)
|
310
|
+
if args.empty?
|
311
|
+
root.click
|
312
|
+
else
|
313
|
+
widget(*args).click
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
# Compares this widget with the given Cucumber +table+.
|
318
|
+
#
|
319
|
+
# === Example
|
320
|
+
#
|
321
|
+
# Then(/^some step that takes in a cucumber table$/) do |table|
|
322
|
+
# widget(:my_widget).diff table
|
323
|
+
# end
|
324
|
+
def diff(table, wait_time = Capybara.default_wait_time)
|
325
|
+
table.diff!(to_table) || true
|
326
|
+
end
|
327
|
+
|
328
|
+
# Returns +true+ if the widget is not visible, or has been removed from the
|
329
|
+
# DOM.
|
330
|
+
def gone?
|
331
|
+
! root rescue true
|
332
|
+
end
|
333
|
+
|
334
|
+
# Determines if the widget underlying an action exists.
|
335
|
+
#
|
336
|
+
# @param name the name of the action
|
337
|
+
#
|
338
|
+
# @raise Missing if an action with +name+ can't be found.
|
339
|
+
#
|
340
|
+
# @return [Boolean] +true+ if the action widget is found, +false+
|
341
|
+
# otherwise.
|
342
|
+
def has_action?(name)
|
343
|
+
raise Missing, "couldn't find `#{name}' action" unless respond_to?(name)
|
344
|
+
|
345
|
+
has_widget?(:"#{name}_widget")
|
346
|
+
end
|
347
|
+
|
348
|
+
def inspect
|
349
|
+
inspection = "<!-- #{self.class.name}: -->\n"
|
350
|
+
|
351
|
+
begin
|
352
|
+
root = self.root
|
353
|
+
xml = Nokogiri::HTML(page.body).at(root.path).to_xml
|
354
|
+
|
355
|
+
inspection << Nokogiri::XML(xml, &:noblanks).to_xhtml
|
356
|
+
rescue Capybara::NotSupportedByDriverError
|
357
|
+
inspection << "<#{root.tag_name}>\n#{to_s}"
|
358
|
+
rescue Capybara::ElementNotFound, *page.driver.invalid_element_errors
|
359
|
+
"#<DETACHED>"
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
# Returns +true+ if widget is visible.
|
364
|
+
def present?
|
365
|
+
!! root rescue false
|
366
|
+
end
|
367
|
+
|
368
|
+
def root
|
369
|
+
node || query.()
|
370
|
+
end
|
371
|
+
|
372
|
+
def text
|
373
|
+
root.text.strip
|
374
|
+
end
|
375
|
+
|
376
|
+
# Converts this widget into a string representation suitable to be displayed
|
377
|
+
# in a Cucumber table cell. By default calls #text.
|
378
|
+
#
|
379
|
+
# This method will be called by methods that build tables or rows (usually
|
380
|
+
# #to_table or #to_row) so, in general, you won't call it directly, but feel
|
381
|
+
# free to override it when needed.
|
382
|
+
#
|
383
|
+
# Returns a String.
|
384
|
+
def to_cell
|
385
|
+
text
|
386
|
+
end
|
387
|
+
|
388
|
+
def to_s
|
389
|
+
text
|
390
|
+
end
|
391
|
+
|
392
|
+
def value
|
393
|
+
text
|
394
|
+
end
|
395
|
+
|
396
|
+
private
|
397
|
+
|
398
|
+
attr_accessor :node, :query
|
399
|
+
|
400
|
+
def page
|
401
|
+
Capybara.current_session
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|