cucumber-salad 0.3.1 → 0.4.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.
@@ -1,4 +1,6 @@
1
1
  require 'chronic'
2
+ require 'nokogiri'
3
+ require 'capybara'
2
4
 
3
5
  require 'cucumber/salad/widget_container'
4
6
  require 'cucumber/salad/widget_macros'
@@ -15,4 +17,9 @@ require 'cucumber/salad/table/cell_text'
15
17
  require 'cucumber/salad/widgets/document'
16
18
  require 'cucumber/salad/dsl'
17
19
 
18
- class UnknownWidgetError < StandardError; end
20
+ module Cucumber
21
+ module Salad
22
+ # An exception that signals that something is missing.
23
+ class Missing < StandardError; end
24
+ end
25
+ end
@@ -1,5 +1,5 @@
1
1
  module Cucumber
2
2
  module Salad
3
- VERSION = "0.3.1"
3
+ VERSION = "0.4.0"
4
4
  end
5
5
  end
@@ -2,7 +2,7 @@ module Cucumber
2
2
  module Salad
3
3
  module WidgetContainer
4
4
  def has_widget?(name)
5
- widget_class(name).has_instance?(root)
5
+ widget_class(name).present_in?(root)
6
6
  end
7
7
 
8
8
  def widget(name, options = {})
@@ -22,8 +22,7 @@ module Cucumber
22
22
 
23
23
  const
24
24
  rescue NameError
25
- raise UnknownWidgetError,
26
- "couldn't find `#{@name}' widget in this scope"
25
+ raise Missing, "couldn't find `#{@name}' widget in this scope"
27
26
  end
28
27
 
29
28
  def to_sym
@@ -3,4 +3,5 @@ require 'cucumber/salad/widgets/list'
3
3
  require 'cucumber/salad/widgets/base_table'
4
4
  require 'cucumber/salad/widgets/auto_table'
5
5
  require 'cucumber/salad/widgets/table'
6
+ require 'cucumber/salad/widgets/field_group'
6
7
  require 'cucumber/salad/widgets/form'
