capybara-ui 0.10.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/lib/capybara/ui.rb +33 -0
  3. data/lib/capybara/ui/assertions.rb +21 -0
  4. data/lib/{capybara-ui → capybara/ui}/capybara.rb +0 -0
  5. data/lib/capybara/ui/checkpoint.rb +113 -0
  6. data/lib/capybara/ui/conversions.rb +33 -0
  7. data/lib/capybara/ui/cucumber.rb +5 -0
  8. data/lib/capybara/ui/dsl.rb +109 -0
  9. data/lib/capybara/ui/instance_conversions.rb +21 -0
  10. data/lib/{capybara-ui → capybara/ui}/matchers.rb +4 -4
  11. data/lib/capybara/ui/optional_dependencies.rb +7 -0
  12. data/lib/capybara/ui/rails.rb +5 -0
  13. data/lib/capybara/ui/rails/role.rb +11 -0
  14. data/lib/capybara/ui/role.rb +21 -0
  15. data/lib/capybara/ui/text_table.rb +109 -0
  16. data/lib/capybara/ui/text_table/cell_text.rb +9 -0
  17. data/lib/capybara/ui/text_table/mapping.rb +42 -0
  18. data/lib/capybara/ui/text_table/transformations.rb +15 -0
  19. data/lib/capybara/ui/text_table/void_mapping.rb +10 -0
  20. data/lib/capybara/ui/version.rb +5 -0
  21. data/lib/capybara/ui/widgets.rb +63 -0
  22. data/lib/capybara/ui/widgets/check_box.rb +28 -0
  23. data/lib/capybara/ui/widgets/cucumber_methods.rb +75 -0
  24. data/lib/capybara/ui/widgets/document.rb +21 -0
  25. data/lib/capybara/ui/widgets/dsl.rb +49 -0
  26. data/lib/capybara/ui/widgets/field.rb +24 -0
  27. data/lib/capybara/ui/widgets/field_group.rb +331 -0
  28. data/lib/capybara/ui/widgets/form.rb +28 -0
  29. data/lib/capybara/ui/widgets/list.rb +202 -0
  30. data/lib/capybara/ui/widgets/list_item.rb +24 -0
  31. data/lib/capybara/ui/widgets/parts/container.rb +48 -0
  32. data/lib/capybara/ui/widgets/parts/struct.rb +119 -0
  33. data/lib/capybara/ui/widgets/radio_button.rb +64 -0
  34. data/lib/capybara/ui/widgets/select.rb +59 -0
  35. data/lib/capybara/ui/widgets/string_value.rb +45 -0
  36. data/lib/capybara/ui/widgets/table.rb +78 -0
  37. data/lib/capybara/ui/widgets/text_field.rb +29 -0
  38. data/lib/capybara/ui/widgets/widget.rb +394 -0
  39. data/lib/capybara/ui/widgets/widget/node_filter.rb +50 -0
  40. data/lib/capybara/ui/widgets/widget_class.rb +13 -0
  41. data/lib/capybara/ui/widgets/widget_name.rb +58 -0
  42. metadata +47 -43
  43. data/lib/capybara-ui.rb +0 -31
  44. data/lib/capybara-ui/assertions.rb +0 -19
  45. data/lib/capybara-ui/checkpoint.rb +0 -111
  46. data/lib/capybara-ui/conversions.rb +0 -31
  47. data/lib/capybara-ui/cucumber.rb +0 -5
  48. data/lib/capybara-ui/dsl.rb +0 -107
  49. data/lib/capybara-ui/instance_conversions.rb +0 -19
  50. data/lib/capybara-ui/optional_dependencies.rb +0 -5
  51. data/lib/capybara-ui/rails.rb +0 -5
  52. data/lib/capybara-ui/rails/role.rb +0 -9
  53. data/lib/capybara-ui/role.rb +0 -19
  54. data/lib/capybara-ui/text_table.rb +0 -107
  55. data/lib/capybara-ui/text_table/cell_text.rb +0 -7
  56. data/lib/capybara-ui/text_table/mapping.rb +0 -40
  57. data/lib/capybara-ui/text_table/transformations.rb +0 -13
  58. data/lib/capybara-ui/text_table/void_mapping.rb +0 -8
  59. data/lib/capybara-ui/version.rb +0 -3
  60. data/lib/capybara-ui/widgets.rb +0 -61
  61. data/lib/capybara-ui/widgets/check_box.rb +0 -26
  62. data/lib/capybara-ui/widgets/cucumber_methods.rb +0 -73
  63. data/lib/capybara-ui/widgets/document.rb +0 -19
  64. data/lib/capybara-ui/widgets/dsl.rb +0 -47
  65. data/lib/capybara-ui/widgets/field.rb +0 -22
  66. data/lib/capybara-ui/widgets/field_group.rb +0 -329
  67. data/lib/capybara-ui/widgets/form.rb +0 -26
  68. data/lib/capybara-ui/widgets/list.rb +0 -200
  69. data/lib/capybara-ui/widgets/list_item.rb +0 -22
  70. data/lib/capybara-ui/widgets/parts/container.rb +0 -46
  71. data/lib/capybara-ui/widgets/parts/struct.rb +0 -117
  72. data/lib/capybara-ui/widgets/radio_button.rb +0 -62
  73. data/lib/capybara-ui/widgets/select.rb +0 -57
  74. data/lib/capybara-ui/widgets/string_value.rb +0 -43
  75. data/lib/capybara-ui/widgets/table.rb +0 -76
  76. data/lib/capybara-ui/widgets/text_field.rb +0 -27
  77. data/lib/capybara-ui/widgets/widget.rb +0 -392
  78. data/lib/capybara-ui/widgets/widget/node_filter.rb +0 -48
  79. data/lib/capybara-ui/widgets/widget_class.rb +0 -11
  80. data/lib/capybara-ui/widgets/widget_name.rb +0 -56
