dill 0.5.2 → 0.6.0
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/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
|