@@ -0,0 +1,342 @@
1
+ module Cucumber
2
+ module Salad
3
+ module Widgets
4
+ # A group of form fields.
5
+ #
6
+ # @todo Explain how to use locators when defining fields, including what
7
+ # happens when locators are omitted.
8
+ class FieldGroup < Widget
9
+ root 'fieldset'
10
+
11
+ def self.default_locator(type = nil, &block)
12
+ alias_method :name_to_locator, type if type
13
+
14
+ define_method :name_to_locator, &block if block
15
+ end
16
+
17
+ # The names of all the fields that belong to this field group.
18
+ #
19
+ # Field names are automatically added to this group as long as you use
20
+ # the field definition macros.
21
+ #
22
+ # @return [Set] the field names.
23
+ #
24
+ # @see field
25
+ def self.field_names
26
+ @field_names ||= Set.new
27
+ end
28
+
29
+ # @!group Field definition macros
30
+
31
+ # Creates a new checkbox accessor.
32
+ #
33
+ # Adds the following methods to the widget:
34
+ #
35
+ # <name>:: Gets the current checkbox state, as a boolean. Returns +true+
36
+ # if the corresponding check box is checked, +false+ otherwise.
37
+ # <name>=:: Sets the current checkbox state. Pass +true+ to check the
38
+ # checkbox, +false+ otherwise.
39
+ #
40
+ # @example
41
+ # # Given the following HTML:
42
+ # #
43
+ # # <form>
44
+ # # <p>
45
+ # # <label for="checked-box">
46
+ # # <input type="checkbox" value="1" id="checked-box" checked>
47
+ # # </p>
48
+ # # <p>
49
+ # # <label for="unchecked-box">
50
+ # # <input type="checkbox" value="1" id="unchecked-box">
51
+ # # </p>
52
+ # # </form>
53
+ # class MyFieldGroup < Cucumber::Salad::Widgets::FieldGroup
54
+ # root 'form'
55
+ #
56
+ # check_box :checked_box, 'checked-box'
57
+ # check_box :unchecked_box, 'unchecked-box'
58
+ # end
59
+ #
60
+ # form = widget(:my_field_group)
61
+ #
62
+ # form.checked_box #=> true
63
+ # form.unchecked_box #=> false
64
+ #
65
+ # form.unchecked_box = true
66
+ # form.unchecked_box #=> true
67
+ #
68
+ # @param name the name of the checkbox accessor.
69
+ # @param locator the locator for the checkbox. If +nil+ the locator will
70
+ # be derived from +name+.
71
+ #
72
+ # @todo Handle checkbox access when the field is disabled (raise an
73
+ # exception?)
74
+ def self.check_box(name, locator = nil)
75
+ field name, locator || name_to_locator(name), CheckBox
76
+ end
77
+
78
+ # Defines a new field.
79
+ #
80
+ # @param name the name of the field accessor.
81
+ # @param locator the field locator.
82
+ # @param type the field class name.
83
+ #
84
+ # @api private
85
+ def self.field(name, locator, type)
86
+ raise TypeError, "can't convert `#{name}' to Symbol" \
87
+ unless name.respond_to?(:to_sym)
88
+
89
+ field_names << name.to_sym
90
+
91
+ widget name, locator, type do
92
+ define_method :label do
93
+ name.to_s.gsub(/_/, ' ').capitalize
94
+ end
95
+ end
96
+
97
+ define_method "#{name}=" do |val|
98
+ widget(name).set val
99
+ end
100
+
101
+ define_method name do
102
+ widget(name).get
103
+ end
104
+ end
105
+
106
+ # Creates a new select accessor.
107
+ #
108
+ # Adds the following methods to the widget:
109
+ #
110
+ # <name>:: Gets the current selected option. Returns the label of the
111
+ # selected option, or +nil+, if no option is selected.
112
+ # <name>=:: Selects an option on the current select. Pass the label of
113
+ # the option you want to select.
114
+ #
115
+ # @example
116
+ # # Given the following HTML:
117
+ # #
118
+ # # <form>
119
+ # # <p>
120
+ # # <label for="selected">
121
+ # # <select id="selected">
122
+ # # <option selected>Selected option</option>
123
+ # # <option>Another option</option>
124
+ # # </select>
125
+ # # </p>
126
+ # # <p>
127
+ # # <label for="deselected">
128
+ # # <select id="deselected">
129
+ # # <option>Deselected option</option>
130
+ # # <option>Another option</option>
131
+ # # </select>
132
+ # # </p>
133
+ # # </form>
134
+ # class MyFieldGroup < Cucumber::Salad::Widgets::FieldGroup
135
+ # root 'form'
136
+ #
137
+ # select :selected, 'selected'
138
+ # select :deselected, 'deselected'
139
+ # end
140
+ #
141
+ # form = widget(:my_field_group)
142
+ #
143
+ # form.selected #=> "Selected option"
144
+ # form.deselected #=> nil
145
+ #
146
+ # form.deselected = "Deselected option"
147
+ # form.unchecked_box #=> "Deselected option"
148
+ #
149
+ # @param name the name of the select accessor.
150
+ # @param locator the locator for the select. If +nil+ the locator will
151
+ # be derived from +name+.
152
+ #
153
+ # @todo Handle select access when the field is disabled (raise an
154
+ # exception?)
155
+ # @todo Raise an exception when an option doesn't exist.
156
+ # @todo Allow passing the option value to set an option.
157
+ # @todo Ensure an option with no text returns the empty string.
158
+ # @todo What to do when +nil+ is passed to the writer?
159
+ def self.select(name, locator = nil)
160
+ field name, locator || name_to_locator(name), Select
161
+ end
162
+
163
+ # Creates a new text field accessor.
164
+ #
165
+ # Adds the following methods to the widget:
166
+ #
167
+ # <name>:: Returns the current text field value, or +nil+ if no value
168
+ # has been set.
169
+ # <name>=:: Sets the current text field value.
170
+ #
171
+ # @example
172
+ # # Given the following HTML:
173
+ # #
174
+ # # <form>
175
+ # # <p>
176
+ # # <label for="text-field">
177
+ # # <input type="text" value="Content" id="text-field">
178
+ # # </p>
179
+ # # <p>
180
+ # # <label for="empty-field">
181
+ # # <input type="text" id="empty-field">
182
+ # # </p>
183
+ # # </form>
184
+ # class MyFieldGroup < Cucumber::Salad::Widgets::FieldGroup
185
+ # root 'form'
186
+ #
187
+ # text_field :filled_field, 'text-field'
188
+ # text_field :empty_field, 'empty-field'
189
+ # end
190
+ #
191
+ # form = widget(:my_field_group)
192
+ #
193
+ # form.filled_field #=> "Content"
194
+ # form.empty_field #=> nil
195
+ #
196
+ # form.empty_field = "Not anymore"
197
+ # form.empty_field #=> "Not anymore"
198
+ #
199
+ # @param name the name of the text field accessor.
200
+ # @param locator the locator for the text field. If +nil+ the locator
201
+ # will be derived from +name+.
202
+ #
203
+ # @todo Handle text field access when the field is disabled (raise an
204
+ # exception?)
205
+ def self.text_field(name, locator = nil)
206
+ field name, locator || name_to_locator(name), TextField
207
+ end
208
+
209
+ # @!endgroup
210
+
211
+ # @return This field group's field widgets.
212
+ def fields
213
+ self.class.field_names.map { |name| widget(name) }
214
+ end
215
+
216
+ # Sets the given form attributes.
217
+ #
218
+ # @param attributes [Hash] the attributes and values we want to set.
219
+ #
220
+ # @return the current widget.
221
+ def set(attributes)
222
+ attributes.each do |k, v|
223
+ send "#{k}=", v
224
+ end
225
+
226
+ self
227
+ end
228
+
229
+ # Converts the current field group into a table suitable for diff'ing
230
+ # with Cucumber::Ast::Table.
231
+ #
232
+ # Field labels are determined by the widget name.
233
+ #
234
+ # Field values correspond to the return value of each field's +to_s+.
235
+ #
236
+ # @return [Array<Array>] the table.
237
+ def to_table
238
+ headers = fields.map { |field| field.label.downcase }
239
+ body = fields.map { |field| field.to_s.downcase }
240
+
241
+ [headers, body]
242
+ end
243
+
244
+ # A form field.
245
+ class Field < Widget
246
+ def self.find_in(parent, options)
247
+ new({root: parent.find_field(selector)}.merge(options))
248
+ end
249
+
250
+ def self.present_in?(parent)
251
+ parent.has_field?(selector)
252
+ end
253
+
254
+ # @return This field's value.
255
+ #
256
+ # Override this to get the actual value.
257
+ def get
258
+ raise NotImplementedError
259
+ end
260
+
261
+ # Sets the field value.
262
+ #
263
+ # Override this to set the value.
264
+ def set(value)
265
+ raise NotImplementedError
266
+ end
267
+ end
268
+
269
+ # A check box.
270
+ class CheckBox < Field
271
+ # @!method set(value)
272
+ # Checks or unchecks the current checkbox.
273
+ #
274
+ # @param value [Boolean] +true+ to check the checkbox, +false+
275
+ # otherwise.
276
+ def_delegator :root, :set
277
+
278
+ # @return [Boolean] +true+ if the checkbox is checked, +false+
279
+ # otherwise.
280
+ def get
281
+ !! root.checked?
282
+ end
283
+
284
+ # @return +"yes"+ if the checkbox is checked, +"no"+ otherwise.
285
+ def to_s
286
+ get ? 'yes' : 'no'
287
+ end
288
+ end
289
+
290
+ # A select.
291
+ class Select < Field
292
+ # @return [String] The text of the selected option.
293
+ def get
294
+ option = root.find('[selected]') rescue nil
295
+
296
+ option && option.text
297
+ end
298
+
299
+ # Selects the given option.
300
+ #
301
+ # @param option [String] The text of the option to select.
302
+ def set(option)
303
+ root.find('option', text: option).select_option
304
+ end
305
+
306
+ # @!method to_s
307
+ # @return the text of the selected option, or the empty string if
308
+ # no option is selected.
309
+ def_delegator :get, :to_s
310
+ end
311
+
312
+ # A text field.
313
+ class TextField < Field
314
+ # @!method get
315
+ # @return The text field value.
316
+ def_delegator :root, :value, :get
317
+
318
+ # @!method set(value)
319
+ # Sets the text field value.
320
+ #
321
+ # @param value [String] the value to set.
322
+ def_delegator :root, :set
323
+
324
+ # @!method to_s
325
+ # @return the text field value, or the empty string if the field is
326
+ # empty.
327
+ def_delegator :get, :to_s
328
+ end
329
+
330
+ private
331
+
332
+ def label(name)
333
+ name.to_s.humanize
334
+ end
335
+
336
+ def name_to_locator(name)
337
+ label(name)
338
+ end
339
+ end
340
+ end
341
+ end
342
+ end
@@ -1,79 +1,18 @@
1
1
  module Cucumber
2
2
  module Salad
3
3
  module Widgets
4
- class Form < Widget
5
- def self.default_locator(type = nil, &block)
6
- alias_method :name_to_locator, type if type
7
-
8
- define_method :name_to_locator, &block if block
9
- end
10
-
11
- def self.check_box(name, label = nil)
12
- define_method "#{name}=" do |val|
13
- l = label || name_to_locator(name)
14
-
15
- if val
16
- root.check l
17
- else
18
- root.uncheck l
19
- end
20
- end
21
- end
22
-
23
- def self.select(name, *args)
24
- opts = args.extract_options!
25
- label, = args
26
-
27
- define_method "#{name}=" do |val|
28
- l = label || name_to_locator(name)
29
- w = opts.fetch(:writer) { ->(v) { v } }
30
-
31
- root.select w.(val).to_s, from: l
32
- end
33
- end
34
-
35
- def self.text_field(name, label = nil)
36
- define_method "#{name}=" do |val|
37
- l = label || name_to_locator(name)
38
-
39
- root.fill_in l, with: val.to_s
40
- end
41
- end
42
-
4
+ class Form < FieldGroup
43
5
  action :submit, '[type = submit]'
44
6
 
45
- # Sets the given form attributes.
46
- #
47
- # @param attributes [Hash] the attributes and values we want to set.
48
- #
49
- # @return the current widget.
50
- def set(attributes)
51
- attributes.each do |k, v|
52
- send "#{k}=", v
53
- end
54
-
55
- self
56
- end
57
-
58
7
  # Submit form with +attributes+.
59
8
  #
60
- # @param attrs [Hash] the form fields and their values
9
+ # @param attributes [Hash] the form fields and their values
61
10
  #
62
11
  # @return the current widget
63
12
  def submit_with(attributes)
64
13
  set attributes
65
14
  submit
66
15
  end
67
-
68
- private
69
-
70
- def label(name)
71
- name.to_s.humanize
72
- end
73
-
74
- def name_to_locator(name)
75
- label(name)
76
- end
77
16
  end
78
17
  end
79
18
  end
@@ -7,7 +7,13 @@ module Cucumber
7
7
 
8
8
  include WidgetContainer
9
9
 
10
- def self.has_instance?(parent_node)
10
+ # Determines if an instance of this widget class exists in
11
+ # +parent_node+.
12
+ #
13
+ # @param parent_node [Capybara::Node] the node we want to search in
14
+ #
15
+ # @return +true+ if a widget instance is found, +false+ otherwise.
16
+ def self.present_in?(parent_node)
11
17
  parent_node.has_selector?(selector)
12
18
  end
13
19
 
@@ -43,6 +49,20 @@ module Cucumber
43
49
  self.root = settings.fetch(:root)
44
50
  end
45
51
 
52
+ # Determines if the widget underlying an action exists.
53
+ #
54
+ # @param name the name of the action
55
+ #
56
+ # @raise Missing if an action with +name+ can't be found.
57
+ #
58
+ # @return [Boolean] +true+ if the action widget is found, +false+
59
+ # otherwise.
60
+ def has_action?(name)
61
+ raise Missing, "couldn't find `#{name}' action" unless respond_to?(name)
62
+
63
+ has_widget?(name)
64
+ end
65
+
46
66
  def inspect
47
67
  xml = Nokogiri::HTML(page.body).at(root.path).to_xml
48
68
 
@@ -50,6 +70,45 @@ module Cucumber
50
70
  Nokogiri::XML(xml, &:noblanks).to_xhtml
