forme 1.9.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +70 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +396 -202
  5. data/lib/forme/bs3.rb +19 -5
  6. data/lib/forme/erb.rb +18 -16
  7. data/lib/forme/form.rb +151 -118
  8. data/lib/forme/input.rb +1 -1
  9. data/lib/forme/rails.rb +41 -72
  10. data/lib/forme/raw.rb +2 -2
  11. data/lib/forme/sinatra.rb +6 -2
  12. data/lib/forme/tag.rb +3 -12
  13. data/lib/forme/template.rb +118 -0
  14. data/lib/forme/transformers/error_handler.rb +46 -1
  15. data/lib/forme/transformers/formatter.rb +36 -35
  16. data/lib/forme/transformers/helper.rb +0 -1
  17. data/lib/forme/transformers/inputs_wrapper.rb +6 -6
  18. data/lib/forme/transformers/labeler.rb +19 -0
  19. data/lib/forme/transformers/wrapper.rb +1 -1
  20. data/lib/forme/version.rb +2 -2
  21. data/lib/forme.rb +15 -2
  22. data/lib/roda/plugins/forme.rb +1 -1
  23. data/lib/roda/plugins/forme_erubi_capture.rb +62 -0
  24. data/lib/roda/plugins/forme_route_csrf.rb +16 -20
  25. data/lib/roda/plugins/forme_set.rb +177 -0
  26. data/lib/sequel/plugins/forme.rb +42 -55
  27. data/lib/sequel/plugins/forme_i18n.rb +3 -1
  28. data/lib/sequel/plugins/forme_set.rb +50 -28
  29. data/spec/all.rb +1 -1
  30. data/spec/bs3_reference_spec.rb +18 -18
  31. data/spec/bs3_sequel_plugin_spec.rb +7 -7
  32. data/spec/bs3_spec.rb +23 -11
  33. data/spec/erb_helper.rb +73 -58
  34. data/spec/erubi_capture_helper.rb +202 -0
  35. data/spec/forme_spec.rb +80 -29
  36. data/spec/rails_integration_spec.rb +47 -24
  37. data/spec/roda_integration_spec.rb +459 -48
  38. data/spec/sequel_helper.rb +0 -1
  39. data/spec/sequel_i18n_helper.rb +1 -1
  40. data/spec/sequel_i18n_plugin_spec.rb +3 -2
  41. data/spec/sequel_plugin_spec.rb +25 -8
  42. data/spec/sequel_set_plugin_spec.rb +10 -3
  43. data/spec/shared_erb_specs.rb +75 -0
  44. data/spec/sinatra_integration_spec.rb +5 -6
  45. data/spec/spec_helper.rb +23 -5
  46. metadata +30 -8
  47. data/lib/forme/erb_form.rb +0 -74
@@ -0,0 +1,177 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'rack/utils'
4
+ require_relative '../../forme/template'
5
+
6
+ class Roda
7
+ module RodaPlugins
8
+ module FormeSet
9
+ # Require the forme_route_csrf plugin.
10
+ def self.load_dependencies(app, _ = nil)
11
+ app.plugin :forme_route_csrf
12
+ end
13
+
14
+ # Set the HMAC secret.
15
+ def self.configure(app, opts = OPTS, &block)
16
+ app.opts[:forme_set_hmac_secret] = opts[:secret] || app.opts[:forme_set_hmac_secret]
17
+
18
+ if block
19
+ app.send(:define_method, :_forme_set_handle_error, &block)
20
+ app.send(:private, :_forme_set_handle_error)
21
+ end
22
+ end
23
+
24
+ # Error class raised for invalid form submissions.
25
+ class Error < StandardError
26
+ end
27
+
28
+ # Map of error types to error messages
29
+ ERROR_MESSAGES = {
30
+ :missing_data=>"_forme_set_data parameter not submitted",
31
+ :missing_hmac=>"_forme_set_data_hmac parameter not submitted",
32
+ :hmac_mismatch=>"_forme_set_data_hmac does not match _forme_set_data",
33
+ :csrf_mismatch=>"_forme_set_data CSRF token does not match submitted CSRF token",
34
+ :missing_namespace=>"no content in expected namespace"
35
+ }.freeze
36
+
37
+ module InstanceMethods
38
+ # Return hash based on submitted parameters, with :values key
39
+ # being submitted values for the object, and :validations key
40
+ # being a hash of validation metadata for the object.
41
+ def forme_parse(obj)
42
+ h = _forme_parse(obj)
43
+
44
+ params = h.delete(:params)
45
+ columns = h.delete(:columns)
46
+ h[:validations] ||= {}
47
+
48
+ values = h[:values] = {}
49
+ columns.each do |col|
50
+ values[col.to_sym] = params[col]
51
+ end
52
+
53
+ h
54
+ end
55
+
56
+ # Set fields on the object based on submitted parameters, as
57
+ # well as validations for associated object values.
58
+ def forme_set(obj)
59
+ h = _forme_parse(obj)
60
+
61
+ obj.set_fields(h[:params], h[:columns])
62
+
63
+ if h[:validations]
64
+ obj.forme_validations.merge!(h[:validations])
65
+ end
66
+
67
+ if block_given?
68
+ yield h[:form_version], obj
69
+ end
70
+
71
+ obj
72
+ end
73
+
74
+ private
75
+
76
+ # Raise error with message based on type
77
+ def _forme_set_handle_error(type, _obj)
78
+ end
79
+
80
+ # Raise error with message based on type
81
+ def _forme_parse_error(type, obj)
82
+ _forme_set_handle_error(type, obj)
83
+ raise Error, ERROR_MESSAGES[type]
84
+ end
85
+
86
+ # Include a reference to the current scope to the form. This reference is needed
87
+ # to correctly construct the HMAC.
88
+ def _forme_form_options(obj, attr, opts)
89
+ super
90
+
91
+ opts[:_after] = lambda do |form|
92
+ if (obj = form.opts[:obj]) && obj.respond_to?(:forme_inputs) && (forme_inputs = obj.forme_inputs)
93
+ columns = []
94
+ valid_values = {}
95
+
96
+ forme_inputs.each do |field, input|
97
+ next unless col = obj.send(:forme_column_for_input, input)
98
+ col = col.to_s
99
+ columns << col
100
+
101
+ next unless validation = obj.send(:forme_validation_for_input, field, input)
102
+ validation[0] = validation[0].to_s
103
+ has_nil = false
104
+ validation[1] = validation[1].map do |v|
105
+ has_nil ||= v.nil?
106
+ v.to_s
107
+ end
108
+ validation[1] << nil if has_nil
109
+ valid_values[col] = validation
110
+ end
111
+
112
+ return if columns.empty?
113
+
114
+ data = {}
115
+ data['columns'] = columns
116
+ data['namespaces'] = form.opts[:namespace]
117
+ data['csrf'] = form.opts[:csrf]
118
+ data['valid_values'] = valid_values unless valid_values.empty?
119
+ data['form_version'] = form.opts[:form_version] if form.opts[:form_version]
120
+
121
+ data = data.to_json
122
+ form.tag(:input, :type=>:hidden, :name=>:_forme_set_data, :value=>data)
123
+ form.tag(:input, :type=>:hidden, :name=>:_forme_set_data_hmac, :value=>OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, self.class.opts[:forme_set_hmac_secret], data))
124
+ end
125
+ end
126
+ end
127
+
128
+ # Internals of forme_parse_hmac and forme_set_hmac.
129
+ def _forme_parse(obj)
130
+ params = request.params
131
+ return _forme_parse_error(:missing_data, obj) unless data = params['_forme_set_data']
132
+ return _forme_parse_error(:missing_hmac, obj) unless hmac = params['_forme_set_data_hmac']
133
+
134
+ data = data.to_s
135
+ hmac = hmac.to_s
136
+ actual = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, self.class.opts[:forme_set_hmac_secret], data)
137
+ unless Rack::Utils.secure_compare(hmac.ljust(64), actual) && hmac.length == actual.length
138
+ return _forme_parse_error(:hmac_mismatch, obj)
139
+ end
140
+
141
+ data = JSON.parse(data)
142
+ csrf_field, hmac_csrf_value = data['csrf']
143
+ if csrf_field
144
+ csrf_value = params[csrf_field].to_s
145
+ hmac_csrf_value = hmac_csrf_value.to_s
146
+ unless Rack::Utils.secure_compare(csrf_value.ljust(hmac_csrf_value.length), hmac_csrf_value) && csrf_value.length == hmac_csrf_value.length
147
+ return _forme_parse_error(:csrf_mismatch, obj)
148
+ end
149
+ end
150
+
151
+ namespaces = data['namespaces']
152
+ namespaces.each do |key|
153
+ return _forme_parse_error(:missing_namespace, obj) unless params = params[key]
154
+ end
155
+
156
+ if valid_values = data['valid_values']
157
+ validations = {}
158
+ valid_values.each do |col, (type, values)|
159
+ value = params[col]
160
+ valid = if type == "subset"
161
+ !value || (value - values).empty?
162
+ else # type == "include"
163
+ values.include?(value)
164
+ end
165
+
166
+ validations[col.to_sym] = [:valid, valid]
167
+ end
168
+ end
169
+
170
+ {:params=>params, :columns=>data["columns"], :validations=>validations, :form_version=>data['form_version']}
171
+ end
172
+ end
173
+ end
174
+
175
+ register_plugin(:forme_set, FormeSet)
176
+ end
177
+ end
@@ -1,7 +1,6 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- require 'forme'
4
- require 'thread'
3
+ require_relative '../../forme'
5
4
 
6
5
  module Sequel # :nodoc:
7
6
  module Plugins # :nodoc:
@@ -37,7 +36,8 @@ module Sequel # :nodoc:
37
36
  # of the association for the form's +obj+. Inside the block, calls to
38
37
  # the +input+ and +inputs+ methods for the receiver treat the associated
39
38
  # object as the recevier's +obj+, using name and id attributes that work
40
- # with the Sequel +nested_attributes+ plugin.
39
+ # with the Sequel +nested_attributes+ plugin. Returns the HTML generated by
40
+ # the subform.
41
41
  #
42
42
  # The following options are currently supported:
43
43
  # :inputs :: Automatically call +inputs+ with the given values. Using
@@ -59,45 +59,47 @@ module Sequel # :nodoc:
59
59
  # :skip_primary_key :: Skip adding a hidden primary key field for existing
60
60
  # objects.
61
61
  def subform(association, opts={}, &block)
62
- nested_obj = opts.has_key?(:obj) ? opts[:obj] : obj.send(association)
63
- ref = obj.class.association_reflection(association)
64
- multiple = ref.returns_array?
65
- grid = opts[:grid]
66
- ns = "#{association}_attributes"
67
-
68
- contents = proc do
69
- send(multiple ? :each_obj : :with_obj, nested_obj, ns) do |no, i|
70
- emit(input(ref.associated_class.primary_key, :type=>:hidden, :label=>nil, :wrapper=>nil)) unless no.new? || opts[:skip_primary_key]
71
- options = opts.dup
72
- if grid
73
- options.delete(:legend)
74
- else
75
- if options.has_key?(:legend)
76
- if options[:legend].respond_to?(:call)
77
- options[:legend] = multiple ? options[:legend].call(no, i) : options[:legend].call(no)
78
- end
62
+ content_added do
63
+ nested_obj = opts.has_key?(:obj) ? opts[:obj] : obj.send(association)
64
+ ref = obj.class.association_reflection(association)
65
+ multiple = ref.returns_array?
66
+ grid = opts[:grid]
67
+ ns = "#{association}_attributes"
68
+
69
+ contents = proc do
70
+ send(multiple ? :each_obj : :with_obj, nested_obj, ns) do |no, i|
71
+ input(ref.associated_class.primary_key, :type=>:hidden, :label=>nil, :wrapper=>nil) unless no.new? || opts[:skip_primary_key]
72
+ options = opts.dup
73
+ if grid
74
+ options.delete(:legend)
79
75
  else
80
- if multiple
81
- options[:legend] = humanize("#{obj.model.send(:singularize, association)} ##{i+1}")
76
+ if options.has_key?(:legend)
77
+ if options[:legend].respond_to?(:call)
78
+ options[:legend] = multiple ? options[:legend].call(no, i) : options[:legend].call(no)
79
+ end
82
80
  else
83
- options[:legend] = humanize(association)
81
+ if multiple
82
+ options[:legend] = humanize("#{obj.model.send(:singularize, association)} ##{i+1}")
83
+ else
84
+ options[:legend] = humanize(association)
85
+ end
84
86
  end
85
87
  end
88
+ options[:subform] = true
89
+
90
+ inputs(options[:inputs]||[], options, &block)
86
91
  end
87
- options[:subform] = true
88
- _inputs(options[:inputs]||[], options, &block)
92
+ end
93
+
94
+ if grid
95
+ labels = opts.fetch(:labels){opts[:inputs].map{|l, *| humanize(l)} if opts[:inputs]}
96
+ legend = opts.fetch(:legend){humanize(association)}
97
+ inputs_opts = opts[:inputs_opts] || {}
98
+ inputs(inputs_opts.merge(:inputs_wrapper=>:table, :nested_inputs_wrapper=>:tr, :wrapper=>:td, :labeler=>nil, :labels=>labels, :legend=>legend), &contents)
99
+ else
100
+ contents.call
89
101
  end
90
102
  end
91
-
92
- if grid
93
- labels = opts.fetch(:labels){opts[:inputs].map{|l, *| humanize(l)} if opts[:inputs]}
94
- legend = opts.fetch(:legend){humanize(association)}
95
- inputs_opts = opts[:inputs_opts] || {}
96
- inputs(inputs_opts.merge(:inputs_wrapper=>:table, :nested_inputs_wrapper=>:tr, :wrapper=>:td, :labeler=>nil, :labels=>labels, :legend=>legend), &contents)
97
- else
98
- contents.call
99
- end
100
- nil
101
103
  end
102
104
  end
103
105
 
@@ -192,6 +194,7 @@ module Sequel # :nodoc:
192
194
 
193
195
  # Set the error option correctly if the field contains errors
194
196
  def handle_errors(f)
197
+ return if opts.has_key?(:error)
195
198
  if e = obj.errors.on(f)
196
199
  opts[:error] = e.join(', ')
197
200
  end
@@ -346,7 +349,7 @@ module Sequel # :nodoc:
346
349
 
347
350
  # Delegate to the +form+.
348
351
  def humanize(s)
349
- form.humanize(s)
352
+ form.respond_to?(:humanize) ? form.humanize(s) : s.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize
350
353
  end
351
354
 
352
355
  # If the column allows +NULL+ values, use a three-valued select
@@ -451,29 +454,14 @@ module Sequel # :nodoc:
451
454
  end
452
455
  end
453
456
 
454
- # Helper module used for Sequel forms using ERB template integration. Necessary for
455
- # proper subform handling when using such forms with partials.
456
- module ERBSequelForm
457
- # Capture the inside of the inputs, injecting it into the template
458
- # if a block is given, or returning it as a string if not.
459
- def subform(*, &block)
460
- if block
461
- capture(block){super}
462
- else
463
- capture{super}
464
- end
465
- end
466
- end
467
- SinatraSequelForm = ERBSequelForm
468
-
469
457
  class Form < ::Forme::Form
470
458
  include SequelForm
471
459
  end
472
460
 
473
- module InstanceMethods
474
- MUTEX = Mutex.new
475
- FORM_CLASSES = {::Forme::Form=>Form}
461
+ MUTEX = Mutex.new
462
+ FORM_CLASSES = {::Forme::Form=>Form}
476
463
 
464
+ module InstanceMethods
477
465
  # Configure the +form+ with support for <tt>Sequel::Model</tt>
478
466
  # specific code, such as support for nested attributes.
479
467
  def forme_config(form)
@@ -485,7 +473,6 @@ module Sequel # :nodoc:
485
473
  unless klass = MUTEX.synchronize{FORM_CLASSES[base]}
486
474
  klass = Class.new(base)
487
475
  klass.send(:include, SequelForm)
488
- klass.send(:include, ERBSequelForm) if defined?(::Forme::ERB::Form) && base == ::Forme::ERB::Form
489
476
  MUTEX.synchronize{FORM_CLASSES[base] = klass}
490
477
  end
491
478
  klass
@@ -1,5 +1,7 @@
1
+ # frozen-string-literal: true
2
+
1
3
  require 'i18n'
2
- require 'sequel/plugins/forme'
4
+ require_relative 'forme'
3
5
 
4
6
  module Sequel # :nodoc:
5
7
  module Plugins # :nodoc:
@@ -3,7 +3,7 @@
3
3
  module Sequel # :nodoc:
4
4
  module Plugins # :nodoc:
5
5
  # The forme_set plugin makes the model instance keep track of which form
6
- # inputs have been added for it. It adds a forme_set method to handle
6
+ # inputs have been added for it. It adds a <tt>forme_set(params['model_name'])</tt> method to handle
7
7
  # the intake of submitted data from the form. For more complete control,
8
8
  # it also adds a forme_parse method that returns a hash of information that can be
9
9
  # used to modify and validate the object.
@@ -18,6 +18,7 @@ module Sequel # :nodoc:
18
18
  module InstanceMethods
19
19
  # Hash with column name symbol keys and Forme::SequelInput values
20
20
  def forme_inputs
21
+ return (@forme_inputs || {}) if frozen?
21
22
  @forme_inputs ||= {}
22
23
  end
23
24
 
@@ -25,12 +26,13 @@ module Sequel # :nodoc:
25
26
  # is a boolean flag, if true, the uploaded values should be a subset of the allowed values,
26
27
  # otherwise, there should be a single uploaded value that is a member of the allowed values.
27
28
  def forme_validations
29
+ return (@forme_validations || {}) if frozen?
28
30
  @forme_validations ||= {}
29
31
  end
30
32
 
31
33
  # Keep track of the inputs used.
32
34
  def forme_input(_form, field, _opts)
33
- forme_inputs[field] = super
35
+ frozen? ? super : (forme_inputs[field] = super)
34
36
  end
35
37
 
36
38
  # Given the hash of submitted parameters, return a hash containing information on how to
@@ -47,34 +49,11 @@ module Sequel # :nodoc:
47
49
  validations = hash[:validations] = {}
48
50
 
49
51
  forme_inputs.each do |field, input|
50
- opts = input.opts
51
- next if SKIP_FORMATTERS.include?(opts.fetch(:formatter){input.form_opts[:formatter]})
52
-
53
- if attr = opts[:attr]
54
- name = attr[:name] || attr['name']
55
- end
56
- name ||= opts[:name] || opts[:key] || next
57
-
58
- # Pull out last component of the name if there is one
59
- column = (name =~ /\[([^\[\]]+)\]\z/ ? $1 : name)
60
- column = column.to_s.sub(/\[\]\z/, '').to_sym
61
-
52
+ next unless column = forme_column_for_input(input)
62
53
  hash_values[column] = params[column] || params[column.to_s]
63
54
 
64
- next unless ref = model.association_reflection(field)
65
- next unless options = opts[:options]
66
-
67
- values = if opts[:text_method]
68
- value_method = opts[:value_method] || opts[:text_method]
69
- options.map(&value_method)
70
- else
71
- options.map{|obj| obj.is_a?(Array) ? obj.last : obj}
72
- end
73
-
74
- if ref[:type] == :many_to_one && !opts[:required]
75
- values << nil
76
- end
77
- validations[column] = [ref[:type] != :many_to_one ? :subset : :include, values]
55
+ next unless validation = forme_validation_for_input(field, input)
56
+ validations[column] = validation
78
57
  end
79
58
 
80
59
  hash
@@ -88,6 +67,7 @@ module Sequel # :nodoc:
88
67
  unless hash[:validations].empty?
89
68
  forme_validations.merge!(hash[:validations])
90
69
  end
70
+ nil
91
71
  end
92
72
 
93
73
  # Check associated values to ensure they match one of options in the form.
@@ -105,6 +85,8 @@ module Sequel # :nodoc:
105
85
  !value || (value - values).empty?
106
86
  when :include
107
87
  values.include?(value)
88
+ when :valid
89
+ values
108
90
  else
109
91
  raise Forme::Error, "invalid type used in forme_validations"
110
92
  end
@@ -115,6 +97,46 @@ module Sequel # :nodoc:
115
97
  end
116
98
  end
117
99
  end
100
+
101
+ private
102
+
103
+ # Return the model column name to use for the given form input.
104
+ def forme_column_for_input(input)
105
+ opts = input.opts
106
+ return if SKIP_FORMATTERS.include?(opts.fetch(:formatter){input.form_opts[:formatter]})
107
+
108
+ if attr = opts[:attr]
109
+ name = attr[:name] || attr['name']
110
+ end
111
+ return unless name ||= opts[:name] || opts[:key]
112
+
113
+ # Pull out last component of the name if there is one
114
+ column = name.to_s.chomp('[]')
115
+ if column =~ /\[([^\[\]]+)\]\z/
116
+ $1
117
+ else
118
+ column
119
+ end.to_sym
120
+ end
121
+
122
+ # Return the validation metadata to use for the given field name and form input.
123
+ def forme_validation_for_input(field, input)
124
+ return unless ref = model.association_reflection(field)
125
+ opts = input.opts
126
+ return unless options = opts[:options]
127
+
128
+ values = if opts[:text_method]
129
+ value_method = opts[:value_method] || opts[:text_method]
130
+ options.map(&value_method)
131
+ else
132
+ options.map{|obj| obj.is_a?(Array) ? obj.last : obj}
133
+ end
134
+
135
+ if ref[:type] == :many_to_one && !opts[:required]
136
+ values << nil
137
+ end
138
+ [ref[:type] != :many_to_one ? :subset : :include, values]
139
+ end
118
140
  end
119
141
  end
120
142
  end
data/spec/all.rb CHANGED
@@ -1,2 +1,2 @@
1
1
  $: << 'lib'
2
- Dir['./spec/*_spec.rb'].each{|f| require f}
2
+ Dir.new(File.dirname(__FILE__)).each{|f| require_relative f if f.end_with?('_spec.rb')}
@@ -1,6 +1,6 @@
1
- require File.join(File.dirname(File.expand_path(__FILE__)), 'spec_helper.rb')
2
- require File.join(File.dirname(File.expand_path(__FILE__)), 'sequel_helper.rb')
3
- require 'forme/bs3'
1
+ require_relative 'spec_helper'
2
+ require_relative 'sequel_helper'
3
+ require_relative '../lib/forme/bs3'
4
4
 
5
5
  describe "Forme Bootstrap3 (BS3) forms" do
6
6
  def sel(opts, s)
@@ -33,7 +33,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
33
33
  end
34
34
 
35
35
  it "should create an input[:text] tag with a label when ':id => bar' with an error" do
36
- @f.input(:text, :label=>"Name", :id=>'bar', :class=>'foo', :error=>'input-text-error').to_s.must_equal '<div class="form-group has-error"><label for="bar">Name</label> <input class="form-control foo" id="bar" type="text"/><span class="help-block with-errors">input-text-error</span></div>'
36
+ @f.input(:text, :label=>"Name", :id=>'bar', :class=>'foo', :error=>'input-text-error').to_s.must_equal '<div class="form-group has-error"><label for="bar">Name</label> <input aria-describedby="bar_error_message" aria-invalid="true" class="form-control foo" id="bar" type="text"/><span class="help-block with-errors">input-text-error</span></div>'
37
37
  end
38
38
 
39
39
  describe 'from a Sequel model' do
@@ -43,7 +43,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
43
43
 
44
44
  it "should correctly handle an input[:text] tag from Sequel model with error" do
45
45
  @ac.errors.add(:name, 'name not valid')
46
- @c.input(:name).to_s.must_equal '<div class="form-group has-error string"><label for="album_name">Name</label> <input class="form-control" id="album_name" maxlength="255" name="album[name]" type="text" value="c"/><span class="help-block with-errors">name not valid</span></div>'
46
+ @c.input(:name).to_s.must_equal '<div class="form-group has-error string"><label for="album_name">Name</label> <input aria-describedby="album_name_error_message" aria-invalid="true" class="form-control" id="album_name" maxlength="255" name="album[name]" type="text" value="c"/><span class="help-block with-errors">name not valid</span></div>'
47
47
  end
48
48
  end
49
49
  end
@@ -66,7 +66,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
66
66
  end
67
67
 
68
68
  it "should create an input[:password] tag with a label when ':id => bar' with an error" do
69
- @f.input(:password, :label=>"Name", :id=>'bar', :class=>'foo', :error=>'input-password-error').to_s.must_equal '<div class="form-group has-error"><label for="bar">Name</label> <input class="form-control foo" id="bar" type="password"/><span class="help-block with-errors">input-password-error</span></div>'
69
+ @f.input(:password, :label=>"Name", :id=>'bar', :class=>'foo', :error=>'input-password-error').to_s.must_equal '<div class="form-group has-error"><label for="bar">Name</label> <input aria-describedby="bar_error_message" aria-invalid="true" class="form-control foo" id="bar" type="password"/><span class="help-block with-errors">input-password-error</span></div>'
70
70
  end
71
71
  end
72
72
 
@@ -88,7 +88,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
88
88
  end
89
89
 
90
90
  it "should create an input[:email] tag with a label when ':id => bar' with an error" do
91
- @f.input(:email, :label=>"Name", :id=>'bar', :class=>'foo', :error=>'input-email-error').to_s.must_equal '<div class="form-group has-error"><label for="bar">Name</label> <input class="form-control foo" id="bar" type="email"/><span class="help-block with-errors">input-email-error</span></div>'
91
+ @f.input(:email, :label=>"Name", :id=>'bar', :class=>'foo', :error=>'input-email-error').to_s.must_equal '<div class="form-group has-error"><label for="bar">Name</label> <input aria-describedby="bar_error_message" aria-invalid="true" class="form-control foo" id="bar" type="email"/><span class="help-block with-errors">input-email-error</span></div>'
92
92
  end
93
93
  end
94
94
 
@@ -110,7 +110,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
110
110
  end
111
111
 
112
112
  it "should create an input[:file] tag with a label when ':id => bar' with an error" do
113
- @f.input(:file, :label=>"Name", :id=>'bar', :class=>'foo', :error=>'input-file-error').to_s.must_equal '<div class="form-group has-error"><label for="bar">Name</label> <input class="foo" id="bar" type="file"/><span class="help-block with-errors">input-file-error</span></div>'
113
+ @f.input(:file, :label=>"Name", :id=>'bar', :class=>'foo', :error=>'input-file-error').to_s.must_equal '<div class="form-group has-error"><label for="bar">Name</label> <input aria-describedby="bar_error_message" aria-invalid="true" class="foo" id="bar" type="file"/><span class="help-block with-errors">input-file-error</span></div>'
114
114
  end
115
115
  end
116
116
 
@@ -132,7 +132,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
132
132
  end
133
133
 
134
134
  it "should create an input[:submit] tag without error message " do
135
- @f.input(:submit, :value=>'Save', :id=>'foo', :error=>'error-message').to_s.must_equal '<input class="btn btn-default" id="foo" type="submit" value="Save"/>'
135
+ @f.input(:submit, :value=>'Save', :id=>'foo', :error=>'error-message').to_s.must_equal '<input aria-describedby="foo_error_message" aria-invalid="true" class="btn btn-default" id="foo" type="submit" value="Save"/>'
136
136
  end
137
137
  end
138
138
 
@@ -154,7 +154,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
154
154
  end
155
155
 
156
156
  it "should create an input[:reset] tag without error message " do
157
- @f.input(:reset, :value=>'Save', :id=>'foo', :error=>'error-message').to_s.must_equal '<input class="btn btn-default" id="foo" type="reset" value="Save"/>'
157
+ @f.input(:reset, :value=>'Save', :id=>'foo', :error=>'error-message').to_s.must_equal '<input aria-describedby="foo_error_message" aria-invalid="true" class="btn btn-default" id="foo" type="reset" value="Save"/>'
158
158
  end
159
159
  end
160
160
 
@@ -180,7 +180,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
180
180
  end
181
181
 
182
182
  it "should create a textarea tag with .has-error and error message when ':error => is-a-string'" do
183
- @f.input(:textarea, :error=>'input-textarea-error').to_s.must_equal '<div class="form-group has-error"><textarea class="form-control"></textarea><span class="help-block with-errors">input-textarea-error</span></div>'
183
+ @f.input(:textarea, :error=>'input-textarea-error').to_s.must_equal '<div class="form-group has-error"><textarea aria-invalid="true" class="form-control"></textarea><span class="help-block with-errors">input-textarea-error</span></div>'
184
184
  end
185
185
 
186
186
  describe 'from a Sequel model' do
@@ -190,7 +190,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
190
190
 
191
191
  it "should correctly handle an input[:text] tag from Sequel model with error" do
192
192
  @ac.errors.add(:name, 'name not valid')
193
- @c.input(:name, :as=>:textarea).to_s.must_equal '<div class="form-group has-error string"><label for="album_name">Name</label> <textarea class="form-control" id="album_name" maxlength="255" name="album[name]">c</textarea><span class="help-block with-errors">name not valid</span></div>'
193
+ @c.input(:name, :as=>:textarea).to_s.must_equal '<div class="form-group has-error string"><label for="album_name">Name</label> <textarea aria-describedby="album_name_error_message" aria-invalid="true" class="form-control" id="album_name" maxlength="255" name="album[name]">c</textarea><span class="help-block with-errors">name not valid</span></div>'
194
194
  end
195
195
  end
196
196
  end
@@ -225,7 +225,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
225
225
  end
226
226
 
227
227
  it "should create a select tag with .has-error and error message when ':error => is-a-string'" do
228
- @f.input(:select, :error=>'input-select-error').to_s.must_equal '<div class="form-group has-error"><select class="form-control"></select><span class="help-block with-errors">input-select-error</span></div>'
228
+ @f.input(:select, :error=>'input-select-error').to_s.must_equal '<div class="form-group has-error"><select aria-invalid="true" class="form-control"></select><span class="help-block with-errors">input-select-error</span></div>'
229
229
  end
230
230
 
231
231
  it "should correctly handle a Sequel model output :as => :select" do
@@ -234,7 +234,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
234
234
 
235
235
  it "should correctly handle a Sequel model output :as => :select with error" do
236
236
  @ac.errors.add(:artist, 'error message')
237
- @c.input(:artist, :as=>:select).to_s.must_equal '<div class="form-group has-error many_to_one"><label for="album_artist_id">Artist</label> <select class="form-control" id="album_artist_id" name="album[artist_id]"><option value=""></option><option value="1">a</option><option selected="selected" value="2">d</option></select><span class="help-block with-errors">error message</span></div>'
237
+ @c.input(:artist, :as=>:select).to_s.must_equal '<div class="form-group has-error many_to_one"><label for="album_artist_id">Artist</label> <select aria-describedby="album_artist_id_error_message" aria-invalid="true" class="form-control" id="album_artist_id" name="album[artist_id]"><option value=""></option><option value="1">a</option><option selected="selected" value="2">d</option></select><span class="help-block with-errors">error message</span></div>'
238
238
  end
239
239
 
240
240
  describe 'from a Sequel model' do
@@ -244,7 +244,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
244
244
 
245
245
  it "should correctly handle a boolean attribute from a Sequel model with an error" do
246
246
  @ac.errors.add(:gold, 'error message')
247
- @c.input(:gold).to_s.must_equal '<div class="boolean form-group has-error"><label for="album_gold">Gold</label> <select class="form-control" id="album_gold" name="album[gold]"><option value=""></option><option selected="selected" value="t">True</option><option value="f">False</option></select><span class="help-block with-errors">error message</span></div>'
247
+ @c.input(:gold).to_s.must_equal '<div class="boolean form-group has-error"><label for="album_gold">Gold</label> <select aria-describedby="album_gold_error_message" aria-invalid="true" class="form-control" id="album_gold" name="album[gold]"><option value=""></option><option selected="selected" value="t">True</option><option value="f">False</option></select><span class="help-block with-errors">error message</span></div>'
248
248
  end
249
249
  end
250
250
  end
@@ -267,7 +267,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
267
267
  end
268
268
 
269
269
  it "should create an input[:checkbox] tag with a label when ':id => bar' with an error" do
270
- @f.input(:checkbox, :label=>"Gold", :id=>'bar', :class=>'foo', :error=>'input-checkbox-error').to_s.must_equal '<div class="has-error"><div class="checkbox"><label for="bar"><input class="foo" id="bar" type="checkbox"/> Gold</label></div><span class="help-block with-errors">input-checkbox-error</span></div>'
270
+ @f.input(:checkbox, :label=>"Gold", :id=>'bar', :class=>'foo', :error=>'input-checkbox-error').to_s.must_equal '<div class="has-error"><div class="checkbox"><label for="bar"><input aria-describedby="bar_error_message" aria-invalid="true" class="foo" id="bar" type="checkbox"/> Gold</label></div><span class="help-block with-errors">input-checkbox-error</span></div>'
271
271
  end
272
272
 
273
273
  describe 'from a Sequel model' do
@@ -277,7 +277,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
277
277
 
278
278
  it "should correctly handle a boolean attribute ':as=>:checkbox' with an error" do
279
279
  @ac.errors.add(:gold, 'error message')
280
- @c.input(:gold, :as=>:checkbox).to_s.must_equal '<div class="boolean has-error"><div class="checkbox"><label for="album_gold"><input id="album_gold_hidden" name="album[gold]" type="hidden" value="f"/><input checked="checked" id="album_gold" name="album[gold]" type="checkbox" value="t"/> Gold</label></div><span class="help-block with-errors">error message</span></div>'
280
+ @c.input(:gold, :as=>:checkbox).to_s.must_equal '<div class="boolean has-error"><div class="checkbox"><label for="album_gold"><input id="album_gold_hidden" name="album[gold]" type="hidden" value="f"/><input aria-describedby="album_gold_error_message" aria-invalid="true" checked="checked" id="album_gold" name="album[gold]" type="checkbox" value="t"/> Gold</label></div><span class="help-block with-errors">error message</span></div>'
281
281
  end
282
282
  end
283
283
  end
@@ -301,7 +301,7 @@ describe "Forme Bootstrap3 (BS3) forms" do
301
301
  end
302
302
 
303
303
  it "should create an input[:radio] tag with a label when ':id => bar' with an error" do
304
- @f.input(:radio, :label=>"Gold", :id=>'bar', :class=>'foo', :error=>'input-radio-error').to_s.must_equal '<div class="has-error"><div class="radio"><label for="bar"><input class="foo" id="bar" type="radio"/> Gold</label></div><span class="help-block with-errors">input-radio-error</span></div>'
304
+ @f.input(:radio, :label=>"Gold", :id=>'bar', :class=>'foo', :error=>'input-radio-error').to_s.must_equal '<div class="has-error"><div class="radio"><label for="bar"><input aria-describedby="bar_error_message" aria-invalid="true" class="foo" id="bar" type="radio"/> Gold</label></div><span class="help-block with-errors">input-radio-error</span></div>'
305
305
  end
306
306
 
307
307
  describe 'from a Sequel model' do