formtastic 0.9.8 → 0.9.9
Sign up to get free protection for your applications and to get access to all the features.
- 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
|