51
71
  end
52
72
 
73
+ class Reload < Capybara::ElementNotFound; end
74
+
75
+ # Reloads the widget, waiting for its contents to change (by default),
76
+ # or until +wait_time+ expires.
77
+ #
78
+ # Call this method to make sure a widget has enough time to update
79
+ # itself.
80
+ #
81
+ # You can pass a block to this method to control what it means for the
82
+ # widget to be reloaded.
83
+ #
84
+ # *Note: does not account for multiple changes to the widget yet.*
85
+ #
86
+ # @param wait_time [Numeric] how long we should wait for changes, in
87
+ # seconds.
88
+ #
89
+ # @yield A block that determines what it means for a widget to be
90
+ # reloaded.
91
+ # @yieldreturn [Boolean] +true+ if the widget is considered to be
92
+ # reloaded, +false+ otherwise.
93
+ #
94
+ # @return the current widget
95
+ def reload(wait_time = Capybara.default_wait_time, &test)
96
+ unless test
97
+ old_inspect = inspect
98
+ test = ->{ old_inspect != inspect }
99
+ end
100
+
101
+ root.synchronize(wait_time) do
102
+ raise Reload unless test.()
103
+ end
104
+
105
+ self
106
+ rescue Reload
107
+ # raised on timeout
108
+
109
+ self
110
+ end
111
+
53
112
  def to_s
54
113
  node_text(root)
55
114
  end
metadata CHANGED
@@ -2,14 +2,14 @@
2
2
  name: cucumber-salad
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.3.1
5
+ version: 0.4.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - David Leal
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-07-21 00:00:00.000000000 Z
12
+ date: 2013-07-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  version_requirements: !ruby/object:Gem::Requirement
@@ -62,18 +62,18 @@ dependencies:
62
62
  - !ruby/object:Gem::Dependency
63
63
  version_requirements: !ruby/object:Gem::Requirement
64
64
  requirements:
65
- - - ! '>='
65
+ - - ~>
66
66
  - !ruby/object:Gem::Version
67
- version: 2.11.0
67
+ version: 3.0.0
68
68
  none: false
69
- name: rspec
69
+ name: rspec-given
70
70
  type: :development
71
71
  prerelease: false
72
72
  requirement: !ruby/object:Gem::Requirement
73
73
  requirements:
74
- - - ! '>='
74
+ - - ~>
75
75
  - !ruby/object:Gem::Version
76
- version: 2.11.0
76
+ version: 3.0.0
77
77
  none: false
78
78
  - !ruby/object:Gem::Dependency
79
79
  version_requirements: !ruby/object:Gem::Requirement
@@ -91,6 +91,22 @@ dependencies:
91
91
  - !ruby/object:Gem::Version
92
92
  version: '0'
93
93
  none: false
94
+ - !ruby/object:Gem::Dependency
95
+ version_requirements: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ~>
98
+ - !ruby/object:Gem::Version
99
+ version: 1.0.0
100
+ none: false
101
+ name: capybara-webkit
102
+ type: :development
103
+ prerelease: false
104
+ requirement: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ~>
107
+ - !ruby/object:Gem::Version
108
+ version: 1.0.0
109
+ none: false
94
110
  description: See https://github.com/mojotech/cucumber-salad/README.md
95
111
  email:
96
112
  - dleal@mojotech.com
@@ -117,12 +133,14 @@ files:
117
133
  - lib/cucumber/salad/widgets/auto_table.rb
118
134
  - lib/cucumber/salad/widgets/base_table.rb
119
135
  - lib/cucumber/salad/widgets/document.rb
136
+ - lib/cucumber/salad/widgets/field_group.rb
120
137
  - lib/cucumber/salad/widgets/form.rb
121
138
  - lib/cucumber/salad/widgets/list.rb
122
139
  - lib/cucumber/salad/widgets/table.rb
123
140
  - lib/cucumber/salad/widgets/widget.rb
124
141
  homepage: https://github.com/mojotech/cucumber-salad
125
- licenses: []
142
+ licenses:
143
+ - MIT
126
144
  post_install_message:
127
145
  rdoc_options: []
128
146
  require_paths: