formtastic 0.9.8 → 0.9.9
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.
- data/README.textile +3 -3
- data/generators/formtastic/templates/formtastic.rb +1 -1
- data/lib/formtastic.rb +76 -38
- data/lib/formtastic/i18n.rb +9 -5
- data/lib/formtastic/layout_helper.rb +1 -0
- data/lib/formtastic/util.rb +25 -0
- data/spec/commit_button_spec.rb +39 -42
- data/spec/errors_spec.rb +19 -0
- data/spec/i18n_spec.rb +33 -16
- data/spec/input_spec.rb +12 -11
- data/spec/inputs/check_boxes_input_spec.rb +55 -0
- data/spec/inputs/country_input_spec.rb +39 -5
- data/spec/inputs/date_input_spec.rb +3 -4
- data/spec/inputs/datetime_input_spec.rb +17 -4
- data/spec/inputs/select_input_spec.rb +33 -16
- data/spec/inputs/time_input_spec.rb +4 -4
- data/spec/inputs_spec.rb +0 -1
- data/spec/spec_helper.rb +19 -2
- metadata +4 -3
data/README.textile
CHANGED
@@ -206,7 +206,7 @@ When working in has many association, you can even supply @"%i"@ in your fieldse
|
|
206
206
|
</pre>
|
207
207
|
|
208
208
|
|
209
|
-
Customize HTML attributes for any input using the @:input_html@ option. Typically
|
209
|
+
Customize HTML attributes for any input using the @:input_html@ option. Typically this is used to disable the input, change the size of a text field, change the rows in a textarea, or even to add a special class to an input to attach special behavior like "autogrow":http://plugins.jquery.com/project/autogrow textareas:
|
210
210
|
|
211
211
|
<pre>
|
212
212
|
<% semantic_form_for @post do |form| %>
|
@@ -337,7 +337,7 @@ Formtastic supports localized *labels*, *hints*, *legends*, *actions* using the
|
|
337
337
|
title: "Choose a good title for you post."
|
338
338
|
body: "Write something inspiring here."
|
339
339
|
actions:
|
340
|
-
create: "Create my {
|
340
|
+
create: "Create my %{model}"
|
341
341
|
update: "Save changes"
|
342
342
|
dummie: "Launch!"
|
343
343
|
</pre>
|
@@ -354,7 +354,7 @@ Formtastic supports localized *labels*, *hints*, *legends*, *actions* using the
|
|
354
354
|
<%= form.input :section %> # => :label => I18n.t('activerecord.attributes.user.section') or 'Section'
|
355
355
|
<% end %>
|
356
356
|
<% form.buttons do %>
|
357
|
-
<%= form.commit_button %> # => "Create my {
|
357
|
+
<%= form.commit_button %> # => "Create my %{model}"
|
358
358
|
<% end %>
|
359
359
|
<% end %>
|
360
360
|
</pre>
|
@@ -28,7 +28,7 @@
|
|
28
28
|
# Formtastic::SemanticFormBuilder.inline_errors = :sentence
|
29
29
|
|
30
30
|
# Set the method to call on label text to transform or format it for human-friendly
|
31
|
-
# reading when formtastic is
|
31
|
+
# reading when formtastic is used without object. Defaults to :humanize.
|
32
32
|
# Formtastic::SemanticFormBuilder.label_str_method = :humanize
|
33
33
|
|
34
34
|
# Set the array of methods to try calling on parent objects in :select and :radio inputs
|
data/lib/formtastic.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
require File.join(File.dirname(__FILE__), *%w[formtastic i18n])
|
3
|
+
require File.join(File.dirname(__FILE__), *%w[formtastic util])
|
3
4
|
|
4
5
|
module Formtastic #:nodoc:
|
5
6
|
|
@@ -9,7 +10,7 @@ module Formtastic #:nodoc:
|
|
9
10
|
@@default_text_area_height = 20
|
10
11
|
@@all_fields_required_by_default = true
|
11
12
|
@@include_blank_for_select_by_default = true
|
12
|
-
@@required_string = proc { %{<abbr title="#{::Formtastic::I18n.t(:required)}">*</abbr>} }
|
13
|
+
@@required_string = proc { ::Formtastic::Util.html_safe(%{<abbr title="#{::Formtastic::I18n.t(:required)}">*</abbr>}) }
|
13
14
|
@@optional_string = ''
|
14
15
|
@@inline_errors = :sentence
|
15
16
|
@@label_str_method = :humanize
|
@@ -105,7 +106,7 @@ module Formtastic #:nodoc:
|
|
105
106
|
send(:"inline_#{type}_for", method, options)
|
106
107
|
end.compact.join("\n")
|
107
108
|
|
108
|
-
return template.content_tag(:li, list_item_content, wrapper_html)
|
109
|
+
return template.content_tag(:li, Formtastic::Util.html_safe(list_item_content), wrapper_html)
|
109
110
|
end
|
110
111
|
|
111
112
|
# Creates an input fieldset and ol tag wrapping for use around a set of inputs. It can be
|
@@ -342,7 +343,7 @@ module Formtastic #:nodoc:
|
|
342
343
|
element_class = ['commit', options.delete(:class)].compact.join(' ') # TODO: Add class reflecting on form action.
|
343
344
|
accesskey = (options.delete(:accesskey) || @@default_commit_button_accesskey) unless button_html.has_key?(:accesskey)
|
344
345
|
button_html = button_html.merge(:accesskey => accesskey) if accesskey
|
345
|
-
template.content_tag(:li, self.submit(text, button_html), :class => element_class)
|
346
|
+
template.content_tag(:li, Formtastic::Util.html_safe(self.submit(text, button_html)), :class => element_class)
|
346
347
|
end
|
347
348
|
|
348
349
|
# A thin wrapper around #fields_for to set :builder => Formtastic::SemanticFormBuilder
|
@@ -399,11 +400,15 @@ module Formtastic #:nodoc:
|
|
399
400
|
text = options_or_text
|
400
401
|
options ||= {}
|
401
402
|
end
|
403
|
+
|
402
404
|
text = localized_string(method, text, :label) || humanized_attribute_name(method)
|
403
405
|
text += required_or_optional_string(options.delete(:required))
|
406
|
+
text = Formtastic::Util.html_safe(text)
|
404
407
|
|
405
408
|
# special case for boolean (checkbox) labels, which have a nested input
|
406
|
-
|
409
|
+
if options.key?(:label_prefix_for_nested_input)
|
410
|
+
text = options.delete(:label_prefix_for_nested_input) + text
|
411
|
+
end
|
407
412
|
|
408
413
|
input_name = options.delete(:input_name) || method
|
409
414
|
super(input_name, text, options)
|
@@ -422,8 +427,10 @@ module Formtastic #:nodoc:
|
|
422
427
|
#
|
423
428
|
def inline_errors_for(method, options = nil) #:nodoc:
|
424
429
|
if render_inline_errors?
|
425
|
-
errors = @object.errors[method.to_sym]
|
426
|
-
|
430
|
+
errors = [@object.errors[method.to_sym]]
|
431
|
+
errors << [@object.errors[association_primary_key(method)]] if association_macro_for_method(method) == :belongs_to
|
432
|
+
errors = errors.flatten.compact.uniq
|
433
|
+
send(:"error_#{@@inline_errors}", [*errors]) if errors.any?
|
427
434
|
else
|
428
435
|
nil
|
429
436
|
end
|
@@ -452,7 +459,7 @@ module Formtastic #:nodoc:
|
|
452
459
|
return nil if full_errors.blank?
|
453
460
|
html_options[:class] ||= "errors"
|
454
461
|
template.content_tag(:ul, html_options) do
|
455
|
-
full_errors.map { |error| template.content_tag(:li, error) }.join
|
462
|
+
Formtastic::Util.html_safe(full_errors.map { |error| template.content_tag(:li, Formtastic::Util.html_safe(error)) }.join)
|
456
463
|
end
|
457
464
|
end
|
458
465
|
|
@@ -483,6 +490,18 @@ module Formtastic #:nodoc:
|
|
483
490
|
[]
|
484
491
|
end
|
485
492
|
end
|
493
|
+
|
494
|
+
# Returns nil, or a symbol like :belongs_to or :has_many
|
495
|
+
def association_macro_for_method(method) #:nodoc:
|
496
|
+
reflection = self.reflection_for(method)
|
497
|
+
reflection.macro if reflection
|
498
|
+
end
|
499
|
+
|
500
|
+
def association_primary_key(method)
|
501
|
+
reflection = self.reflection_for(method)
|
502
|
+
reflection.options[:foreign_key] if reflection && !reflection.options[:foreign_key].blank?
|
503
|
+
:"#{method}_id"
|
504
|
+
end
|
486
505
|
|
487
506
|
# Prepare options to be sent to label
|
488
507
|
#
|
@@ -504,9 +523,9 @@ module Formtastic #:nodoc:
|
|
504
523
|
raise ArgumentError, 'You gave :for option with a block to inputs method, ' <<
|
505
524
|
'but the block does not accept any argument.' if block.arity <= 0
|
506
525
|
|
507
|
-
proc { |f| f.inputs(*args){ block.call(f) } }
|
526
|
+
proc { |f| return f.inputs(*args){ block.call(f) } }
|
508
527
|
else
|
509
|
-
proc { |f| f.inputs(*args) }
|
528
|
+
proc { |f| return f.inputs(*args) }
|
510
529
|
end
|
511
530
|
|
512
531
|
fields_for_args = [options.delete(:for), options.delete(:for_options) || {}].flatten
|
@@ -859,12 +878,12 @@ module Formtastic #:nodoc:
|
|
859
878
|
html_options[:checked] = selected_value == value if selected_option_is_present
|
860
879
|
|
861
880
|
li_content = template.content_tag(:label,
|
862
|
-
"#{self.radio_button(input_name, value, html_options)} #{label}",
|
881
|
+
Formtastic::Util.html_safe("#{self.radio_button(input_name, value, html_options)} #{label}"),
|
863
882
|
:for => input_id
|
864
883
|
)
|
865
884
|
|
866
885
|
li_options = value_as_class ? { :class => [method.to_s.singularize, value.to_s.downcase].join('_') } : {}
|
867
|
-
template.content_tag(:li, li_content, li_options)
|
886
|
+
template.content_tag(:li, Formtastic::Util.html_safe(li_content), li_options)
|
868
887
|
end
|
869
888
|
|
870
889
|
field_set_and_list_wrapping_for_method(method, options, list_item_content)
|
@@ -994,8 +1013,8 @@ module Formtastic #:nodoc:
|
|
994
1013
|
list_items_capture = ""
|
995
1014
|
hidden_fields_capture = ""
|
996
1015
|
|
997
|
-
datetime = options
|
998
|
-
datetime = @object.send(method) if @object && @object.send(method) # object trumps :selected
|
1016
|
+
datetime = options[:selected]
|
1017
|
+
datetime = @object.send(method) if @object && @object.send(method) # object value trumps :selected value
|
999
1018
|
|
1000
1019
|
html_options = options.delete(:input_html) || {}
|
1001
1020
|
input_ids = []
|
@@ -1013,10 +1032,10 @@ module Formtastic #:nodoc:
|
|
1013
1032
|
opts = strip_formtastic_options(options).merge(:prefix => @object_name, :field_name => field_name, :default => datetime)
|
1014
1033
|
item_label_text = labels[input] || ::I18n.t(input.to_s, :default => input.to_s.humanize, :scope => [:datetime, :prompts])
|
1015
1034
|
|
1016
|
-
list_items_capture << template.content_tag(:li, [
|
1017
|
-
!item_label_text.blank? ? template.content_tag(:label, item_label_text, :for => input_id) : "",
|
1035
|
+
list_items_capture << template.content_tag(:li, Formtastic::Util.html_safe([
|
1036
|
+
!item_label_text.blank? ? template.content_tag(:label, Formtastic::Util.html_safe(item_label_text), :for => input_id) : "",
|
1018
1037
|
template.send(:"select_#{input}", datetime, opts, html_options.merge(:id => input_id))
|
1019
|
-
].join("")
|
1038
|
+
].join(""))
|
1020
1039
|
)
|
1021
1040
|
end
|
1022
1041
|
end
|
@@ -1110,7 +1129,10 @@ module Formtastic #:nodoc:
|
|
1110
1129
|
selected_option_is_present = [:selected, :checked].any? { |k| options.key?(k) }
|
1111
1130
|
selected_values = (options.key?(:checked) ? options[:checked] : options[:selected]) if selected_option_is_present
|
1112
1131
|
selected_values = [*selected_values].compact
|
1113
|
-
|
1132
|
+
|
1133
|
+
disabled_option_is_present = options.key?(:disabled)
|
1134
|
+
disabled_values = [*options[:disabled]] if disabled_option_is_present
|
1135
|
+
|
1114
1136
|
list_item_content = collection.map do |c|
|
1115
1137
|
label = c.is_a?(Array) ? c.first : c
|
1116
1138
|
value = c.is_a?(Array) ? c.last : c
|
@@ -1118,15 +1140,16 @@ module Formtastic #:nodoc:
|
|
1118
1140
|
input_ids << input_id
|
1119
1141
|
|
1120
1142
|
html_options[:checked] = selected_values.include?(value) if selected_option_is_present
|
1143
|
+
html_options[:disabled] = disabled_values.include?(value) if disabled_option_is_present
|
1121
1144
|
html_options[:id] = input_id
|
1122
1145
|
|
1123
1146
|
li_content = template.content_tag(:label,
|
1124
|
-
"#{self.check_box(input_name, html_options, value, unchecked_value)} #{label}",
|
1147
|
+
Formtastic::Util.html_safe("#{self.check_box(input_name, html_options, value, unchecked_value)} #{label}"),
|
1125
1148
|
:for => input_id
|
1126
1149
|
)
|
1127
1150
|
|
1128
1151
|
li_options = value_as_class ? { :class => [method.to_s.singularize, value.to_s.downcase].join('_') } : {}
|
1129
|
-
template.content_tag(:li, li_content, li_options)
|
1152
|
+
template.content_tag(:li, Formtastic::Util.html_safe(li_content), li_options)
|
1130
1153
|
end
|
1131
1154
|
|
1132
1155
|
field_set_and_list_wrapping_for_method(method, options, list_item_content)
|
@@ -1191,13 +1214,13 @@ module Formtastic #:nodoc:
|
|
1191
1214
|
def inline_hints_for(method, options) #:nodoc:
|
1192
1215
|
options[:hint] = localized_string(method, options[:hint], :hint)
|
1193
1216
|
return if options[:hint].blank?
|
1194
|
-
template.content_tag(:p, options[:hint], :class => 'inline-hints')
|
1217
|
+
template.content_tag(:p, Formtastic::Util.html_safe(options[:hint]), :class => 'inline-hints')
|
1195
1218
|
end
|
1196
1219
|
|
1197
1220
|
# Creates an error sentence by calling to_sentence on the errors array.
|
1198
1221
|
#
|
1199
1222
|
def error_sentence(errors) #:nodoc:
|
1200
|
-
template.content_tag(:p, errors.to_sentence.untaint, :class => 'inline-errors')
|
1223
|
+
template.content_tag(:p, Formtastic::Util.html_safe(errors.to_sentence.untaint), :class => 'inline-errors')
|
1201
1224
|
end
|
1202
1225
|
|
1203
1226
|
# Creates an error li list.
|
@@ -1205,15 +1228,15 @@ module Formtastic #:nodoc:
|
|
1205
1228
|
def error_list(errors) #:nodoc:
|
1206
1229
|
list_elements = []
|
1207
1230
|
errors.each do |error|
|
1208
|
-
list_elements << template.content_tag(:li, error.untaint)
|
1231
|
+
list_elements << template.content_tag(:li, Formtastic::Util.html_safe(error.untaint))
|
1209
1232
|
end
|
1210
|
-
template.content_tag(:ul, list_elements.join("\n"), :class => 'errors')
|
1233
|
+
template.content_tag(:ul, Formtastic::Util.html_safe(list_elements.join("\n")), :class => 'errors')
|
1211
1234
|
end
|
1212
1235
|
|
1213
1236
|
# Creates an error sentence containing only the first error
|
1214
1237
|
#
|
1215
1238
|
def error_first(errors) #:nodoc:
|
1216
|
-
template.content_tag(:p, errors.first.untaint, :class => 'inline-errors')
|
1239
|
+
template.content_tag(:p, Formtastic::Util.html_safe(errors.first.untaint), :class => 'inline-errors')
|
1217
1240
|
end
|
1218
1241
|
|
1219
1242
|
# Generates the required or optional string. If the value set is a proc,
|
@@ -1260,7 +1283,7 @@ module Formtastic #:nodoc:
|
|
1260
1283
|
|
1261
1284
|
legend = html_options.delete(:name).to_s
|
1262
1285
|
legend %= parent_child_index(html_options[:parent]) if html_options[:parent]
|
1263
|
-
legend = template.content_tag(:legend, template.content_tag(:span, legend)) unless legend.blank?
|
1286
|
+
legend = template.content_tag(:legend, template.content_tag(:span, Formtastic::Util.html_safe(legend))) unless legend.blank?
|
1264
1287
|
|
1265
1288
|
if block_given?
|
1266
1289
|
contents = if template.respond_to?(:is_haml?) && template.is_haml?
|
@@ -1273,11 +1296,11 @@ module Formtastic #:nodoc:
|
|
1273
1296
|
# Ruby 1.9: String#to_s behavior changed, need to make an explicit join.
|
1274
1297
|
contents = contents.join if contents.respond_to?(:join)
|
1275
1298
|
fieldset = template.content_tag(:fieldset,
|
1276
|
-
legend << template.content_tag(:ol, contents),
|
1299
|
+
Formtastic::Util.html_safe(legend) << template.content_tag(:ol, Formtastic::Util.html_safe(contents)),
|
1277
1300
|
html_options.except(:builder, :parent)
|
1278
1301
|
)
|
1279
1302
|
|
1280
|
-
template.concat(fieldset) if block_given?
|
1303
|
+
template.concat(fieldset) if block_given? && (!defined?(Rails::VERSION) || Rails::VERSION::MAJOR == 2)
|
1281
1304
|
fieldset
|
1282
1305
|
end
|
1283
1306
|
|
@@ -1305,7 +1328,7 @@ module Formtastic #:nodoc:
|
|
1305
1328
|
template.content_tag(:legend,
|
1306
1329
|
self.label(method, options_for_label(options).merge(:for => options.delete(:label_for))), :class => 'label'
|
1307
1330
|
) <<
|
1308
|
-
template.content_tag(:ol, contents)
|
1331
|
+
template.content_tag(:ol, Formtastic::Util.html_safe(contents))
|
1309
1332
|
)
|
1310
1333
|
end
|
1311
1334
|
|
@@ -1323,7 +1346,7 @@ module Formtastic #:nodoc:
|
|
1323
1346
|
case column.type
|
1324
1347
|
when :string
|
1325
1348
|
return :password if method.to_s =~ /password/
|
1326
|
-
return :country if method.to_s =~ /country
|
1349
|
+
return :country if method.to_s =~ /country$/
|
1327
1350
|
return :time_zone if method.to_s =~ /time_zone/
|
1328
1351
|
when :integer
|
1329
1352
|
return :select if method.to_s =~ /_id$/
|
@@ -1380,7 +1403,13 @@ module Formtastic #:nodoc:
|
|
1380
1403
|
collection = if options[:collection]
|
1381
1404
|
options.delete(:collection)
|
1382
1405
|
elsif reflection = self.reflection_for(column)
|
1383
|
-
|
1406
|
+
options[:find_options] ||= {}
|
1407
|
+
|
1408
|
+
if conditions = reflection.options[:conditions]
|
1409
|
+
options[:find_options][:conditions] = reflection.klass.merge_conditions(conditions, options[:find_options][:conditions])
|
1410
|
+
end
|
1411
|
+
|
1412
|
+
reflection.klass.find(:all, options[:find_options])
|
1384
1413
|
else
|
1385
1414
|
create_boolean_collection(options)
|
1386
1415
|
end
|
@@ -1471,7 +1500,7 @@ module Formtastic #:nodoc:
|
|
1471
1500
|
if [:has_and_belongs_to_many, :has_many].include?(reflection.macro)
|
1472
1501
|
"#{method.to_s.singularize}_ids"
|
1473
1502
|
else
|
1474
|
-
reflection.options[:foreign_key] ||
|
1503
|
+
reflection.options[:foreign_key] || "#{method}_id"
|
1475
1504
|
end
|
1476
1505
|
else
|
1477
1506
|
method
|
@@ -1566,9 +1595,9 @@ module Formtastic #:nodoc:
|
|
1566
1595
|
#
|
1567
1596
|
# Lookup priority:
|
1568
1597
|
#
|
1569
|
-
# 'formtastic
|
1570
|
-
# 'formtastic
|
1571
|
-
# 'formtastic
|
1598
|
+
# 'formtastic.%{type}.%{model}.%{action}.%{attribute}'
|
1599
|
+
# 'formtastic.%{type}.%{model}.%{attribute}'
|
1600
|
+
# 'formtastic.%{type}.%{attribute}'
|
1572
1601
|
#
|
1573
1602
|
# Example:
|
1574
1603
|
#
|
@@ -1587,15 +1616,16 @@ module Formtastic #:nodoc:
|
|
1587
1616
|
use_i18n = value.nil? ? @@i18n_lookups_by_default : (value != false)
|
1588
1617
|
|
1589
1618
|
if use_i18n
|
1590
|
-
model_name = self.model_name.underscore
|
1619
|
+
model_name, nested_model_name = normalize_model_name(self.model_name.underscore)
|
1591
1620
|
action_name = template.params[:action].to_s rescue ''
|
1592
1621
|
attribute_name = key.to_s
|
1593
1622
|
|
1594
1623
|
defaults = ::Formtastic::I18n::SCOPES.collect do |i18n_scope|
|
1595
1624
|
i18n_path = i18n_scope.dup
|
1596
|
-
i18n_path.gsub!('{
|
1597
|
-
i18n_path.gsub!('{
|
1598
|
-
i18n_path.gsub!('{
|
1625
|
+
i18n_path.gsub!('%{action}', action_name)
|
1626
|
+
i18n_path.gsub!('%{model}', model_name)
|
1627
|
+
i18n_path.gsub!('%{nested_model}', nested_model_name) unless nested_model_name.nil?
|
1628
|
+
i18n_path.gsub!('%{attribute}', attribute_name)
|
1599
1629
|
i18n_path.gsub!('..', '.')
|
1600
1630
|
i18n_path.to_sym
|
1601
1631
|
end
|
@@ -1612,6 +1642,14 @@ module Formtastic #:nodoc:
|
|
1612
1642
|
@object.present? ? @object.class.name : @object_name.to_s.classify
|
1613
1643
|
end
|
1614
1644
|
|
1645
|
+
def normalize_model_name(name)
|
1646
|
+
if name =~ /(.+)\[(.+)\]/
|
1647
|
+
[$1, $2]
|
1648
|
+
else
|
1649
|
+
[name]
|
1650
|
+
end
|
1651
|
+
end
|
1652
|
+
|
1615
1653
|
def send_or_call(duck, object)
|
1616
1654
|
if duck.is_a?(Proc)
|
1617
1655
|
duck.call(object)
|
data/lib/formtastic/i18n.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# coding: utf-8
|
1
2
|
module Formtastic
|
2
3
|
module I18n
|
3
4
|
|
@@ -6,13 +7,16 @@ module Formtastic
|
|
6
7
|
:required => 'required',
|
7
8
|
:yes => 'Yes',
|
8
9
|
:no => 'No',
|
9
|
-
:create => 'Create {
|
10
|
-
:update => 'Update {
|
10
|
+
:create => 'Create %{model}',
|
11
|
+
:update => 'Update %{model}'
|
11
12
|
}.freeze
|
12
13
|
SCOPES = [
|
13
|
-
'{
|
14
|
-
'{
|
15
|
-
'{{attribute}
|
14
|
+
'%{model}.%{nested_model}.%{action}.%{attribute}',
|
15
|
+
'%{model}.%{action}.%{attribute}',
|
16
|
+
'%{model}.%{nested_model}.%{attribute}',
|
17
|
+
'%{model}.%{attribute}',
|
18
|
+
'%{nested_model}.%{attribute}',
|
19
|
+
'%{attribute}'
|
16
20
|
]
|
17
21
|
|
18
22
|
class << self
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Adapted from the rails3 compatibility shim in Haml 2.2
|
2
|
+
module Formtastic
|
3
|
+
module Util
|
4
|
+
extend self
|
5
|
+
## Rails XSS Safety
|
6
|
+
|
7
|
+
# Returns the given text, marked as being HTML-safe.
|
8
|
+
# With older versions of the Rails XSS-safety mechanism,
|
9
|
+
# this destructively modifies the HTML-safety of `text`.
|
10
|
+
#
|
11
|
+
# @param text [String]
|
12
|
+
# @return [String] `text`, marked as HTML-safe
|
13
|
+
def html_safe(text)
|
14
|
+
return text if text.nil?
|
15
|
+
return text.html_safe if defined?(ActiveSupport::SafeBuffer)
|
16
|
+
return text.html_safe!
|
17
|
+
end
|
18
|
+
|
19
|
+
def rails_safe_buffer_class
|
20
|
+
return ActionView::SafeBuffer if defined?(ActionView::SafeBuffer)
|
21
|
+
ActiveSupport::SafeBuffer
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
data/spec/commit_button_spec.rb
CHANGED
@@ -136,11 +136,11 @@ describe 'SemanticFormBuilder#commit_button' do
|
|
136
136
|
describe 'when no explicit label is provided' do
|
137
137
|
describe 'when no I18n-localized label is provided' do
|
138
138
|
before do
|
139
|
-
::I18n.backend.store_translations :en, :formtastic => {:submit => 'Submit {
|
139
|
+
::I18n.backend.store_translations :en, :formtastic => {:submit => 'Submit %{model}'}
|
140
140
|
end
|
141
141
|
|
142
142
|
after do
|
143
|
-
::I18n.backend.
|
143
|
+
::I18n.backend.reload!
|
144
144
|
end
|
145
145
|
|
146
146
|
it 'should render an input with default I18n-localized label (fallback)' do
|
@@ -157,33 +157,34 @@ describe 'SemanticFormBuilder#commit_button' do
|
|
157
157
|
:formtastic => {
|
158
158
|
:actions => {
|
159
159
|
:submit => 'Custom Submit',
|
160
|
-
:post => {
|
161
|
-
:submit => 'Custom Submit {{model}}'
|
162
|
-
}
|
163
160
|
}
|
164
161
|
}
|
165
162
|
::Formtastic::SemanticFormBuilder.i18n_lookups_by_default = true
|
166
163
|
end
|
167
164
|
|
168
|
-
|
169
|
-
|
170
|
-
concat(builder.commit_button)
|
171
|
-
end
|
172
|
-
output_buffer.should have_tag(%Q{li.commit input[@value="Custom Submit Post"][@class~="submit"]})
|
165
|
+
after do
|
166
|
+
::I18n.backend.reload!
|
173
167
|
end
|
174
168
|
|
175
|
-
it 'should render an input with
|
169
|
+
it 'should render an input with localized label (I18n)' do
|
176
170
|
::I18n.backend.store_translations :en,
|
177
171
|
:formtastic => {
|
178
172
|
:actions => {
|
179
173
|
:post => {
|
180
|
-
:submit =>
|
174
|
+
:submit => 'Custom Submit %{model}'
|
181
175
|
}
|
182
176
|
}
|
183
177
|
}
|
184
178
|
semantic_form_for(:post, :url => 'http://example.com') do |builder|
|
185
179
|
concat(builder.commit_button)
|
186
180
|
end
|
181
|
+
output_buffer.should have_tag(%Q{li.commit input[@value="Custom Submit Post"][@class~="submit"]})
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'should render an input with anoptional localized label (I18n) - if first is not set' do
|
185
|
+
semantic_form_for(:post, :url => 'http://example.com') do |builder|
|
186
|
+
concat(builder.commit_button)
|
187
|
+
end
|
187
188
|
output_buffer.should have_tag(%Q{li.commit input[@value="Custom Submit"][@class~="submit"]})
|
188
189
|
end
|
189
190
|
|
@@ -209,11 +210,11 @@ describe 'SemanticFormBuilder#commit_button' do
|
|
209
210
|
describe 'when no explicit label is provided' do
|
210
211
|
describe 'when no I18n-localized label is provided' do
|
211
212
|
before do
|
212
|
-
::I18n.backend.store_translations :en, :formtastic => {:create => 'Create {
|
213
|
+
::I18n.backend.store_translations :en, :formtastic => {:create => 'Create %{model}'}
|
213
214
|
end
|
214
215
|
|
215
216
|
after do
|
216
|
-
::I18n.backend.
|
217
|
+
::I18n.backend.reload!
|
217
218
|
end
|
218
219
|
|
219
220
|
it 'should render an input with default I18n-localized label (fallback)' do
|
@@ -230,40 +231,35 @@ describe 'SemanticFormBuilder#commit_button' do
|
|
230
231
|
:formtastic => {
|
231
232
|
:actions => {
|
232
233
|
:create => 'Custom Create',
|
233
|
-
:post => {
|
234
|
-
:create => 'Custom Create {{model}}'
|
235
|
-
}
|
236
234
|
}
|
237
235
|
}
|
238
236
|
::Formtastic::SemanticFormBuilder.i18n_lookups_by_default = true
|
239
237
|
end
|
240
238
|
|
241
239
|
after do
|
242
|
-
::I18n.backend.
|
240
|
+
::I18n.backend.reload!
|
243
241
|
end
|
244
242
|
|
245
243
|
it 'should render an input with localized label (I18n)' do
|
246
|
-
semantic_form_for(@new_post) do |builder|
|
247
|
-
concat(builder.commit_button)
|
248
|
-
end
|
249
|
-
output_buffer.should have_tag(%Q{li.commit input[@value="Custom Create Post"][@class~="create"]})
|
250
|
-
end
|
251
|
-
|
252
|
-
it 'should render an input with anoptional localized label (I18n) - if first is not set' do
|
253
244
|
::I18n.backend.store_translations :en,
|
254
245
|
:formtastic => {
|
255
246
|
:actions => {
|
256
247
|
:post => {
|
257
|
-
:create =>
|
248
|
+
:create => 'Custom Create %{model}'
|
258
249
|
}
|
259
250
|
}
|
260
251
|
}
|
261
252
|
semantic_form_for(@new_post) do |builder|
|
262
253
|
concat(builder.commit_button)
|
263
254
|
end
|
264
|
-
output_buffer.should have_tag(%Q{li.commit input[@value="Custom Create"][@class~="create"]})
|
265
|
-
|
266
|
-
|
255
|
+
output_buffer.should have_tag(%Q{li.commit input[@value="Custom Create Post"][@class~="create"]})
|
256
|
+
end
|
257
|
+
|
258
|
+
it 'should render an input with anoptional localized label (I18n) - if first is not set' do
|
259
|
+
semantic_form_for(@new_post) do |builder|
|
260
|
+
concat(builder.commit_button)
|
261
|
+
end
|
262
|
+
output_buffer.should have_tag(%Q{li.commit input[@value="Custom Create"][@class~="create"]})
|
267
263
|
end
|
268
264
|
|
269
265
|
end
|
@@ -288,11 +284,11 @@ describe 'SemanticFormBuilder#commit_button' do
|
|
288
284
|
describe 'when no explicit label is provided' do
|
289
285
|
describe 'when no I18n-localized label is provided' do
|
290
286
|
before do
|
291
|
-
::I18n.backend.store_translations :en, :formtastic => {:update => 'Save {
|
287
|
+
::I18n.backend.store_translations :en, :formtastic => {:update => 'Save %{model}'}
|
292
288
|
end
|
293
289
|
|
294
290
|
after do
|
295
|
-
::I18n.backend.
|
291
|
+
::I18n.backend.reload!
|
296
292
|
end
|
297
293
|
|
298
294
|
it 'should render an input with default I18n-localized label (fallback)' do
|
@@ -309,33 +305,34 @@ describe 'SemanticFormBuilder#commit_button' do
|
|
309
305
|
:formtastic => {
|
310
306
|
:actions => {
|
311
307
|
:update => 'Custom Save',
|
312
|
-
:post => {
|
313
|
-
:update => 'Custom Save {{model}}'
|
314
|
-
}
|
315
308
|
}
|
316
309
|
}
|
317
310
|
::Formtastic::SemanticFormBuilder.i18n_lookups_by_default = true
|
318
311
|
end
|
319
312
|
|
320
|
-
|
321
|
-
|
322
|
-
concat(builder.commit_button)
|
323
|
-
end
|
324
|
-
output_buffer.should have_tag(%Q{li.commit input[@value="Custom Save Post"][@class~="update"]})
|
313
|
+
after do
|
314
|
+
::I18n.backend.reload!
|
325
315
|
end
|
326
|
-
|
327
|
-
it 'should render an input with
|
316
|
+
|
317
|
+
it 'should render an input with localized label (I18n)' do
|
328
318
|
::I18n.backend.store_translations :en,
|
329
319
|
:formtastic => {
|
330
320
|
:actions => {
|
331
321
|
:post => {
|
332
|
-
:update =>
|
322
|
+
:update => 'Custom Save %{model}'
|
333
323
|
}
|
334
324
|
}
|
335
325
|
}
|
336
326
|
semantic_form_for(@new_post) do |builder|
|
337
327
|
concat(builder.commit_button)
|
338
328
|
end
|
329
|
+
output_buffer.should have_tag(%Q{li.commit input[@value="Custom Save Post"][@class~="update"]})
|
330
|
+
end
|
331
|
+
|
332
|
+
it 'should render an input with anoptional localized label (I18n) - if first is not set' do
|
333
|
+
semantic_form_for(@new_post) do |builder|
|
334
|
+
concat(builder.commit_button)
|
335
|
+
end
|
339
336
|
output_buffer.should have_tag(%Q{li.commit input[@value="Custom Save"][@class~="update"]})
|
340
337
|
::I18n.backend.store_translations :en, :formtastic => {}
|
341
338
|
end
|
data/spec/errors_spec.rb
CHANGED
@@ -81,5 +81,24 @@ describe 'SemanticFormBuilder#errors_on' do
|
|
81
81
|
end
|
82
82
|
end
|
83
83
|
|
84
|
+
|
85
|
+
describe 'when there are errors on the association and column' do
|
86
|
+
|
87
|
+
it "should list all unique errors" do
|
88
|
+
::Formtastic::SemanticFormBuilder.inline_errors = :list
|
89
|
+
::Post.stub!(:reflections).and_return({:author => mock('reflection', :options => {}, :macro => :belongs_to)})
|
90
|
+
|
91
|
+
@errors.stub!(:[]).with(:author).and_return(['must not be blank'])
|
92
|
+
@errors.stub!(:[]).with(:author_id).and_return(['is already taken', 'must not be blank']) # note the duplicate of association
|
93
|
+
|
94
|
+
semantic_form_for(@new_post) do |builder|
|
95
|
+
concat(builder.input(:author))
|
96
|
+
end
|
97
|
+
output_buffer.should have_tag("ul.errors li", /must not be blank/, :count => 1)
|
98
|
+
output_buffer.should have_tag("ul.errors li", /is already taken/, :count => 1)
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
84
103
|
end
|
85
104
|
|
data/spec/i18n_spec.rb
CHANGED
@@ -24,20 +24,24 @@ describe 'Formtastic::I18n' do
|
|
24
24
|
|
25
25
|
before do
|
26
26
|
@formtastic_strings = {
|
27
|
-
:required => 'Default Required',
|
28
27
|
:yes => 'Default Yes',
|
29
28
|
:no => 'Default No',
|
30
|
-
:create => 'Default Create {
|
31
|
-
:update => 'Default Update {
|
29
|
+
:create => 'Default Create %{model}',
|
30
|
+
:update => 'Default Update %{model}',
|
32
31
|
:custom_scope => {
|
33
32
|
:duck => 'Duck',
|
34
|
-
:duck_pond => '{
|
33
|
+
:duck_pond => '%{ducks} ducks in a pond'
|
35
34
|
}
|
36
35
|
}
|
37
36
|
::I18n.backend.store_translations :en, :formtastic => @formtastic_strings
|
38
37
|
end
|
38
|
+
|
39
|
+
after do
|
40
|
+
::I18n.backend.reload!
|
41
|
+
end
|
39
42
|
|
40
43
|
it "should translate core strings correctly" do
|
44
|
+
::I18n.backend.store_translations :en, {:formtastic => {:required => 'Default Required'}}
|
41
45
|
::Formtastic::I18n.t(:required).should == "Default Required"
|
42
46
|
::Formtastic::I18n.t(:yes).should == "Default Yes"
|
43
47
|
::Formtastic::I18n.t(:no).should == "Default No"
|
@@ -54,7 +58,6 @@ describe 'Formtastic::I18n' do
|
|
54
58
|
end
|
55
59
|
|
56
60
|
it "should be possible to override default values" do
|
57
|
-
::I18n.backend.store_translations :en, {:formtastic => {:required => nil}}
|
58
61
|
::Formtastic::I18n.t(:required, :default => 'Nothing found!').should == 'Nothing found!'
|
59
62
|
end
|
60
63
|
|
@@ -63,18 +66,12 @@ describe 'Formtastic::I18n' do
|
|
63
66
|
describe "when no I18n locales are available" do
|
64
67
|
|
65
68
|
before do
|
66
|
-
::I18n.backend.
|
67
|
-
:required => nil,
|
68
|
-
:yes => nil,
|
69
|
-
:no => nil,
|
70
|
-
:create => nil,
|
71
|
-
:update => nil
|
72
|
-
}
|
69
|
+
::I18n.backend.reload!
|
73
70
|
end
|
74
|
-
|
71
|
+
|
75
72
|
it "should use default strings" do
|
76
73
|
(::Formtastic::I18n::DEFAULT_VALUES.keys).each do |key|
|
77
|
-
::Formtastic::I18n.t(key, :model => '{
|
74
|
+
::Formtastic::I18n.t(key, :model => '%{model}').should == ::Formtastic::I18n::DEFAULT_VALUES[key]
|
78
75
|
end
|
79
76
|
end
|
80
77
|
|
@@ -92,7 +89,8 @@ describe 'Formtastic::I18n' do
|
|
92
89
|
:labels => {
|
93
90
|
:title => "Hello world!",
|
94
91
|
:post => {:title => "Hello post!"},
|
95
|
-
:project => {:title => "Hello project!"}
|
92
|
+
:project => {:title => "Hello project!", :task => {:name => "Hello task name!"}},
|
93
|
+
:line_item => {:name => "Hello line item name!"}
|
96
94
|
}
|
97
95
|
}
|
98
96
|
::Formtastic::SemanticFormBuilder.i18n_lookups_by_default = true
|
@@ -102,7 +100,7 @@ describe 'Formtastic::I18n' do
|
|
102
100
|
end
|
103
101
|
|
104
102
|
after do
|
105
|
-
::I18n.backend.
|
103
|
+
::I18n.backend.reload!
|
106
104
|
::Formtastic::SemanticFormBuilder.i18n_lookups_by_default = false
|
107
105
|
end
|
108
106
|
|
@@ -124,6 +122,25 @@ describe 'Formtastic::I18n' do
|
|
124
122
|
output_buffer.should have_tag("form label", /Hello project!/)
|
125
123
|
end
|
126
124
|
|
125
|
+
it 'should be able to translate nested objects with nested translations' do
|
126
|
+
semantic_form_for(:project, :url => 'http://test.host') do |builder|
|
127
|
+
builder.semantic_fields_for(:task) do |f|
|
128
|
+
concat(f.input(:name))
|
129
|
+
end
|
130
|
+
end
|
131
|
+
output_buffer.should have_tag("form label", /Hello task name!/)
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'should be able to translated nested objects with top level translations' do
|
135
|
+
semantic_form_for(:order, :url => 'http://test.host') do |builder|
|
136
|
+
builder.semantic_fields_for(:line_item) do |f|
|
137
|
+
concat(f.input(:name))
|
138
|
+
end
|
139
|
+
end
|
140
|
+
output_buffer.should have_tag("form label", /Hello line item name!/)
|
141
|
+
end
|
142
|
+
|
143
|
+
|
127
144
|
# TODO: Add spec for namespaced models?
|
128
145
|
|
129
146
|
end
|
data/spec/input_spec.rb
CHANGED
@@ -528,34 +528,35 @@ describe 'SemanticFormBuilder#input' do
|
|
528
528
|
:formtastic => {
|
529
529
|
:hints => {
|
530
530
|
:title => @default_localized_hint_text,
|
531
|
-
:post => {
|
532
|
-
:title => @localized_hint_text
|
533
|
-
}
|
534
531
|
}
|
535
532
|
}
|
536
533
|
::Formtastic::SemanticFormBuilder.i18n_lookups_by_default = false
|
537
534
|
end
|
538
535
|
|
536
|
+
after do
|
537
|
+
::I18n.backend.reload!
|
538
|
+
end
|
539
|
+
|
539
540
|
describe 'when provided value (hint value) is set to TRUE' do
|
540
541
|
it 'should render a hint paragraph containing a localized hint (I18n)' do
|
541
|
-
semantic_form_for(@new_post) do |builder|
|
542
|
-
concat(builder.input(:title, :hint => true))
|
543
|
-
end
|
544
|
-
output_buffer.should have_tag('form li p.inline-hints', @localized_hint_text)
|
545
|
-
end
|
546
|
-
|
547
|
-
it 'should render a hint paragraph containing an optional localized hint (I18n) if first is not set' do
|
548
542
|
::I18n.backend.store_translations :en,
|
549
543
|
:formtastic => {
|
550
544
|
:hints => {
|
551
545
|
:post => {
|
552
|
-
:title =>
|
546
|
+
:title => @localized_hint_text
|
553
547
|
}
|
554
548
|
}
|
555
549
|
}
|
556
550
|
semantic_form_for(@new_post) do |builder|
|
557
551
|
concat(builder.input(:title, :hint => true))
|
558
552
|
end
|
553
|
+
output_buffer.should have_tag('form li p.inline-hints', @localized_hint_text)
|
554
|
+
end
|
555
|
+
|
556
|
+
it 'should render a hint paragraph containing an optional localized hint (I18n) if first is not set' do
|
557
|
+
semantic_form_for(@new_post) do |builder|
|
558
|
+
concat(builder.input(:title, :hint => true))
|
559
|
+
end
|
559
560
|
output_buffer.should have_tag('form li p.inline-hints', @default_localized_hint_text)
|
560
561
|
end
|
561
562
|
end
|
@@ -177,6 +177,61 @@ describe 'check_boxes input' do
|
|
177
177
|
end
|
178
178
|
|
179
179
|
|
180
|
+
describe 'when :disabled is set' do
|
181
|
+
before do
|
182
|
+
@output_buffer = ''
|
183
|
+
end
|
184
|
+
|
185
|
+
describe "no disabled items" do
|
186
|
+
before do
|
187
|
+
@new_post.stub!(:author_ids).and_return(nil)
|
188
|
+
|
189
|
+
semantic_form_for(@new_post) do |builder|
|
190
|
+
concat(builder.input(:authors, :as => :check_boxes, :disabled => nil))
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
it 'should not have any disabled item(s)' do
|
195
|
+
output_buffer.should_not have_tag("form li fieldset ol li label input[@disabled='disabled']")
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
describe "single disabled item" do
|
200
|
+
before do
|
201
|
+
@new_post.stub!(:author_ids).and_return(nil)
|
202
|
+
|
203
|
+
semantic_form_for(@new_post) do |builder|
|
204
|
+
concat(builder.input(:authors, :as => :check_boxes, :disabled => @fred.id))
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
it "should have one item disabled; the specified one" do
|
209
|
+
output_buffer.should have_tag("form li fieldset ol li label input[@disabled='disabled']", :count => 1)
|
210
|
+
output_buffer.should have_tag("form li fieldset ol li label[@for='post_author_ids_#{@fred.id}']", /fred/i)
|
211
|
+
output_buffer.should have_tag("form li fieldset ol li label input[@disabled='disabled'][@value='#{@fred.id}']")
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
describe "multiple disabled items" do
|
216
|
+
before do
|
217
|
+
@new_post.stub!(:author_ids).and_return(nil)
|
218
|
+
|
219
|
+
semantic_form_for(@new_post) do |builder|
|
220
|
+
concat(builder.input(:authors, :as => :check_boxes, :disabled => [@bob.id, @fred.id]))
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
it "should have multiple items disabled; the specified ones" do
|
225
|
+
output_buffer.should have_tag("form li fieldset ol li label input[@disabled='disabled']", :count => 2)
|
226
|
+
output_buffer.should have_tag("form li fieldset ol li label[@for='post_author_ids_#{@bob.id}']", /bob/i)
|
227
|
+
output_buffer.should have_tag("form li fieldset ol li label input[@disabled='disabled'][@value='#{@bob.id}']")
|
228
|
+
output_buffer.should have_tag("form li fieldset ol li label[@for='post_author_ids_#{@fred.id}']", /fred/i)
|
229
|
+
output_buffer.should have_tag("form li fieldset ol li label input[@disabled='disabled'][@value='#{@fred.id}']")
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
234
|
+
|
180
235
|
end
|
181
236
|
|
182
237
|
|
@@ -26,7 +26,7 @@ describe 'country input' do
|
|
26
26
|
|
27
27
|
before do
|
28
28
|
semantic_form_for(@new_post) do |builder|
|
29
|
-
builder.stub!(:country_select).and_return("<select><option>...</option></select>")
|
29
|
+
builder.stub!(:country_select).and_return(Formtastic::Util.html_safe("<select><option>...</option></select>"))
|
30
30
|
concat(builder.input(:country, :as => :country))
|
31
31
|
end
|
32
32
|
end
|
@@ -54,8 +54,8 @@ describe 'country input' do
|
|
54
54
|
it "should be passed down to the country_select helper when provided" do
|
55
55
|
priority_countries = ["Foo", "Bah"]
|
56
56
|
semantic_form_for(@new_post) do |builder|
|
57
|
-
builder.stub!(:country_select).and_return("<select><option>...</option></select>")
|
58
|
-
builder.should_receive(:country_select).with(:country, priority_countries, {}, {}).and_return("<select><option>...</option></select>")
|
57
|
+
builder.stub!(:country_select).and_return(Formtastic::Util.html_safe("<select><option>...</option></select>"))
|
58
|
+
builder.should_receive(:country_select).with(:country, priority_countries, {}, {}).and_return(Formtastic::Util.html_safe("<select><option>...</option></select>"))
|
59
59
|
|
60
60
|
concat(builder.input(:country, :as => :country, :priority_countries => priority_countries))
|
61
61
|
end
|
@@ -68,13 +68,47 @@ describe 'country input' do
|
|
68
68
|
|
69
69
|
semantic_form_for(@new_post) do |builder|
|
70
70
|
builder.stub!(:country_select).and_return("<select><option>...</option></select>")
|
71
|
-
builder.should_receive(:country_select).with(:country, priority_countries, {}, {}).and_return("<select><option>...</option></select>")
|
71
|
+
builder.should_receive(:country_select).with(:country, priority_countries, {}, {}).and_return(Formtastic::Util.html_safe("<select><option>...</option></select>"))
|
72
72
|
|
73
73
|
concat(builder.input(:country, :as => :country))
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
77
77
|
end
|
78
|
-
|
78
|
+
|
79
|
+
describe "matching" do
|
80
|
+
|
81
|
+
describe "when the attribute is 'country'" do
|
82
|
+
|
83
|
+
before do
|
84
|
+
semantic_form_for(@new_post) do |builder|
|
85
|
+
builder.stub!(:country_select).and_return(Formtastic::Util.html_safe("<select><option>...</option></select>"))
|
86
|
+
concat(builder.input(:country))
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should render a country input" do
|
91
|
+
output_buffer.should have_tag "form li.country"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "whent the attribute is 'country_something'" do
|
96
|
+
|
97
|
+
before do
|
98
|
+
semantic_form_for(@new_post) do |builder|
|
99
|
+
concat(builder.input(:country_subdivision))
|
100
|
+
concat(builder.input(:country_code))
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should render a country input" do
|
105
|
+
output_buffer.should_not have_tag "form li.country"
|
106
|
+
output_buffer.should have_tag "form li.string", :count => 2
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
79
113
|
end
|
80
114
|
|
@@ -68,7 +68,7 @@ describe 'date input' do
|
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
71
|
-
describe 'when the object has no value' do
|
71
|
+
describe 'when the object has no value (nil)' do
|
72
72
|
it "should select the :selected if provided as a Date" do
|
73
73
|
output_buffer.replace ''
|
74
74
|
@new_post.stub!(:created_at => nil)
|
@@ -104,14 +104,13 @@ describe 'date input' do
|
|
104
104
|
output_buffer.should_not have_tag("form li ol li select#post_created_at_1i option[@selected]")
|
105
105
|
end
|
106
106
|
|
107
|
-
it "should select
|
107
|
+
it "should select nothing if a :selected is not provided" do
|
108
108
|
output_buffer.replace ''
|
109
109
|
@new_post.stub!(:created_at => nil)
|
110
110
|
semantic_form_for(@new_post) do |builder|
|
111
111
|
concat(builder.input(:created_at, :as => :date))
|
112
112
|
end
|
113
|
-
output_buffer.
|
114
|
-
output_buffer.should have_tag("form li ol li select#post_created_at_1i option[@value='#{Time.now.year}'][@selected]", :count => 1)
|
113
|
+
output_buffer.should_not have_tag("form li ol li select option[@selected]")
|
115
114
|
|
116
115
|
end
|
117
116
|
end
|
@@ -163,6 +163,21 @@ describe 'datetime input' do
|
|
163
163
|
end
|
164
164
|
end
|
165
165
|
|
166
|
+
#describe 'when an object is given and the attribute value is nil' do
|
167
|
+
# before(:each) do
|
168
|
+
# @new_post.stub!(:publish_at).and_return(nil)
|
169
|
+
# output_buffer.replace ''
|
170
|
+
# semantic_form_for(@new_post) do |builder|
|
171
|
+
# concat(builder.input(:publish_at, :as => :datetime))
|
172
|
+
# end
|
173
|
+
# end
|
174
|
+
#
|
175
|
+
# it 'should not select a value' do
|
176
|
+
# output_buffer.should_not have_tag('form li.datetime option[@selected]')
|
177
|
+
# end
|
178
|
+
#
|
179
|
+
#end
|
180
|
+
|
166
181
|
describe ':selected option' do
|
167
182
|
|
168
183
|
describe "when the object has a value" do
|
@@ -215,15 +230,13 @@ describe 'datetime input' do
|
|
215
230
|
output_buffer.should_not have_tag("form li ol li select#post_created_at_1i option[@selected]")
|
216
231
|
end
|
217
232
|
|
218
|
-
it "should select
|
233
|
+
it "should select nothing if a :selected is not provided" do
|
219
234
|
output_buffer.replace ''
|
220
235
|
@new_post.stub!(:created_at => nil)
|
221
236
|
semantic_form_for(@new_post) do |builder|
|
222
237
|
concat(builder.input(:created_at, :as => :datetime))
|
223
238
|
end
|
224
|
-
output_buffer.
|
225
|
-
output_buffer.should have_tag("form li ol li select#post_created_at_1i option[@value='#{Time.now.year}'][@selected]", :count => 1)
|
226
|
-
|
239
|
+
output_buffer.should_not have_tag("form li ol li select option[@selected]")
|
227
240
|
end
|
228
241
|
end
|
229
242
|
|
@@ -94,6 +94,7 @@ describe 'select input' do
|
|
94
94
|
before do
|
95
95
|
semantic_form_for(@new_post) do |builder|
|
96
96
|
concat(builder.input(:author, :as => :select))
|
97
|
+
concat(builder.input(:reviewer, :as => :select))
|
97
98
|
end
|
98
99
|
end
|
99
100
|
|
@@ -108,11 +109,13 @@ describe 'select input' do
|
|
108
109
|
it 'should have a select inside the wrapper' do
|
109
110
|
output_buffer.should have_tag('form li select')
|
110
111
|
output_buffer.should have_tag('form li select#post_author_id')
|
112
|
+
output_buffer.should have_tag('form li select#post_reviewer_id')
|
111
113
|
end
|
112
114
|
|
113
115
|
it 'should have a valid name' do
|
114
116
|
output_buffer.should have_tag("form li select[@name='post[author_id]']")
|
115
117
|
output_buffer.should_not have_tag("form li select[@name='post[author_id][]']")
|
118
|
+
output_buffer.should_not have_tag("form li select[@name='post[reviewer_id][]']")
|
116
119
|
end
|
117
120
|
|
118
121
|
it 'should not create a multi-select' do
|
@@ -128,14 +131,14 @@ describe 'select input' do
|
|
128
131
|
end
|
129
132
|
|
130
133
|
it 'should have a select option for each Author' do
|
131
|
-
output_buffer.should have_tag(
|
134
|
+
output_buffer.should have_tag("form li select[@name='post[author_id]'] option", :count => ::Author.find(:all).size + 1)
|
132
135
|
::Author.find(:all).each do |author|
|
133
136
|
output_buffer.should have_tag("form li select option[@value='#{author.id}']", /#{author.to_label}/)
|
134
137
|
end
|
135
138
|
end
|
136
139
|
|
137
140
|
it 'should have one option with a "selected" attribute' do
|
138
|
-
output_buffer.should have_tag(
|
141
|
+
output_buffer.should have_tag("form li select[@name='post[author_id]'] option[@selected]", :count => 1)
|
139
142
|
end
|
140
143
|
|
141
144
|
it 'should not singularize the association name' do
|
@@ -149,20 +152,6 @@ describe 'select input' do
|
|
149
152
|
|
150
153
|
output_buffer.should have_tag('form li select#post_author_status_id')
|
151
154
|
end
|
152
|
-
|
153
|
-
it 'should use the "class_name" option' do
|
154
|
-
@new_post.stub!(:status).and_return(@bob)
|
155
|
-
@new_post.stub!(:author_status_id).and_return(@bob.id)
|
156
|
-
|
157
|
-
::Post.stub!(:reflect_on_association).with(:status).and_return(
|
158
|
-
mock('reflection', :options => {:class_name => 'AuthorStatus'}, :klass => ::Author, :macro => :belongs_to))
|
159
|
-
|
160
|
-
semantic_form_for(@new_post) do |builder|
|
161
|
-
concat(builder.input(:status, :as => :select))
|
162
|
-
end
|
163
|
-
|
164
|
-
output_buffer.should have_tag('form li select#post_author_status_id')
|
165
|
-
end
|
166
155
|
end
|
167
156
|
|
168
157
|
describe "for a belongs_to association with :group_by => :author" do
|
@@ -176,6 +165,34 @@ describe 'select input' do
|
|
176
165
|
end
|
177
166
|
end
|
178
167
|
|
168
|
+
describe "for a belongs_to association with :conditions" do
|
169
|
+
before do
|
170
|
+
::Post.stub!(:reflect_on_association).with(:author).and_return do
|
171
|
+
mock = mock('reflection', :options => {:conditions => {:active => true}}, :klass => ::Author, :macro => :belongs_to)
|
172
|
+
mock.stub!(:[]).with(:class_name).and_return("Author")
|
173
|
+
mock
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
it "should call author.find with association conditions" do
|
178
|
+
::Author.should_receive(:merge_conditions).with({:active => true}, nil).and_return(:active => true)
|
179
|
+
::Author.should_receive(:find).with(:all, :conditions => {:active => true})
|
180
|
+
|
181
|
+
semantic_form_for(@new_post) do |builder|
|
182
|
+
concat(builder.input(:author, :as => :select))
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
it "should call author.find with association conditions and find_options conditions" do
|
187
|
+
::Author.should_receive(:merge_conditions).with({:active => true}, {:publisher => true}).and_return(:active => true, :publisher => true)
|
188
|
+
::Author.should_receive(:find).with(:all, :conditions => {:active => true, :publisher => true})
|
189
|
+
|
190
|
+
semantic_form_for(@new_post) do |builder|
|
191
|
+
concat(builder.input(:author, :as => :select, :find_options => {:conditions => {:publisher => true}}))
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
179
196
|
describe 'for a belongs_to association with :group_by => :continent' do
|
180
197
|
before do
|
181
198
|
@authors = [@bob, @fred, @fred, @fred]
|
@@ -12,6 +12,7 @@ describe 'time input' do
|
|
12
12
|
|
13
13
|
describe "general" do
|
14
14
|
before do
|
15
|
+
::I18n.backend.reload!
|
15
16
|
output_buffer.replace ''
|
16
17
|
end
|
17
18
|
|
@@ -94,7 +95,7 @@ describe 'time input' do
|
|
94
95
|
end
|
95
96
|
end
|
96
97
|
|
97
|
-
describe 'when the object has no value' do
|
98
|
+
describe 'when the object has no value (nil)' do
|
98
99
|
it "should select the :selected if provided as a Time" do
|
99
100
|
output_buffer.replace ''
|
100
101
|
@new_post.stub!(:created_at => nil)
|
@@ -118,14 +119,13 @@ describe 'time input' do
|
|
118
119
|
output_buffer.should_not have_tag("form li ol li select#post_created_at_4i option[@selected]")
|
119
120
|
end
|
120
121
|
|
121
|
-
it "should select
|
122
|
+
it "should select nothing if a :selected is not provided" do
|
122
123
|
output_buffer.replace ''
|
123
124
|
@new_post.stub!(:created_at => nil)
|
124
125
|
semantic_form_for(@new_post) do |builder|
|
125
126
|
concat(builder.input(:created_at, :as => :time))
|
126
127
|
end
|
127
|
-
output_buffer.
|
128
|
-
output_buffer.should have_tag("form li ol li select#post_created_at_4i option[@value='#{Time.now.hour.to_s.rjust(2,'0')}'][@selected]", :count => 1)
|
128
|
+
output_buffer.should_not have_tag("form li ol li select option[@selected]")
|
129
129
|
end
|
130
130
|
end
|
131
131
|
|
data/spec/inputs_spec.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
require 'rubygems'
|
3
3
|
|
4
|
-
gem 'activesupport', '2.3.
|
5
|
-
gem 'actionpack', '2.3.
|
4
|
+
gem 'activesupport', '2.3.7'
|
5
|
+
gem 'actionpack', '2.3.7'
|
6
6
|
require 'active_support'
|
7
7
|
require 'action_pack'
|
8
8
|
require 'action_view'
|
@@ -22,6 +22,7 @@ Spec::Runner.configure do |config|
|
|
22
22
|
end
|
23
23
|
|
24
24
|
require File.expand_path(File.join(File.dirname(__FILE__), '../lib/formtastic'))
|
25
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '../lib/formtastic/util'))
|
25
26
|
require File.expand_path(File.join(File.dirname(__FILE__), '../lib/formtastic/layout_helper'))
|
26
27
|
|
27
28
|
|
@@ -126,6 +127,7 @@ module FormtasticSpecHelper
|
|
126
127
|
@new_post.stub!(:new_record?).and_return(true)
|
127
128
|
@new_post.stub!(:errors).and_return(mock('errors', :[] => nil))
|
128
129
|
@new_post.stub!(:author).and_return(nil)
|
130
|
+
@new_post.stub!(:reviewer).and_return(nil)
|
129
131
|
@new_post.stub!(:main_post).and_return(nil)
|
130
132
|
@new_post.stub!(:sub_posts).and_return([]) #TODO should be a mock with methods for adding sub posts
|
131
133
|
|
@@ -146,11 +148,16 @@ module FormtasticSpecHelper
|
|
146
148
|
::Post.stub!(:human_name).and_return('Post')
|
147
149
|
::Post.stub!(:reflect_on_all_validations).and_return([])
|
148
150
|
::Post.stub!(:reflect_on_validations_for).and_return([])
|
151
|
+
::Post.stub!(:reflections).and_return({})
|
149
152
|
::Post.stub!(:reflect_on_association).and_return do |column_name|
|
150
153
|
case column_name
|
151
154
|
when :author, :author_status
|
152
155
|
mock = mock('reflection', :options => {}, :klass => ::Author, :macro => :belongs_to)
|
153
156
|
mock.stub!(:[]).with(:class_name).and_return("Author")
|
157
|
+
mock
|
158
|
+
when :reviewer
|
159
|
+
mock = mock('reflection', :options => {:class_name => 'Author'}, :klass => ::Author, :macro => :belongs_to)
|
160
|
+
mock.stub!(:[]).with(:class_name).and_return("Author")
|
154
161
|
mock
|
155
162
|
when :authors
|
156
163
|
mock('reflection', :options => {}, :klass => ::Author, :macro => :has_and_belongs_to_many)
|
@@ -173,6 +180,9 @@ module FormtasticSpecHelper
|
|
173
180
|
@new_post.stub!(:time_zone)
|
174
181
|
@new_post.stub!(:category_name)
|
175
182
|
@new_post.stub!(:allow_comments)
|
183
|
+
@new_post.stub!(:country)
|
184
|
+
@new_post.stub!(:country_subdivision)
|
185
|
+
@new_post.stub!(:country_code)
|
176
186
|
@new_post.stub!(:column_for_attribute).with(:meta_description).and_return(mock('column', :type => :string, :limit => 255))
|
177
187
|
@new_post.stub!(:column_for_attribute).with(:title).and_return(mock('column', :type => :string, :limit => 50))
|
178
188
|
@new_post.stub!(:column_for_attribute).with(:body).and_return(mock('column', :type => :text))
|
@@ -180,10 +190,17 @@ module FormtasticSpecHelper
|
|
180
190
|
@new_post.stub!(:column_for_attribute).with(:publish_at).and_return(mock('column', :type => :date))
|
181
191
|
@new_post.stub!(:column_for_attribute).with(:time_zone).and_return(mock('column', :type => :string))
|
182
192
|
@new_post.stub!(:column_for_attribute).with(:allow_comments).and_return(mock('column', :type => :boolean))
|
193
|
+
@new_post.stub!(:column_for_attribute).with(:author).and_return(mock('column', :type => :integer))
|
194
|
+
@new_post.stub!(:column_for_attribute).with(:country).and_return(mock('column', :type => :string, :limit => 255))
|
195
|
+
@new_post.stub!(:column_for_attribute).with(:country_subdivision).and_return(mock('column', :type => :string, :limit => 255))
|
196
|
+
@new_post.stub!(:column_for_attribute).with(:country_code).and_return(mock('column', :type => :string, :limit => 255))
|
183
197
|
|
184
198
|
@new_post.stub!(:author).and_return(@bob)
|
185
199
|
@new_post.stub!(:author_id).and_return(@bob.id)
|
186
200
|
|
201
|
+
@new_post.stub!(:reviewer).and_return(@fred)
|
202
|
+
@new_post.stub!(:reviewer_id).and_return(@fred.id)
|
203
|
+
|
187
204
|
@new_post.should_receive(:publish_at=).any_number_of_times
|
188
205
|
@new_post.should_receive(:title=).any_number_of_times
|
189
206
|
@new_post.stub!(:main_post_id).and_return(nil)
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 9
|
8
|
-
-
|
9
|
-
version: 0.9.
|
8
|
+
- 9
|
9
|
+
version: 0.9.9
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Justin French
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-
|
17
|
+
date: 2010-05-25 00:00:00 +10:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -97,6 +97,7 @@ files:
|
|
97
97
|
- lib/formtastic.rb
|
98
98
|
- lib/formtastic/i18n.rb
|
99
99
|
- lib/formtastic/layout_helper.rb
|
100
|
+
- lib/formtastic/util.rb
|
100
101
|
- lib/locale/en.yml
|
101
102
|
- rails/init.rb
|
102
103
|
- spec/buttons_spec.rb
|