cucumber-salad 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: