forme 1.9.0 → 2.0.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.
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