capybara-ui 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/lib/capybara-ui.rb +31 -0
  3. data/lib/capybara-ui/assertions.rb +19 -0
  4. data/lib/capybara-ui/capybara.rb +7 -0
  5. data/lib/capybara-ui/checkpoint.rb +111 -0
  6. data/lib/capybara-ui/conversions.rb +31 -0
  7. data/lib/capybara-ui/cucumber.rb +5 -0
  8. data/lib/capybara-ui/dsl.rb +107 -0
  9. data/lib/capybara-ui/instance_conversions.rb +19 -0
  10. data/lib/capybara-ui/matchers.rb +28 -0
  11. data/lib/capybara-ui/optional_dependencies.rb +5 -0
  12. data/lib/capybara-ui/rails.rb +5 -0
  13. data/lib/capybara-ui/rails/role.rb +9 -0
  14. data/lib/capybara-ui/role.rb +19 -0
  15. data/lib/capybara-ui/text_table.rb +107 -0
  16. data/lib/capybara-ui/text_table/cell_text.rb +7 -0
  17. data/lib/capybara-ui/text_table/mapping.rb +40 -0
  18. data/lib/capybara-ui/text_table/transformations.rb +13 -0
  19. data/lib/capybara-ui/text_table/void_mapping.rb +8 -0
  20. data/lib/capybara-ui/version.rb +3 -0
  21. data/lib/capybara-ui/widgets.rb +61 -0
  22. data/lib/capybara-ui/widgets/check_box.rb +26 -0
  23. data/lib/capybara-ui/widgets/cucumber_methods.rb +73 -0
  24. data/lib/capybara-ui/widgets/document.rb +19 -0
  25. data/lib/capybara-ui/widgets/dsl.rb +47 -0
  26. data/lib/capybara-ui/widgets/field.rb +22 -0
  27. data/lib/capybara-ui/widgets/field_group.rb +329 -0
  28. data/lib/capybara-ui/widgets/form.rb +26 -0
  29. data/lib/capybara-ui/widgets/list.rb +200 -0
  30. data/lib/capybara-ui/widgets/list_item.rb +22 -0
  31. data/lib/capybara-ui/widgets/parts/container.rb +46 -0
  32. data/lib/capybara-ui/widgets/parts/struct.rb +117 -0
  33. data/lib/capybara-ui/widgets/radio_button.rb +62 -0
  34. data/lib/capybara-ui/widgets/select.rb +57 -0
  35. data/lib/capybara-ui/widgets/string_value.rb +43 -0
  36. data/lib/capybara-ui/widgets/table.rb +76 -0
  37. data/lib/capybara-ui/widgets/text_field.rb +27 -0
  38. data/lib/capybara-ui/widgets/widget.rb +392 -0
  39. data/lib/capybara-ui/widgets/widget/node_filter.rb +48 -0
  40. data/lib/capybara-ui/widgets/widget_class.rb +11 -0
  41. data/lib/capybara-ui/widgets/widget_name.rb +56 -0
  42. metadata +240 -0
@@ -0,0 +1,7 @@
1
+ module CapybaraUI
2
+ class TextTable
3
+ class CellText < String
4
+ include InstanceConversions
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,40 @@
1
+ module CapybaraUI
2
+ class TextTable
3
+ class Mapping
4
+ def initialize(settings = {})
5
+ self.key = settings[:key]
6
+ self.value_transformer = transformer(settings[:value_transformer], :pass)
7
+ self.key_transformer = transformer(settings[:key_transformer], :keyword)
8
+ end
9
+
10
+ def set(instance, row, key, value)
11
+ row[transform_key(instance, key)] = transform_value(instance, value)
12
+ end
13
+
14
+ private
15
+
16
+ attr_accessor :key, :value_transformer, :key_transformer
17
+
18
+ def transform_key(_, k)
19
+ key || key_transformer.(k)
20
+ end
21
+
22
+ def transform_value(instance, value)
23
+ instance.instance_exec(value, &value_transformer)
24
+ end
25
+
26
+ def transformer(set, fallback)
27
+ case set
28
+ when Symbol
29
+ Transformations.send(set)
30
+ when Proc
31
+ set
32
+ when nil
33
+ Transformations.send(fallback)
34
+ else
35
+ raise ArgumentError, "can't convert #{set.inspect} to transformer"
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,13 @@
1
+ module CapybaraUI
2
+ class TextTable
3
+ module Transformations
4
+ def self.keyword
5
+ ->(val) { val.squeeze(' ').strip.gsub(' ', '_').sub(/\?$/, '').to_sym }
6
+ end
7
+
8
+ def self.pass
9
+ ->(val) { val }
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,8 @@
1
+ module CapybaraUI
2
+ class TextTable
3
+ class VoidMapping
4
+ def set(*)
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ module CapybaraUI
2
+ VERSION = '0.10.0'
3
+ end
@@ -0,0 +1,61 @@
1
+ # This file describes the organization of the major Widget API components.
2
+ #
3
+ # === Parts
4
+ #
5
+ # Widget parts encapsulate the set of behaviours that constitute a widget.
6
+ module CapybaraUI
7
+ module Constructors
8
+ def Widget(*selector, &block)
9
+ if block_given?
10
+ WidgetClass.new(selector.flatten) do
11
+ define_method :value do
12
+ block.call(text)
13
+ end
14
+ end
15
+ else
16
+ WidgetClass.new(selector.flatten)
17
+ end
18
+ end
19
+
20
+ alias_method :String, :Widget
21
+
22
+ def Integer(*selector)
23
+ Widget(selector) { |text| Kernel::Integer(text) }
24
+ end
25
+
26
+ require 'bigdecimal'
27
+
28
+ def Decimal(*selector)
29
+ Widget(selector) { |text|
30
+ # ensure we can convert to float first
31
+ Float(text) && BigDecimal.new(text)
32
+ }
33
+ end
34
+ end
35
+
36
+ extend Constructors
37
+ end
38
+
39
+ module CapybaraUI::WidgetParts; end
40
+
41
+ require 'capybara-ui/widgets/parts/struct'
42
+ require 'capybara-ui/widgets/parts/container'
43
+
44
+ require 'capybara-ui/widgets/cucumber_methods'
45
+ require 'capybara-ui/widgets/dsl'
46
+ require 'capybara-ui/widgets/widget_class'
47
+ require 'capybara-ui/widgets/widget_name'
48
+ require 'capybara-ui/widgets/widget'
49
+ require 'capybara-ui/widgets/widget/node_filter'
50
+ require 'capybara-ui/widgets/list_item'
51
+ require 'capybara-ui/widgets/list'
52
+ require 'capybara-ui/widgets/field'
53
+ require 'capybara-ui/widgets/check_box'
54
+ require 'capybara-ui/widgets/radio_button'
55
+ require 'capybara-ui/widgets/select'
56
+ require 'capybara-ui/widgets/text_field'
57
+ require 'capybara-ui/widgets/field_group'
58
+ require 'capybara-ui/widgets/form'
59
+ require 'capybara-ui/widgets/document'
60
+ require 'capybara-ui/widgets/table'
61
+ require 'capybara-ui/widgets/string_value'
@@ -0,0 +1,26 @@
1
+ module CapybaraUI
2
+ # A check box.
3
+ class CheckBox < Field
4
+ # @!method set(value)
5
+ # Checks or unchecks the current checkbox.
6
+ #
7
+ # @param value [Boolean] +true+ to check the checkbox, +false+
8
+ # otherwise.
9
+ def_delegator :root, :set
10
+
11
+ # @return [Boolean] +true+ if the checkbox is checked, +false+
12
+ # otherwise.
13
+ def get
14
+ !! root.checked?
15
+ end
16
+
17
+ def to_cell
18
+ to_s
19
+ end
20
+
21
+ # @return +"yes"+ if the checkbox is checked, +"no"+ otherwise.
22
+ def to_s
23
+ get ? 'yes' : 'no'
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,73 @@
1
+ module CapybaraUI
2
+ module CucumberMethods
3
+ begin
4
+ require 'cucumber/ast/table'
5
+
6
+ # Compares this widget with the given Cucumber +table+.
7
+ #
8
+ # === Example
9
+ #
10
+ # Then(/^some step that takes in a cucumber table$/) do |table|
11
+ # widget(:my_widget).diff table
12
+ # end
13
+ #
14
+ # Pass +ignore_case: true+, for a case-insensitive table match
15
+ #
16
+ # === Example
17
+ #
18
+ # Then(/^some step that takes in a cucumber table$/) do |table|
19
+ # widget(:my_widget).diff table, ignore_case: true
20
+ # end
21
+ #
22
+ def diff(table, wait_time = Capybara.default_max_wait_time, ignore_case: false)
23
+ to_table = self.to_table
24
+
25
+ if ignore_case == true
26
+ table = downcase_table(table)
27
+ to_table = downcase_array(to_table)
28
+ end
29
+
30
+ table.diff!(to_table) || true
31
+ end
32
+
33
+ private
34
+
35
+ def downcase_table(table)
36
+ new_cucumber_table downcase_array(table.raw)
37
+ end
38
+
39
+ def downcase_array(array)
40
+ array.map do |item|
41
+ case item
42
+ when String
43
+ item.downcase
44
+ when Array
45
+ downcase_array(item)
46
+ when Hash
47
+ downcase_hash(item)
48
+ end
49
+ end
50
+ end
51
+
52
+ def downcase_hash(hash)
53
+ hash.each do |k, v|
54
+ case v
55
+ when String
56
+ hash[k] = v.downcase
57
+ when Array
58
+ downcase_array(v)
59
+ when Hash
60
+ downcase_hash(v)
61
+ end
62
+ end
63
+ end
64
+
65
+ def new_cucumber_table(table)
66
+ Cucumber::Ast::Table.new(table)
67
+ end
68
+
69
+ rescue LoadError
70
+ # *crickets*
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,19 @@
1
+ module CapybaraUI
2
+ class Document
3
+ include WidgetParts::Container
4
+
5
+ def initialize(widget_lookup_scope)
6
+ self.widget_lookup_scope = widget_lookup_scope or raise 'No scope given'
7
+ end
8
+
9
+ def root
10
+ Capybara.current_session
11
+ end
12
+
13
+ def body
14
+ xml = Nokogiri::HTML(Capybara.page.body).to_xml
15
+
16
+ Nokogiri::XML(xml, &:noblanks).to_xhtml
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,47 @@
1
+ module CapybaraUI
2
+ module Widgets
3
+ module DSL
4
+ # Declares a new form widget.
5
+ #
6
+ # See features/role.feature.
7
+ def form(name, *rest, &block)
8
+ widget name, *rest, CapybaraUI::Form, &block
9
+ end
10
+
11
+ # Declares a new list widget.
12
+ #
13
+ # See features/roles/list.feature.
14
+ def list(name, *rest, &block)
15
+ widget name, *rest, CapybaraUI::List, &block
16
+ end
17
+
18
+ # Declares a new child widget.
19
+ #
20
+ # See https://github.com/mojotech/capybara-ui/blob/master/features/roles/widget.feature
21
+ def widget(name, selector_or_parent, parent = Widget, &block)
22
+ raise ArgumentError, "`#{name}' is a reserved name" \
23
+ if WidgetParts::Container.instance_methods.include?(name.to_sym)
24
+
25
+ case selector_or_parent
26
+ when String, Array, Proc
27
+ selector, type = selector_or_parent, parent
28
+ when Class
29
+ selector, type = selector_or_parent.selector, selector_or_parent
30
+ else
31
+ raise ArgumentError, "wrong number of arguments (#{rest.size} for 1)"
32
+ end
33
+
34
+ raise TypeError, "can't convert `#{type}' to Widget" \
35
+ unless type.methods.include?(:selector)
36
+
37
+ raise ArgumentError, "missing selector" unless selector || type.selector
38
+
39
+ child = WidgetClass.new(selector, type, &block)
40
+
41
+ const_set(CapybaraUI::WidgetName.new(name).to_sym, child)
42
+
43
+ child
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,22 @@
1
+ module CapybaraUI
2
+ # A form field.
3
+ class Field < Widget
4
+ def self.root(selector)
5
+ super String === selector ? [:field, selector] : selector
6
+ end
7
+
8
+ # @return This field's value.
9
+ #
10
+ # Override this to get the actual value.
11
+ def get
12
+ raise NotImplementedError
13
+ end
14
+
15
+ # Sets the field value.
16
+ #
17
+ # Override this to set the value.
18
+ def set(value)
19
+ raise NotImplementedError
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,329 @@
1
+ module CapybaraUI
2
+ # A group of form fields.
3
+ #
4
+ # @todo Explain how to use locators when defining fields, including what
5
+ # happens when locators are omitted.
6
+ class FieldGroup < Widget
7
+ root 'fieldset'
8
+
9
+ def self.default_locator(type = nil, &block)
10
+ alias_method :name_to_locator, type if type
11
+
12
+ define_method :name_to_locator, &block if block
13
+ end
14
+
15
+ # The names of all the fields that belong to this field group.
16
+ #
17
+ # Field names are automatically added to this group as long as you use
18
+ # the field definition macros.
19
+ #
20
+ # @return [Set] the field names.
21
+ #
22
+ # @see field
23
+ def self.field_names
24
+ @field_names ||= Set.new
25
+ end
26
+
27
+ # @!group Field definition macros
28
+
29
+ # Creates a new checkbox accessor.
30
+ #
31
+ # Adds the following methods to the widget:
32
+ #
33
+ # <name>:: Gets the current checkbox state, as a boolean. Returns +true+
34
+ # if the corresponding check box is checked, +false+ otherwise.
35
+ # <name>=:: Sets the current checkbox state. Pass +true+ to check the
36
+ # checkbox, +false+ otherwise.
37
+ #
38
+ # @example
39
+ # # Given the following HTML:
40
+ # #
41
+ # # <form>
42
+ # # <p>
43
+ # # <label for="checked-box">
44
+ # # <input type="checkbox" value="1" id="checked-box" checked>
45
+ # # </p>
46
+ # # <p>
47
+ # # <label for="unchecked-box">
48
+ # # <input type="checkbox" value="1" id="unchecked-box">
49
+ # # </p>
50
+ # # </form>
51
+ # class MyFieldGroup < CapybaraUI::FieldGroup
52
+ # root 'form'
53
+ #
54
+ # check_box :checked_box, 'checked-box'
55
+ # check_box :unchecked_box, 'unchecked-box'
56
+ # end
57
+ #
58
+ # form = widget(:my_field_group)
59
+ #
60
+ # form.checked_box #=> true
61
+ # form.unchecked_box #=> false
62
+ #
63
+ # form.unchecked_box = true
64
+ # form.unchecked_box #=> true
65
+ #
66
+ # @param name the name of the checkbox accessor.
67
+ # @param locator the locator for the checkbox. If +nil+ the locator will
68
+ # be derived from +name+.
69
+ #
70
+ # @todo Handle checkbox access when the field is disabled (raise an
71
+ # exception?)
72
+ def self.check_box(name, locator = nil)
73
+ field name, locator, CheckBox
74
+ end
75
+
76
+ # Defines a new field.
77
+ #
78
+ # @param name the name of the field accessor.
79
+ # @param locator the field locator.
80
+ # @param type the field class name.
81
+ #
82
+ # @api private
83
+ def self.field(name, locator, type)
84
+ raise TypeError, "can't convert `#{name}' to Symbol" \
85
+ unless name.respond_to?(:to_sym)
86
+
87
+ field_names << name.to_sym
88
+
89
+ label = name.to_s.gsub(/_/, ' ').capitalize
90
+ locator ||= label
91
+
92
+ widget name, locator, type do
93
+ define_method :label do
94
+ label
95
+ end
96
+ end
97
+
98
+ define_method "#{name}=" do |val|
99
+ widget(name).set val
100
+ end
101
+
102
+ define_method name do
103
+ widget(name).get
104
+ end
105
+ end
106
+
107
+ # Creates a new select accessor.
108
+ #
109
+ # Adds the following methods to the widget:
110
+ #
111
+ # <name>:: Gets the text of the current selected option, or +nil+,
112
+ # if no option is selected.
113
+ # <name>_value:: Gets the value of the current selected option, or
114
+ # +nil+, if no option is selected.
115
+ # <name>=:: Selects an option on the current select. Pass the text or
116
+ # value of the option you want to select.
117
+ #
118
+ # @example
119
+ # # Given the following HTML:
120
+ # #
121
+ # # <form>
122
+ # # <p>
123
+ # # <label for="selected">
124
+ # # <select id="selected">
125
+ # # <option value ="1s" selected>Selected option</option>
126
+ # # <option value ="2s">Selected option two</option>
127
+ # # </select>
128
+ # # </p>
129
+ # # <p>
130
+ # # <label for="unselected">
131
+ # # <select id="unselected">
132
+ # # <option value="1u">Unselected option</option>
133
+ # # <option value="2u">Unselected option two</option>
134
+ # # </select>
135
+ # # </p>
136
+ # # </form>
137
+ # class MyFieldGroup < CapybaraUI::FieldGroup
138
+ # root 'form'
139
+ #
140
+ # select :selected, 'selected'
141
+ # select :unselected, 'unselected'
142
+ # end
143
+ #
144
+ # form = widget(:my_field_group)
145
+ #
146
+ # form.selected #=> "Selected option"
147
+ # form.selected_value #=> "1s"
148
+ #
149
+ # # Select by text
150
+ # form.unselected #=> nil
151
+ # form.unselected = "Unselected option"
152
+ # form.unselected #=> "Unselected option"
153
+ #
154
+ # # Select by value
155
+ # form.unselected = "2u"
156
+ # form.unselected #=> "Unselected option two"
157
+ # form.unselected_value #=> "2u"
158
+ #
159
+ # @param name the name of the select accessor.
160
+ # @param locator the locator for the select. If +nil+ the locator will
161
+ # be derived from +name+.
162
+ #
163
+ # @todo Handle select access when the field is disabled (raise an
164
+ # exception?)
165
+ # @todo Raise an exception when an option doesn't exist.
166
+ # @todo Allow passing the option value to set an option.
167
+ # @todo Ensure an option with no text returns the empty string.
168
+ # @todo What to do when +nil+ is passed to the writer?
169
+ def self.select(name, locator = nil)
170
+ field name, locator, Select
171
+
172
+ define_method "#{name}_value" do
173
+ widget(name).value
174
+ end
175
+ end
176
+
177
+ # Creates a new radio button group accessor.
178
+ #
179
+ # Adds the following methods to the widget:
180
+ #
181
+ # <name>:: Gets the text of the current checked button, or +nil+,
182
+ # if no button is checked.
183
+ # <name>_value:: Gets the value of the current checked button, or +nil+,
184
+ # if no button is checked.
185
+ # <name>=:: Checks a button in the current container. Pass the text of
186
+ # the label or the id or value of the button you want to choose.
187
+ #
188
+ # @example
189
+ # # Given the following HTML:
190
+ # #
191
+ # # <form>
192
+ # # <p class='checked'>
193
+ # # <label for="checked">Checked button</label>
194
+ # # <input type="radio" id="checked" name="c" value="checked_value" checked>
195
+ # # <label for="checked_two">Checked button two</label>
196
+ # # <input type="radio" id="checked_two" name="c" value="checked_two_value">
197
+ # # </p>
198
+ # # <p class='unchecked'>
199
+ # # <label for="unchecked">Unchecked button</label>
200
+ # # <input type="radio" id="unchecked" name="u" value="unchecked_value_one">
201
+ # # <label for="unchecked_two">Unchecked button two</label>
202
+ # # <input type="radio" id="unchecked_two" name="u" value="unchecked_value_two">
203
+ # # </p>
204
+ # # </form>
205
+ # class MyFieldGroup < CapybaraUI::FieldGroup
206
+ # root 'form'
207
+ #
208
+ # radio_button :checked, '.checked'
209
+ # radio_button :unchecked, '.unchecked'
210
+ # end
211
+ #
212
+ # form = widget(:my_field_group)
213
+ #
214
+ # form.checked #=> "Checked button"
215
+ # form.unchecked #=> nil
216
+ #
217
+ # form.unchecked = "Unchecked button" # Choose by label text
218
+ # form.unchecked #=> "Unchecked button"
219
+ # form.unchecked_value #=> "unchecked_value_one"
220
+ #
221
+ # form.unchecked = "unchecked_two" # Choose by id
222
+ # form.unchecked #=> "Unchecked button two"
223
+ # form.unchecked_value #=> "unchecked_value_two"
224
+ #
225
+ # form.unchecked = "unchecked_value_one" # Choose by value
226
+ # form.unchecked #=> "Unchecked button"
227
+ #
228
+ # @param name the name of the radio_button group accessor.
229
+ # @param locator the locator for the radio_button group.
230
+ #
231
+ def self.radio_button(name, locator = nil)
232
+ field name, locator, RadioButton
233
+
234
+ define_method "#{name}_value" do
235
+ widget(name).value
236
+ end
237
+ end
238
+
239
+ # Creates a new text field accessor.
240
+ #
241
+ # Adds the following methods to the widget:
242
+ #
243
+ # <name>:: Returns the current text field value, or +nil+ if no value
244
+ # has been set.
245
+ # <name>=:: Sets the current text field value.
246
+ # <name>? Returns +true+ if the current text field has content or
247
+ # +false+ otherwise
248
+ #
249
+ # @example
250
+ # # Given the following HTML:
251
+ # #
252
+ # # <form>
253
+ # # <p>
254
+ # # <label for="text-field">
255
+ # # <input type="text" value="Content" id="text-field">
256
+ # # </p>
257
+ # # <p>
258
+ # # <label for="empty-field">
259
+ # # <input type="text" id="empty-field">
260
+ # # </p>
261
+ # # </form>
262
+ # class MyFieldGroup < CapybaraUI::FieldGroup
263
+ # root 'form'
264
+ #
265
+ # text_field :filled_field, 'text-field'
266
+ # text_field :empty_field, 'empty-field'
267
+ # end
268
+ #
269
+ # form = widget(:my_field_group)
270
+ #
271
+ # form.filled_field #=> "Content"
272
+ # form.empty_field #=> nil
273
+ #
274
+ # form.filled_field? #=> true
275
+ # form.empty_field? #=> false
276
+ #
277
+ # form.empty_field = "Not anymore"
278
+ # form.empty_field #=> "Not anymore"
279
+ #
280
+ # @param name the name of the text field accessor.
281
+ # @param locator the locator for the text field. If +nil+ the locator
282
+ # will be derived from +name+.
283
+ #
284
+ # @todo Handle text field access when the field is disabled (raise an
285
+ # exception?)
286
+ def self.text_field(name, locator = nil)
287
+ define_method "#{name}?" do
288
+ widget(name).content?
289
+ end
290
+
291
+ field name, locator, TextField
292
+ end
293
+
294
+ # @!endgroup
295
+
296
+ # @return This field group's field widgets.
297
+ def fields
298
+ self.class.field_names.map { |name| widget(name) }
299
+ end
300
+
301
+ # Sets the given form attributes.
302
+ #
303
+ # @param attributes [Hash] the attributes and values we want to set.
304
+ #
305
+ # @return the current widget.
306
+ def set(attributes)
307
+ attributes.each do |k, v|
308
+ send "#{k}=", v
309
+ end
310
+
311
+ self
312
+ end
313
+
314
+ # Converts the current field group into a table suitable for diff'ing
315
+ # with Cucumber::Ast::Table.
316
+ #
317
+ # Field labels are determined by the widget name.
318
+ #
319
+ # Field values correspond to the return value of each field's +to_s+.
320
+ #
321
+ # @return [Array<Array>] the table.
322
+ def to_table
323
+ headers = fields.map { |field| field.label.downcase }
324
+ body = fields.map { |field| field.to_s.downcase }
325
+
326
+ [headers, body]
327
+ end
328
+ end
329
+ end