capybara 2.0.3 → 2.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data.tar.gz.sig +0 -0
  2. data/History.md +73 -0
  3. data/README.md +52 -5
  4. data/lib/capybara.rb +44 -62
  5. data/lib/capybara/cucumber.rb +4 -1
  6. data/lib/capybara/driver/base.rb +13 -9
  7. data/lib/capybara/driver/node.rb +18 -6
  8. data/lib/capybara/helpers.rb +111 -22
  9. data/lib/capybara/node/actions.rb +24 -19
  10. data/lib/capybara/node/base.rb +25 -32
  11. data/lib/capybara/node/document.rb +6 -2
  12. data/lib/capybara/node/element.rb +45 -7
  13. data/lib/capybara/node/finders.rb +48 -21
  14. data/lib/capybara/node/matchers.rb +38 -21
  15. data/lib/capybara/node/simple.rb +29 -7
  16. data/lib/capybara/query.rb +72 -26
  17. data/lib/capybara/rack_test/browser.rb +11 -3
  18. data/lib/capybara/rack_test/css_handlers.rb +8 -0
  19. data/lib/capybara/rack_test/driver.rb +11 -3
  20. data/lib/capybara/rack_test/form.rb +8 -1
  21. data/lib/capybara/rack_test/node.rb +92 -36
  22. data/lib/capybara/rails.rb +1 -2
  23. data/lib/capybara/result.rb +21 -37
  24. data/lib/capybara/rspec/matchers.rb +60 -31
  25. data/lib/capybara/selector.rb +46 -13
  26. data/lib/capybara/selenium/driver.rb +42 -6
  27. data/lib/capybara/selenium/node.rb +23 -5
  28. data/lib/capybara/session.rb +46 -11
  29. data/lib/capybara/spec/public/test.js +12 -0
  30. data/lib/capybara/spec/session/all_spec.rb +9 -8
  31. data/lib/capybara/spec/session/attach_file_spec.rb +14 -0
  32. data/lib/capybara/spec/session/check_spec.rb +14 -0
  33. data/lib/capybara/spec/session/choose_spec.rb +14 -0
  34. data/lib/capybara/spec/session/click_button_spec.rb +77 -1
  35. data/lib/capybara/spec/session/click_link_or_button_spec.rb +65 -0
  36. data/lib/capybara/spec/session/click_link_spec.rb +24 -0
  37. data/lib/capybara/spec/session/current_scope_spec.rb +29 -0
  38. data/lib/capybara/spec/session/fill_in_spec.rb +14 -0
  39. data/lib/capybara/spec/session/find_button_spec.rb +12 -0
  40. data/lib/capybara/spec/session/find_by_id_spec.rb +12 -1
  41. data/lib/capybara/spec/session/find_field_spec.rb +30 -0
  42. data/lib/capybara/spec/session/find_link_spec.rb +12 -0
  43. data/lib/capybara/spec/session/find_spec.rb +258 -16
  44. data/lib/capybara/spec/session/first_spec.rb +25 -8
  45. data/lib/capybara/spec/session/has_css_spec.rb +2 -2
  46. data/lib/capybara/spec/session/has_field_spec.rb +10 -2
  47. data/lib/capybara/spec/session/has_selector_spec.rb +9 -0
  48. data/lib/capybara/spec/session/has_text_spec.rb +96 -0
  49. data/lib/capybara/spec/session/has_title_spec.rb +47 -0
  50. data/lib/capybara/spec/session/node_spec.rb +43 -0
  51. data/lib/capybara/spec/session/reset_session_spec.rb +10 -2
  52. data/lib/capybara/spec/session/save_page_spec.rb +30 -0
  53. data/lib/capybara/spec/session/select_spec.rb +65 -0
  54. data/lib/capybara/spec/session/text_spec.rb +31 -0
  55. data/lib/capybara/spec/session/title_spec.rb +16 -0
  56. data/lib/capybara/spec/session/uncheck_spec.rb +14 -0
  57. data/lib/capybara/spec/session/unselect_spec.rb +42 -0
  58. data/lib/capybara/spec/session/visit_spec.rb +3 -3
  59. data/lib/capybara/spec/session/within_frame_spec.rb +14 -0
  60. data/lib/capybara/spec/session/within_spec.rb +3 -3
  61. data/lib/capybara/spec/spec_helper.rb +6 -1
  62. data/lib/capybara/spec/views/form.erb +39 -2
  63. data/lib/capybara/spec/views/frame_child.erb +9 -0
  64. data/lib/capybara/spec/views/frame_parent.erb +8 -0
  65. data/lib/capybara/spec/views/with_base_tag.erb +10 -0
  66. data/lib/capybara/spec/views/with_count.erb +7 -0
  67. data/lib/capybara/spec/views/with_hover.erb +17 -0
  68. data/lib/capybara/spec/views/with_html.erb +21 -2
  69. data/lib/capybara/spec/views/with_js.erb +5 -0
  70. data/lib/capybara/spec/views/with_scope.erb +6 -1
  71. data/lib/capybara/spec/views/with_title.erb +1 -0
  72. data/lib/capybara/spec/views/within_frames.erb +1 -0
  73. data/lib/capybara/version.rb +1 -1
  74. data/spec/basic_node_spec.rb +75 -24
  75. data/spec/dsl_spec.rb +2 -1
  76. data/spec/rack_test_spec.rb +4 -3
  77. data/spec/rspec/matchers_spec.rb +105 -17
  78. data/spec/server_spec.rb +8 -8
  79. data/spec/spec_helper.rb +2 -1
  80. metadata +83 -23
  81. metadata.gz.sig +0 -0