@@ -1,57 +0,0 @@
1
- module CapybaraUI
2
- # A select.
3
- class Select < Field
4
- def selected
5
- root.all(:xpath, ".//option", visible: true).select(&:selected?).first
6
- end
7
-
8
- module Selectable
9
- def select
10
- root.select_option
11
- end
12
- end
13
-
14
- widget :option, -> (opt) {
15
- opt.is_a?(Regexp) ? ["option", text: opt] : [:option, opt]
16
- } do
17
- include Selectable
18
- end
19
-
20
- widget :option_by_value, -> (opt) { "option[value = #{opt.inspect}]" } do
21
- include Selectable
22
- end
23
-
24
- # @return [String] The text of the selected option.
25
- def get
26
- selected.text unless selected.nil?
27
- end
28
-
29
- # @return [String] The value of the selected option.
30
- def value
31
- selected.value unless selected.nil?
32
- end
33
-
34
- # Selects the given +option+.
35
- #
36
- # You may pass in the option text or value.
37
- def set(option)
38
- widget(:option, option).select
39
- rescue
40
- begin
41
- widget(:option_by_value, option).select
42
- rescue CapybaraUI::MissingWidget => e
43
- raise InvalidOption.new(e.message).
44
- tap { |x| x.set_backtrace e.backtrace }
45
- end
46
- end
47
-
48
- # @!method to_s
49
- # @return the text of the selected option, or the empty string if
50
- # no option is selected.
51
- def_delegator :get, :to_s
52
-
53
- def to_cell
54
- get
55
- end
56
- end
57
- end
@@ -1,43 +0,0 @@
1
- module CapybaraUI
2
- class StringValue < String
3
- def to_date(format = nil)
4
- format ? Date.strptime(self, format) : super()
5
- end
6
-
7
- def to_key
8
- fst, rest = first, self[1..-1]
9
- decamelized = fst + rest.gsub(/([A-Z])/, '_\1')
10
- underscored = decamelized.gsub(/[\W_]+/, '_')
11
- stripped = underscored.gsub(/^_|_$/, '')
12
- downcased = stripped.downcase
13
- key = downcased.to_sym
14
-
15
- key
16
- end
17
-
18
- class Money
19
- extend Forwardable
20
-
21
- delegate %w(to_i to_f) => :str
22
-
23
- def initialize(str)
24
- fail ArgumentError, "can't convert `#{str}` to money" \
25
- unless str =~ /^-?\$\d+(?:,\d{3})*(?:\.\d+)?/
26
-
27
- @str = (str =~ /^-/ ? '-' : '') + str.gsub(/^-?\$|,/, '')
28
- end
29
-
30
- private
31
-
32
- attr_reader :str
33
- end
34
-
35
- def to_usd
36
- Money.new(self)
37
- end
38
-
39
- def to_split
40
- split(',').map(&:strip).map { |e| self.class.new(e) }
41
- end
42
- end
43
- end
@@ -1,76 +0,0 @@
1
- module CapybaraUI
2
- class Table < CapybaraUI::Widget
3
- root 'table'
4
-
5
- class Row < CapybaraUI::List
6
- def self.column(*args, &block)
7
- item(*args, &block)
8
- end
9
- end
10
-
11
- def self.header_row(selector, &block)
12
- widget :header_row, selector, Row, &block
13
- end
14
-
15
- header_row 'thead tr' do
16
- column 'th'
17
- end
18
-
19
- def self.data_row(selector, &block)
20
- widget :data_row, selector, Row, &block
21
- end
22
-
23
- data_row 'tbody tr' do
24
- column 'td'
25
- end
26
-
27
- class Columns
28
- include Enumerable
29
-
30
- def initialize(parent)
31
- @parent = parent
32
- end
33
-
34
- def [](header_or_index)
35
- case header_or_index
36
- when Integer
37
- values_by_index(header_or_index)
38
- when String
39
- values_by_header(header_or_index)
40
- else
41
- raise TypeError,
42
- "can't convert #{header_or_index.inspect} to Integer or String"
43
- end
44
- end
45
-
46
- def each(&block)
47
- parent.each(&block)
48
- end
49
-
50
- private
51
-
52
- attr_reader :parent
53
-
54
- def values_by_index(index)
55
- parent.rows.transpose[index]
56
- end
57
-
58
- def values_by_header(header)
59
- values_by_index(find_header_index(header))
60
- end
61
-
62
- def find_header_index(header)
63
- parent.widget(:header_row).value.find_index(header) or
64
- raise ArgumentError, "header not found: #{header.inspect}"
65
- end
66
- end
67
-
68
- def columns
69
- Columns.new(self)
70
- end
71
-
72
- def rows
73
- widgets(:data_row).map(&:value)
74
- end
75
- end
76
- end
@@ -1,27 +0,0 @@
1
- module CapybaraUI
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
-
23
- def content?
24
- get.respond_to?(:empty?) ? ! get.empty? : !! get
25
- end
26
- end
27
- end
@@ -1,392 +0,0 @@
1
- module CapybaraUI
2
- class Widget
3
- extend Forwardable
4
- extend Widgets::DSL
5
-
6
- include WidgetParts::Struct
7
- include WidgetParts::Container
8
- include CucumberMethods
9
-
10
- class Removed < StandardError; end
11
-
12
- attr_reader :root
13
-
14
- # @!group Widget macros
15
-
16
- # Defines a new action.
17
- #
18
- # This is a shortcut to help defining a widget and a method that clicks
19
- # on that widget. You can then send a widget instance the message given
20
- # by +name+.
21
- #
22
- # You can access the underlying widget by appending "_widget" to the
23
- # action name.
24
- #
25
- # @example
26
- # # Consider the widget will encapsulate the following HTML
27
- # #
28
- # # <div id="profile">
29
- # # <a href="/profiles/1/edit" rel="edit">Edit</a>
30
- # # </div>
31
- # class PirateProfile < CapybaraUI::Widget
32
- # root "#profile"
33
- #
34
- # # Declare the action
35
- # action :edit, '[rel = edit]'
36
- # end
37
- #
38
- # pirate_profile = widget(:pirate_profile)
39
- #
40
- # # Access the action widget
41
- # action_widget = pirate_profile.widget(:edit_widget)
42
- # action_widget = pirate_profile.edit_widget
43
- #
44
- # # Click the link
45
- # pirate_profile.edit
46
- #
47
- # @param name the name of the action
48
- # @param selector the selector for the widget that will be clicked
49
- def self.action(name, selector = nil)
50
- block = if selector
51
- wname = :"#{name}_widget"
52
-
53
- widget wname, selector
54
-
55
- -> { widget(wname).click; self }
56
- else
57
- -> { click; self }
58
- end
59
-
60
- define_method name, &block
61
- end
62
-
63
- # Creates a delegator for one child widget message.
64
- #
65
- # Since widgets are accessed through {WidgetParts::Container#widget}, we
66
- # can't use {Forwardable} to delegate messages to widgets.
67
- #
68
- # @param name the name of the receiver child widget
69
- # @param widget_message the name of the message to be sent to the child widget
70
- # @param method_name the name of the delegator. If +nil+ the method will
71
- # have the same name as the message it will send.
72
- def self.widget_delegator(name, widget_message, method_name = nil)
73
- method_name = method_name || widget_message
74
-
75
- class_eval <<-RUBY
76
- def #{method_name}(*args)
77
- if args.size == 1
78
- widget(:#{name}).#{widget_message} args.first
79
- else
80
- widget(:#{name}).#{widget_message} *args
81
- end
82
- end
83
- RUBY
84
- end
85
-
86
- # @!endgroup
87
-
88
- # Finds a single instance of the current widget in +node+.
89
- #
90
- # @param node the node we want to search in
91
- #
92
- # @return a new instance of the current widget class.
93
- #
94
- # @raise [Capybara::ElementNotFoundError] if the widget can't be found
95
- def self.find_in(parent, *args)
96
- new(filter.node(parent, *args))
97
- rescue Capybara::Ambiguous => e
98
- raise AmbiguousWidget.new(e.message).
99
- tap { |x| x.set_backtrace e.backtrace }
100
- rescue Capybara::ElementNotFound => e
101
- raise MissingWidget.new(e.message).
102
- tap { |x| x.set_backtrace e.backtrace }
103
- end
104
-
105
- def self.find_all_in(parent, *args)
106
- filter.nodes(parent, *args).map { |e| new(e) }
107
- end
108
-
109
- # Determines if an instance of this widget class exists in
110
- # +parent_node+.
111
- #
112
- # @param parent_node [Capybara::Node] the node we want to search in
113
- #
114
- # @return +true+ if a widget instance is found, +false+ otherwise.
115
- def self.present_in?(parent, *args)
116
- filter.node?(parent, *args)
117
- end
118
-
119
- def self.not_present_in?(parent, *args)
120
- filter.nodeless?(parent, *args)
121
- end
122
-
123
- # Sets this widget's default selector.
124
- #
125
- # You can pass more than one argument to it, or a single Array. Any valid
126
- # Capybara selector accepted by Capybara::Node::Finders#find will work.
127
- #
128
- # === Examples
129
- #
130
- # Most of the time, your selectors will be Strings:
131
- #
132
- # class MyWidget < CapybaraUI::Widget
133
- # root '.selector'
134
- # end
135
- #
136
- # This will match any element with a class of "selector". For example:
137
- #
138
- # <span class="selector">Pick me!</span>
139
- #
140
- # ==== Composite selectors
141
- #
142
- # If you're using CSS as the query language, it's useful to be able to use
143
- # +text: 'Some text'+ to zero in on a specific node:
144
- #
145
- # class MySpecificWidget < CapybaraUI::Widget
146
- # root '.selector', text: 'Pick me!'
147
- # end
148
- #
149
- # This is especially useful, e.g., when you want to create a widget
150
- # to match a specific error or notification:
151
- #
152
- # class NoFreeSpace < CapybaraUI::Widget
153
- # root '.error', text: 'No free space left!'
154
- # end
155
- #
156
- # So, given the following HTML:
157
- #
158
- # <body>
159
- # <div class="error">No free space left!</div>
160
- #
161
- # <!-- ... -->
162
- # </body>
163
- #
164
- # You can test for the error's present using the following code:
165
- #
166
- # document.visible?(:no_free_space) #=> true
167
- #
168
- # Note: When you want to match text, consider using +I18n.t+ instead of
169
- # hard-coding the text, so that your tests don't break when the text changes.
170
- #
171
- # Finally, you may want to override the query language:
172
- #
173
- # class MyWidgetUsesXPath < CapybaraUI::Widget
174
- # root :xpath, '//some/node'
175
- # end
176
- def self.root(*selector, &block)
177
- @filter = NodeFilter.new(block || selector)
178
- end
179
-
180
- class MissingSelector < StandardError
181
- end
182
-
183
- def self.filter
184
- @filter || superclass.filter
185
- rescue NoMethodError
186
- raise MissingSelector, 'no selector defined'
187
- end
188
-
189
- def self.filter?
190
- filter rescue false
191
- end
192
-
193
- def self.selector
194
- filter.selector
195
- end
196
-
197
- def initialize(root)
198
- @root = root
199
- end
200
-
201
- # Clicks the current widget, or the child widget given by +name+.
202
- #
203
- # === Usage
204
- #
205
- # Given the following widget definition:
206
- #
207
- # class Container < CapybaraUI::Widget
208
- # root '#container'
209
- #
210
- # widget :link, 'a'
211
- # end
212
- #
213
- # Send +click+ with no arguments to trigger a +click+ event on +#container+.
214
- #
215
- # widget(:container).click
216
- #
217
- # This is the equivalent of doing the following using Capybara:
218
- #
219
- # find('#container').click
220
- #
221
- # Send +click :link+ to trigger a +click+ event on +a+:
222
- #
223
- # widget(:container).click :link
224
- #
225
- # This is the equivalent of doing the following using Capybara:
226
- #
227
- # find('#container a').click
228
- def click(*args)
229
- if args.empty?
230
- root.click
231
- else
232
- widget(*args).click
233
- end
234
- end
235
-
236
- # Hovers over the current widget, or the child widget given by +name+.
237
- #
238
- # === Usage
239
- #
240
- # Given the following widget definition:
241
- #
242
- # class Container < CapybaraUI::Widget
243
- # root '#container'
244
- #
245
- # widget :link, 'a'
246
- # end
247
- #
248
- # Send +hover+ with no arguments to trigger a +hover+ event on +#container+.
249
- #
250
- # widget(:container).hover
251
- #
252
- # This is the equivalent of doing the following using Capybara:
253
- #
254
- # find('#container').hover
255
- #
256
- # Send +hover :link+ to trigger a +hover+ event on +a+:
257
- #
258
- # widget(:container).hover :link
259
- #
260
- # This is the equivalent of doing the following using Capybara:
261
- #
262
- # find('#container a').hover
263
- def hover(*args)
264
- if args.empty?
265
- root.hover
266
- else
267
- widget(*args).hover
268
- end
269
- end
270
-
271
- # Double clicks the current widget, or the child widget given by +name+.
272
- #
273
- # === Usage
274
- #
275
- # Given the following widget definition:
276
- #
277
- # class Container < CapybaraUI::Widget
278
- # root '#container'
279
- #
280
- # widget :link, 'a'
281
- # end
282
- #
283
- # Send +double_click+ with no arguments to trigger an +ondblclick+ event on +#container+.
284
- #
285
- # widget(:container).double_click
286
- #
287
- # This is the equivalent of doing the following using Capybara:
288
- #
289
- # find('#container').double_click
290
- def double_click(*args)
291
- if args.empty?
292
- root.double_click
293
- else
294
- widget(*args).double_click
295
- end
296
- end
297
-
298
- # Right clicks the current widget, or the child widget given by +name+.
299
- #
300
- # === Usage
301
- #
302
- # Given the following widget definition:
303
- #
304
- # class Container < CapybaraUI::Widget
305
- # root '#container'
306
- #
307
- # widget :link, 'a'
308
- # end
309
- #
310
- # Send +right_click+ with no arguments to trigger an +oncontextmenu+ event on +#container+.
311
- #
312
- # widget(:container).right_click
313
- #
314
- # This is the equivalent of doing the following using Capybara:
315
- #
316
- # find('#container').right_click
317
- def right_click(*args)
318
- if args.empty?
319
- root.right_click
320
- else
321
- widget(*args).right_click
322
- end
323
- end
324
-
325
- # Determines if the widget underlying an action exists.
326
- #
327
- # @param name the name of the action
328
- #
329
- # @raise Missing if an action with +name+ can't be found.
330
- #
331
- # @return [Boolean] +true+ if the action widget is found, +false+
332
- # otherwise.
333
- def has_action?(name)
334
- raise Missing, "couldn't find `#{name}' action" unless respond_to?(name)
335
-
336
- visible?(:"#{name}_widget")
337
- end
338
-
339
- def id
340
- root['id']
341
- end
342
-
343
- def classes
344
- root['class'].split
345
- end
346
-
347
- # Determines if the widget has a specific class
348
- #
349
- # @param name the name of the class
350
- #
351
- # @return [Boolean] +true+ if the class is found, +false+ otherwise
352
- def class?(name)
353
- classes.include?(name)
354
- end
355
-
356
- def html
357
- xml = Nokogiri::HTML(page.body).at(root.path).to_xml
358
-
359
- Nokogiri::XML(xml, &:noblanks).to_xhtml.gsub("\n", "")
360
- end
361
-
362
- def text
363
- StringValue.new(root.text.strip)
364
- end
365
-
366
- # Converts this widget into a string representation suitable to be displayed
367
- # in a Cucumber table cell. By default calls #text.
368
- #
369
- # This method will be called by methods that build tables or rows (usually
370
- # #to_table or #to_row) so, in general, you won't call it directly, but feel
371
- # free to override it when needed.
372
- #
373
- # Returns a String.
374
- def to_cell
375
- text
376
- end
377
-
378
- def to_s
379
- text
380
- end
381
-
382
- def value
383
- text
384
- end
385
-
386
- private
387
-
388
- def page
389
- Capybara.current_session
390
- end
391
- end
392
- end