@@ -13,7 +13,7 @@ module Capybara
13
13
  # By default it will check if the expression occurs at least once,
14
14
  # but a different number can be specified.
15
15
  #
16
- # page.has_selector?('p#foo', :count => 4)
16
+ # page.has_selector?('p.foo', :count => 4)
17
17
  #
18
18
  # This will check if the expression occurs exactly 4 times.
19
19
  #
@@ -28,8 +28,12 @@ module Capybara
28
28
  # page.has_selector?(:xpath, XPath.descendant(:p))
29
29
  #
30
30
  # @param (see Capybara::Node::Finders#all)
31
- # @option options [Integer] :count (nil) Number of times the expression should occur
32
- # @return [Boolean] If the expression exists
31
+ # @param options a customizable set of options
32
+ # @option options [Integer] :count (nil) Number of times the text should occur
33
+ # @option options [Integer] :minimum (nil) Minimum number of times the text should occur
34
+ # @option options [Integer] :maximum (nil) Maximum number of times the text should occur
35
+ # @option options [Range] :between (nil) Range of times that should contain number of times text occurs
36
+ # @return [Boolean] If the expression exists
33
37
  #
34
38
  def has_selector?(*args)
35
39
  assert_selector(*args)
@@ -71,7 +75,7 @@ module Capybara
71
75
  #
72
76
  # page.assert_selector('li', :text => 'Horse', :visible => true)
73
77
  #
74
- # {assert_selector} can also accept XPath expressions generated by the
78
+ # `assert_selector` can also accept XPath expressions generated by the
75
79
  # XPath gem:
76
80
  #
77
81
  # page.assert_selector(:xpath, XPath.descendant(:p))
@@ -193,17 +197,26 @@ module Capybara
193
197
  # Checks if the page or current node has the given text content,
194
198
  # ignoring any HTML tags and normalizing whitespace.
195
199
  #
196
- # This only matches displayable text and specifically excludes text
197
- # contained within non-display nodes such as script or head tags.
200
+ # By default it will check if the text occurs at least once,
201
+ # but a different number can be specified.
202
+ #
203
+ # page.has_text?('lorem ipsum', between: 2..4)
204
+ #
205
+ # This will check if the text occurs from 2 to 4 times.
198
206
  #
199
- # @param [String] content The text to check for
200
- # @return [Boolean] Whether it exists
207
+ # @overload has_text?([type], text, [options])
208
+ # @param [:all, :visible] type Whether to check for only visible or all text
209
+ # @param [String, Regexp] text The text/regexp to check for
210
+ # @param [Hash] options additional options
211
+ # @option options [Integer] :count (nil) Number of times the text should occur
212
+ # @option options [Integer] :minimum (nil) Minimum number of times the text should occur
213
+ # @option options [Integer] :maximum (nil) Maximum number of times the text should occur
214
+ # @option options [Range] :between (nil) Range of times that should contain number of times text occurs
215
+ # @return [Boolean] Whether it exists
201
216
  #
202
- def has_text?(content)
217
+ def has_text?(*args)
203
218
  synchronize do
204
- unless Capybara::Helpers.normalize_whitespace(text).match(Capybara::Helpers.to_regexp(content))
205
- raise ExpectationNotMet
206
- end
219
+ raise ExpectationNotMet unless text_found?(*args)
207
220
  end
208
221
  return true
209
222
  rescue Capybara::ExpectationNotMet
@@ -216,17 +229,12 @@ module Capybara
216
229
  # Checks if the page or current node does not have the given text
217
230
  # content, ignoring any HTML tags and normalizing whitespace.
218
231
  #
219
- # This only matches displayable text and specifically excludes text
220
- # contained within non-display nodes such as script or head tags.
232
+ # @param (see #has_text?)
233
+ # @return [Boolean] Whether it doesn't exist
221
234
  #
222
- # @param [String] content The text to check for
223
- # @return [Boolean] Whether it doesn't exist
224
- #
225
- def has_no_text?(content)
235
+ def has_no_text?(*args)
226
236
  synchronize do
227
- if Capybara::Helpers.normalize_whitespace(text).match(Capybara::Helpers.to_regexp(content))
228
- raise ExpectationNotMet
229
- end
237
+ raise ExpectationNotMet if text_found?(*args)
230
238
  end
231
239
  return true
232
240
  rescue Capybara::ExpectationNotMet
@@ -299,6 +307,8 @@ module Capybara
299
307
  #
300
308
  # page.has_field?('Email', :type => 'email')
301
309
  #
310
+ # Note: 'textarea' and 'select' are valid type values, matching the associated tag names.
311
+ #
302
312
  # @param [String] locator The label, name or id of a field to check for
303
313
  # @option options [String] :with The text content of the field
304
314
  # @option options [String] :type The type attribute of the field
@@ -450,6 +460,13 @@ module Capybara
450
460
 
451
461
  private
452
462
 
463
+ def text_found?(*args)
464
+ type = args.shift if args.first.is_a?(Symbol) or args.first.nil?
465
+ content, options = args
466
+ count = Capybara::Helpers.normalize_whitespace(text(type)).scan(Capybara::Helpers.to_regexp(content)).count
467
+
468
+ Capybara::Helpers.matches_count?(count, options || {})
469
+ end
453
470
  end
454
471
  end
455
472
  end
@@ -26,7 +26,7 @@ module Capybara
26
26
  #
27
27
  # @return [String] The text of the element
28
28
  #
29
- def text
29
+ def text(type=nil)
30
30
  native.text
31
31
  end
32
32
 
@@ -108,6 +108,15 @@ module Capybara
108
108
  native[:checked]
109
109
  end
110
110
 
111
+ ##
112
+ #
113
+ # Whether or not the element is disabled.
114
+ #
115
+ # @return [Boolean] Whether the element is disabled
116
+ def disabled?
117
+ native[:disabled]
118
+ end
119
+
111
120
  ##
112
121
  #
113
122
  # Whether or not the element is selected.
@@ -118,7 +127,7 @@ module Capybara
118
127
  native[:selected]
119
128
  end
120
129
 
121
- def synchronize
130
+ def synchronize(seconds=nil)
122
131
  yield # simple nodes don't need to wait
123
132
  end
124
133
 
@@ -126,13 +135,26 @@ module Capybara
126
135
  # no op
127
136
  end
128
137
 
129
- def unsynchronized
130
- yield # simple nodes don't need to wait
138
+ def title
139
+ native.xpath("//title").first.text
131
140
  end
132
141
 
133
- def all(*args)
134
- query = Capybara::Query.new(*args)
135
- elements = native.xpath(query.xpath).map do |node|
142
+ def has_title?(content)
143
+ title.match(Capybara::Helpers.to_regexp(content))
144
+ end
145
+
146
+ def has_no_title?(content)
147
+ not has_title?(content)
148
+ end
149
+
150
+ private
151
+
152
+ def resolve_query(query, exact=nil)
153
+ elements = if query.selector.format == :css
154
+ native.css(query.css)
155
+ else
156
+ native.xpath(query.xpath(exact))
157
+ end.map do |node|
136
158
  self.class.new(node)
137
159
  end
138
160
  Capybara::Result.new(elements, query)
@@ -1,16 +1,13 @@
1
1
  module Capybara
2
2
  class Query
3
- attr_accessor :selector, :locator, :options, :xpath, :find, :negative
3
+ attr_accessor :selector, :locator, :options, :expression, :find, :negative
4
4
 
5
- VALID_KEYS = [:text, :visible, :between, :count, :maximum, :minimum]
5
+ VALID_KEYS = [:text, :visible, :between, :count, :maximum, :minimum, :exact, :match, :wait]
6
+ VALID_MATCH = [:first, :smart, :prefer_exact, :one]
6
7
 
7
8
  def initialize(*args)
8
9
  @options = if args.last.is_a?(Hash) then args.pop.dup else {} end
9
10
 
10
- unless options.has_key?(:visible)
11
- @options[:visible] = Capybara.ignore_hidden_elements
12
- end
13
-
14
11
  if args[0].is_a?(Symbol)
15
12
  @selector = Selector.all[args[0]]
16
13
  @locator = args[1]
@@ -20,8 +17,12 @@ module Capybara
20
17
  end
21
18
  @selector ||= Selector.all[Capybara.default_selector]
22
19
 
23
- @xpath = @selector.call(@locator).to_s
20
+ # for compatibility with Capybara 2.0
21
+ if Capybara.exact_options and @selector == Selector.all[:option]
22
+ @options[:exact] = true
23
+ end
24
24
 
25
+ @expression = @selector.call(@locator)
25
26
  assert_valid_keys!
26
27
  end
27
28
 
@@ -35,34 +36,76 @@ module Capybara
35
36
  end
36
37
 
37
38
  def matches_filters?(node)
38
- node.unsynchronized do
39
- if options[:text]
40
- regexp = options[:text].is_a?(Regexp) ? options[:text] : Regexp.escape(options[:text])
41
- return false if not node.text.match(regexp)
39
+ if options[:text]
40
+ regexp = options[:text].is_a?(Regexp) ? options[:text] : Regexp.escape(options[:text])
41
+ return false if not node.text(visible).match(regexp)
42
+ end
43
+ case visible
44
+ when :visible then return false unless node.visible?
45
+ when :hidden then return false if node.visible?
46
+ end
47
+ selector.custom_filters.each do |name, filter|
48
+ if options.has_key?(name)
49
+ return false unless filter.matches?(node, options[name])
50
+ elsif filter.default?
51
+ return false unless filter.matches?(node, filter.default)
42
52
  end
43
- return false if options[:visible] and not node.visible?
44
- selector.custom_filters.each do |name, block|
45
- return false if options.has_key?(name) and not block.call(node, options[name])
53
+ end
54
+ end
55
+
56
+ def visible
57
+ if options.has_key?(:visible)
58
+ case @options[:visible]
59
+ when true then :visible
60
+ when false then :all
61
+ else @options[:visible]
62
+ end
63
+ else
64
+ if Capybara.ignore_hidden_elements
65
+ :visible
66
+ else
67
+ :all
46
68
  end
47
- true
48
69
  end
49
70
  end
50
71
 
51
- def matches_count?(count)
52
- case
53
- when options[:between]
54
- options[:between] === count
55
- when options[:count]
56
- options[:count].to_i == count
57
- when options[:maximum]
58
- options[:maximum].to_i >= count
59
- when options[:minimum]
60
- options[:minimum].to_i <= count
72
+ def wait
73
+ if options.has_key?(:wait)
74
+ @options[:wait] or 0
61
75
  else
62
- count > 0
76
+ Capybara.default_wait_time
63
77
  end
64
78
  end
65
79
 
80
+ def exact?
81
+ if options.has_key?(:exact)
82
+ @options[:exact]
83
+ else
84
+ Capybara.exact
85
+ end
86
+ end
87
+
88
+ def match
89
+ if options.has_key?(:match)
90
+ @options[:match]
91
+ else
92
+ Capybara.match
93
+ end
94
+ end
95
+
96
+ def xpath(exact=nil)
97
+ exact = self.exact? if exact == nil
98
+ if @expression.respond_to?(:to_xpath) and exact
99
+ @expression.to_xpath(:exact)
100
+ else
101
+ @expression.to_s
102
+ end
103
+ end
104
+
105
+ def css
106
+ @expression
107
+ end
108
+
66
109
  private
67
110
 
68
111
  def assert_valid_keys!
@@ -73,6 +116,9 @@ module Capybara
73
116
  valid_names = valid_keys.map(&:inspect).join(", ")
74
117
  raise ArgumentError, "invalid keys #{invalid_names}, should be one of #{valid_names}"
75
118
  end
119
+ unless VALID_MATCH.include?(match)
120
+ raise ArgumentError, "invalid option #{match.inspect} for :match, should be one of #{VALID_MATCH.map(&:inspect).join(", ")}"
121
+ end
76
122
  end
77
123
  end
78
124
  end
@@ -80,8 +80,12 @@ class Capybara::RackTest::Browser
80
80
  @dom ||= Nokogiri::HTML(html)
81
81
  end
82
82
 
83
- def find(selector)
84
- dom.xpath(selector).map { |node| Capybara::RackTest::Node.new(self, node) }
83
+ def find(format, selector)
84
+ if format==:css
85
+ dom.css(selector, Capybara::RackTest::CSSHandlers.new)
86
+ else
87
+ dom.xpath(selector)
88
+ end.map { |node| Capybara::RackTest::Node.new(self, node) }
85
89
  end
86
90
 
87
91
  def html
@@ -89,7 +93,11 @@ class Capybara::RackTest::Browser
89
93
  rescue Rack::Test::Error
90
94
  ""
91
95
  end
92
-
96
+
97
+ def title
98
+ dom.xpath("//title").text
99
+ end
100
+
93
101
  protected
94
102
 
95
103
  def build_rack_mock_session
@@ -0,0 +1,8 @@
1
+ class Capybara::RackTest::CSSHandlers
2
+ def disabled list
3
+ list.find_all { |node| node.has_attribute? 'disabled' }
4
+ end
5
+ def enabled list
6
+ list.find_all { |node| !node.has_attribute? 'disabled' }
7
+ end
8
+ end
@@ -62,10 +62,14 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
62
62
  response.status
63
63
  end
64
64
 
65
- def find(selector)
66
- browser.find(selector)
65
+ def find_xpath(selector)
66
+ browser.find(:xpath, selector)
67
67
  end
68
-
68
+
69
+ def find_css(selector)
70
+ browser.find(:css,selector)
71
+ end
72
+
69
73
  def html
70
74
  browser.html
71
75
  end
@@ -73,6 +77,10 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
73
77
  def dom
74
78
  browser.dom
75
79
  end
80
+
81
+ def title
82
+ browser.title
83
+ end
76
84
 
77
85
  def reset!
78
86
  @browser = nil
@@ -17,7 +17,14 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node
17
17
  def params(button)
18
18
  params = {}
19
19
 
20
- native.xpath("(.//input|.//select|.//textarea)[not(@disabled)]").map do |field|
20
+ form_element_types=[:input, :select, :textarea]
21
+ form_elements_xpath=XPath.generate do |x|
22
+ xpath=x.descendant(*form_element_types).where(~x.attr(:form))
23
+ xpath=xpath.union(x.anywhere(*form_element_types).where(x.attr(:form) == native[:id])) if native[:id]
24
+ xpath.where(~x.attr(:disabled))
25
+ end.to_s
26
+
27
+ native.xpath(form_elements_xpath).map do |field|
21
28
  case field.name
22
29
  when 'input'
23
30
  if %w(radio checkbox).include? field['type']
@@ -1,5 +1,9 @@
1
1
  class Capybara::RackTest::Node < Capybara::Driver::Node
2
- def text
2
+ def all_text
3
+ Capybara::Helpers.normalize_whitespace(native.text)
4
+ end
5
+
6
+ def visible_text
3
7
  Capybara::Helpers.normalize_whitespace(unnormalized_text)
4
8
  end
5
9
 
@@ -15,42 +19,21 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
15
19
  if (Array === value) && !self[:multiple]
16
20
  raise ArgumentError.new "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}"
17
21
  end
18
- if tag_name == 'input' and type == 'radio'
19
- other_radios_xpath = XPath.generate { |x| x.anywhere(:input)[x.attr(:name).equals(self[:name])] }.to_s
20
- driver.dom.xpath(other_radios_xpath).each { |node| node.remove_attribute("checked") }
21
- native['checked'] = 'checked'
22
- elsif tag_name == 'input' and type == 'checkbox'
23
- if value && !native['checked']
24
- native['checked'] = 'checked'
25
- elsif !value && native['checked']
26
- native.remove_attribute('checked')
27
- end
28
- elsif tag_name == 'input'
29
- if (type == 'text' || type == 'password') && self[:maxlength] &&
30
- !self[:maxlength].empty?
31
- # Browser behavior for maxlength="0" is inconsistent, so we stick with
32
- # Firefox, allowing no input
33
- value = value[0...self[:maxlength].to_i]
34
- end
35
- if Array === value #Assert multiple attribute is present
36
- value.each do |v|
37
- new_native = native.clone
38
- new_native.remove_attribute('value')
39
- native.add_next_sibling(new_native)
40
- new_native['value'] = v.to_s
41
- end
42
- native.remove
43
- else
44
- native['value'] = value.to_s
45
- end
46
- elsif tag_name == "textarea"
47
- native.content = value.to_s
22
+
23
+ if radio?
24
+ set_radio(value)
25
+ elsif checkbox?
26
+ set_checkbox(value)
27
+ elsif input_field?
28
+ set_input(value)
29
+ elsif textarea?
30
+ native.content = value.to_s unless self[:readonly]
48
31
  end
49
32
  end
50
33
 
51
34
  def select_option
52
35
  if select_node['multiple'] != 'multiple'
53
- select_node.find(".//option[@selected]").each { |node| node.native.remove_attribute("selected") }
36
+ select_node.find_xpath(".//option[@selected]").each { |node| node.native.remove_attribute("selected") }
54
37
  end
55
38
  native["selected"] = 'selected'
56
39
  end
@@ -89,14 +72,26 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
89
72
  string_node.selected?
90
73
  end
91
74
 
75
+ def disabled?
76
+ if %w(option optgroup).include? tag_name
77
+ string_node.disabled? || find_xpath("parent::*")[0].disabled?
78
+ else
79
+ string_node.disabled?
80
+ end
81
+ end
82
+
92
83
  def path
93
84
  native.path
94
85
  end
95
86
 
96
- def find(locator)
87
+ def find_xpath(locator)
97
88
  native.xpath(locator).map { |n| self.class.new(driver, n) }
98
89
  end
99
-
90
+
91
+ def find_css(locator)
92
+ native.css(locator, Capybara::RackTest::CSSHandlers.new).map { |n| self.class.new(driver, n) }
93
+ end
94
+
100
95
  def ==(other)
101
96
  native == other.native
102
97
  end
@@ -125,7 +120,7 @@ private
125
120
 
126
121
  # a reference to the select node if this is an option node
127
122
  def select_node
128
- find('./ancestor::select').first
123
+ find_xpath('./ancestor::select').first
129
124
  end
130
125
 
131
126
  def type
@@ -133,6 +128,67 @@ private
133
128
  end
134
129
 
135
130
  def form
136
- native.ancestors('form').first
131
+ if native[:form]
132
+ native.xpath("//form[@id='#{native[:form]}']").first
133
+ else
134
+ native.ancestors('form').first
135
+ end
136
+ end
137
+
138
+ def set_radio(value)
139
+ other_radios_xpath = XPath.generate { |x| x.anywhere(:input)[x.attr(:name).equals(self[:name])] }.to_s
140
+ driver.dom.xpath(other_radios_xpath).each { |node| node.remove_attribute("checked") }
141
+ native['checked'] = 'checked'
142
+ end
143
+
144
+ def set_checkbox(value)
145
+ if value && !native['checked']
146
+ native['checked'] = 'checked'
147
+ elsif !value && native['checked']
148
+ native.remove_attribute('checked')
149
+ end
150
+ end
151
+
152
+ def set_input(value)
153
+ if text_or_password? && attribute_is_not_blank?(:maxlength)
154
+ # Browser behavior for maxlength="0" is inconsistent, so we stick with
155
+ # Firefox, allowing no input
156
+ value = value[0...self[:maxlength].to_i]
157
+ end
158
+ if Array === value #Assert multiple attribute is present
159
+ value.each do |v|
160
+ new_native = native.clone
161
+ new_native.remove_attribute('value')
162
+ native.add_next_sibling(new_native)
163
+ new_native['value'] = v.to_s
164
+ end
165
+ native.remove
166
+ else
167
+ native['value'] = value.to_s unless self[:readonly]
168
+ end
169
+ end
170
+
171
+ def attribute_is_not_blank?(attribute)
172
+ self[attribute] && !self[attribute].empty?
173
+ end
174
+
175
+ def checkbox?
176
+ input_field? && type == 'checkbox'
177
+ end
178
+
179
+ def input_field?
180
+ tag_name == 'input'
181
+ end
182
+
183
+ def radio?
184
+ input_field? && type == 'radio'
185
+ end
186
+
187
+ def textarea?
188
+ tag_name == "textarea"
189
+ end
190
+
191
+ def text_or_password?
192
+ input_field? && (type == 'text' || type == 'password')
137
193
  end
138
194
  end