formtastic-rails3 0.9.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.textile +558 -0
  3. data/Rakefile +103 -0
  4. data/generators/form/USAGE +16 -0
  5. data/generators/form/form_generator.rb +120 -0
  6. data/generators/form/templates/view__form.html.erb +5 -0
  7. data/generators/form/templates/view__form.html.haml +4 -0
  8. data/generators/formtastic/formtastic_generator.rb +24 -0
  9. data/generators/formtastic/templates/formtastic.css +144 -0
  10. data/generators/formtastic/templates/formtastic.rb +54 -0
  11. data/generators/formtastic/templates/formtastic_changes.css +10 -0
  12. data/generators/formtastic_stylesheets/formtastic_stylesheets_generator.rb +16 -0
  13. data/lib/formtastic.rb +1724 -0
  14. data/lib/formtastic/i18n.rb +32 -0
  15. data/lib/formtastic/layout_helper.rb +10 -0
  16. data/lib/generators/formtastic/form/form_generator.rb +85 -0
  17. data/lib/generators/formtastic/form/templates/_form.html.erb +5 -0
  18. data/lib/generators/formtastic/form/templates/_form.html.haml +4 -0
  19. data/lib/generators/formtastic/install/install_generator.rb +23 -0
  20. data/lib/generators/formtastic/install/templates/formtastic.css +144 -0
  21. data/lib/generators/formtastic/install/templates/formtastic.rb +58 -0
  22. data/lib/generators/formtastic/install/templates/formtastic_changes.css +10 -0
  23. data/lib/locale/en.yml +8 -0
  24. data/spec/buttons_spec.rb +149 -0
  25. data/spec/commit_button_spec.rb +377 -0
  26. data/spec/custom_builder_spec.rb +62 -0
  27. data/spec/custom_macros.rb +460 -0
  28. data/spec/defaults_spec.rb +20 -0
  29. data/spec/error_proc_spec.rb +27 -0
  30. data/spec/errors_spec.rb +85 -0
  31. data/spec/form_helper_spec.rb +110 -0
  32. data/spec/i18n_spec.rb +131 -0
  33. data/spec/include_blank_spec.rb +70 -0
  34. data/spec/input_spec.rb +632 -0
  35. data/spec/inputs/boolean_input_spec.rb +93 -0
  36. data/spec/inputs/check_boxes_input_spec.rb +167 -0
  37. data/spec/inputs/country_input_spec.rb +80 -0
  38. data/spec/inputs/currency_input_spec.rb +80 -0
  39. data/spec/inputs/date_input_spec.rb +143 -0
  40. data/spec/inputs/datetime_input_spec.rb +259 -0
  41. data/spec/inputs/file_input_spec.rb +33 -0
  42. data/spec/inputs/hidden_input_spec.rb +52 -0
  43. data/spec/inputs/numeric_input_spec.rb +44 -0
  44. data/spec/inputs/password_input_spec.rb +46 -0
  45. data/spec/inputs/radio_input_spec.rb +152 -0
  46. data/spec/inputs/select_input_spec.rb +470 -0
  47. data/spec/inputs/string_input_spec.rb +47 -0
  48. data/spec/inputs/text_input_spec.rb +33 -0
  49. data/spec/inputs/time_input_spec.rb +128 -0
  50. data/spec/inputs/time_zone_input_spec.rb +102 -0
  51. data/spec/inputs_spec.rb +395 -0
  52. data/spec/label_spec.rb +48 -0
  53. data/spec/layout_helper_spec.rb +42 -0
  54. data/spec/semantic_errors_spec.rb +98 -0
  55. data/spec/semantic_fields_for_spec.rb +44 -0
  56. data/spec/spec.opts +2 -0
  57. data/spec/spec_helper.rb +238 -0
  58. metadata +205 -0
@@ -0,0 +1,470 @@
1
+ # coding: utf-8
2
+ require File.dirname(__FILE__) + '/../spec_helper'
3
+
4
+ describe 'select input' do
5
+
6
+ include FormtasticSpecHelper
7
+
8
+ before do
9
+ @output_buffer = ActiveSupport::SafeBuffer.new
10
+ mock_everything
11
+ end
12
+
13
+ describe 'explicit values' do
14
+ describe 'using an array of values' do
15
+ before do
16
+ @array_with_values = ["Title A", "Title B", "Title C"]
17
+ @array_with_keys_and_values = [["Title D", 1], ["Title E", 2], ["Title F", 3]]
18
+ semantic_form_for(@new_post) do |builder|
19
+ concat(builder.input(:title, :as => :select, :collection => @array_with_values))
20
+ concat(builder.input(:title, :as => :select, :collection => @array_with_keys_and_values))
21
+ end
22
+ end
23
+
24
+ it 'should have a option for each key and/or value' do
25
+ @array_with_values.each do |v|
26
+ output_buffer.should have_tag("form li select option[@value='#{v}']", /^#{v}$/)
27
+ end
28
+ @array_with_keys_and_values.each do |v|
29
+ output_buffer.should have_tag("form li select option[@value='#{v.second}']", /^#{v.first}$/)
30
+ end
31
+ end
32
+ end
33
+
34
+ describe 'using a range' do
35
+ before do
36
+ @range_with_values = 1..5
37
+ semantic_form_for(@new_post) do |builder|
38
+ concat(builder.input(:title, :as => :select, :collection => @range_with_values))
39
+ end
40
+ end
41
+
42
+ it 'should have an option for each value' do
43
+ @range_with_values.each do |v|
44
+ output_buffer.should have_tag("form li select option[@value='#{v}']", /^#{v}$/)
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ describe 'for boolean columns' do
51
+ describe 'default formtastic locale' do
52
+ before do
53
+ # Note: Works, but something like Formtastic.root.join(...) would probably be "safer".
54
+ ::I18n.load_path = [File.join(File.dirname(__FILE__), *%w[.. .. lib locale en.yml])]
55
+ ::I18n.backend.send(:init_translations)
56
+
57
+ semantic_form_for(@new_post) do |builder|
58
+ concat(builder.input(:published, :as => :select))
59
+ end
60
+ end
61
+
62
+ after do
63
+ ::I18n.backend.store_translations :en, {}
64
+ end
65
+
66
+ it 'should render a select with at least options: true/false' do
67
+ output_buffer.should have_tag("form li select option[@value='true']", /^Yes$/)
68
+ output_buffer.should have_tag("form li select option[@value='false']", /^No$/)
69
+ end
70
+ end
71
+
72
+ describe 'custom locale' do
73
+ before do
74
+ @boolean_select_labels = {:yes => 'Yep', :no => 'Nope'}
75
+ ::I18n.backend.store_translations :en, :formtastic => @boolean_select_labels
76
+
77
+ semantic_form_for(@new_post) do |builder|
78
+ concat(builder.input(:published, :as => :select))
79
+ end
80
+ end
81
+
82
+ after do
83
+ ::I18n.backend.store_translations :en, {}
84
+ end
85
+
86
+ it 'should render a select with at least options: true/false' do
87
+ output_buffer.should have_tag("form li select option[@value='true']", /#{@boolean_select_labels[:yes]}/)
88
+ output_buffer.should have_tag("form li select option[@value='false']", /#{@boolean_select_labels[:no]}/)
89
+ end
90
+ end
91
+ end
92
+
93
+ describe 'for a belongs_to association' do
94
+ before do
95
+ semantic_form_for(@new_post) do |builder|
96
+ concat(builder.input(:author, :as => :select))
97
+ end
98
+ end
99
+
100
+ it_should_have_input_wrapper_with_class("select")
101
+ it_should_have_input_wrapper_with_id("post_author_input")
102
+ it_should_have_label_with_text(/Author/)
103
+ it_should_have_label_for('post_author_id')
104
+ it_should_apply_error_logic_for_input_type(:select)
105
+ it_should_call_find_on_association_class_when_no_collection_is_provided(:select)
106
+ it_should_use_the_collection_when_provided(:select, 'option')
107
+
108
+ it 'should have a select inside the wrapper' do
109
+ output_buffer.should have_tag('form li select')
110
+ output_buffer.should have_tag('form li select#post_author_id')
111
+ end
112
+
113
+ it 'should have a valid name' do
114
+ output_buffer.should have_tag("form li select[@name='post[author_id]']")
115
+ output_buffer.should_not have_tag("form li select[@name='post[author_id][]']")
116
+ end
117
+
118
+ it 'should not create a multi-select' do
119
+ output_buffer.should_not have_tag('form li select[@multiple]')
120
+ end
121
+
122
+ it 'should create a select without size' do
123
+ output_buffer.should_not have_tag('form li select[@size]')
124
+ end
125
+
126
+ it 'should have a blank option' do
127
+ output_buffer.should have_tag("form li select option[@value='']")
128
+ end
129
+
130
+ it 'should have a select option for each Author' do
131
+ output_buffer.should have_tag('form li select option', :count => ::Author.find(:all).size + 1)
132
+ ::Author.find(:all).each do |author|
133
+ output_buffer.should have_tag("form li select option[@value='#{author.id}']", /#{author.to_label}/)
134
+ end
135
+ end
136
+
137
+ it 'should have one option with a "selected" attribute' do
138
+ output_buffer.should have_tag('form li select option[@selected]', :count => 1)
139
+ end
140
+
141
+ it 'should not singularize the association name' do
142
+ @new_post.stub!(:author_status).and_return(@bob)
143
+ @new_post.stub!(:author_status_id).and_return(@bob.id)
144
+ @new_post.stub!(:column_for_attribute).and_return(mock('column', :type => :integer, :limit => 255))
145
+
146
+ semantic_form_for(@new_post) do |builder|
147
+ concat(builder.input(:author_status, :as => :select))
148
+ end
149
+
150
+ output_buffer.should have_tag('form li select#post_author_status_id')
151
+ 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
+ end
167
+
168
+ describe "for a belongs_to association with :group_by => :author" do
169
+ it "should call author.posts" do
170
+ [@freds_post].each { |post| post.stub!(:to_label).and_return("Post - #{post.id}") }
171
+ @fred.should_receive(:posts)
172
+
173
+ semantic_form_for(@new_post) do |builder|
174
+ concat(builder.input(:main_post, :as => :select, :group_by => :author ) )
175
+ end
176
+ end
177
+ end
178
+
179
+ describe 'for a belongs_to association with :group_by => :continent' do
180
+ before do
181
+ @authors = [@bob, @fred, @fred, @fred]
182
+ ::Author.stub!(:find).and_return(@authors)
183
+ @continent_names = %w(Europe Africa)
184
+ @continents = (0..1).map { |i| c = ::Continent.new; c.stub!(:id).and_return(100 - i);c }
185
+ @authors[0..1].each_with_index { |author, i| author.stub!(:continent).and_return(@continents[i]) }
186
+ ::Continent.stub!(:reflect_on_all_associations).and_return {|macro| mock('reflection', :klass => Author) if macro == :has_many}
187
+ ::Continent.stub!(:reflect_on_association).and_return {|column_name| mock('reflection', :klass => Author) if column_name == :authors}
188
+ ::Author.stub!(:reflect_on_association).and_return { |column_name| mock('reflection', :options => {}, :klass => Continent, :macro => :belongs_to) if column_name == :continent }
189
+
190
+
191
+ @continents.each_with_index do |continent, i|
192
+ continent.stub!(:to_label).and_return(@continent_names[i])
193
+ continent.stub!(:authors).and_return([@authors[i]])
194
+ end
195
+
196
+ semantic_form_for(@new_post) do |builder|
197
+ concat(builder.input(:author, :as => :select, :group_by => :continent ) )
198
+ concat(builder.input(:author, :as => :select, :group_by => :continent, :group_label_method => :id ) )
199
+ end
200
+ end
201
+
202
+ it_should_have_input_wrapper_with_class("select")
203
+ it_should_have_input_wrapper_with_id("post_author_input")
204
+ it_should_have_label_with_text(/Author/)
205
+ it_should_have_label_for('post_author_id')
206
+
207
+ # TODO, need to find a way to repeat some of the specs and logic from the belongs_to specs without grouping
208
+
209
+ 0.upto(1) do |i|
210
+ it 'should have all option groups and the right values' do
211
+ output_buffer.should have_tag("form li select optgroup[@label='#{@continent_names[i]}']", @authors[i].to_label)
212
+ end
213
+
214
+ it 'should have custom group labels' do
215
+ output_buffer.should have_tag("form li select optgroup[@label='#{@continents[i].id}']", @authors[i].to_label)
216
+ end
217
+ end
218
+
219
+ it 'should have no duplicate groups' do
220
+ output_buffer.should have_tag('form li select optgroup', :count => 4)
221
+ end
222
+
223
+ it 'should sort the groups on the label method' do
224
+ output_buffer.should have_tag("form li select optgroup[@label='Africa']")
225
+ output_buffer.should have_tag("form li select optgroup[@label='99']")
226
+ end
227
+
228
+ it 'should call find with :include for more optimized queries' do
229
+ Author.should_receive(:find).with(:all, :include => :continent)
230
+
231
+ semantic_form_for(@new_post) do |builder|
232
+ concat(builder.input(:author, :as => :select, :group_by => :continent ) )
233
+ end
234
+ end
235
+ end
236
+
237
+ describe 'for a has_many association' do
238
+ before do
239
+ semantic_form_for(@fred) do |builder|
240
+ concat(builder.input(:posts, :as => :select))
241
+ end
242
+ end
243
+
244
+ it_should_have_input_wrapper_with_class("select")
245
+ it_should_have_input_wrapper_with_id("author_posts_input")
246
+ it_should_have_label_with_text(/Post/)
247
+ it_should_have_label_for('author_post_ids')
248
+ it_should_apply_error_logic_for_input_type(:select)
249
+ it_should_call_find_on_association_class_when_no_collection_is_provided(:select)
250
+ it_should_use_the_collection_when_provided(:select, 'option')
251
+
252
+ it 'should have a select inside the wrapper' do
253
+ output_buffer.should have_tag('form li select')
254
+ output_buffer.should have_tag('form li select#author_post_ids')
255
+ end
256
+
257
+ it 'should have a multi-select select' do
258
+ output_buffer.should have_tag('form li select[@multiple="multiple"]')
259
+ end
260
+
261
+ it 'should have a select option for each Post' do
262
+ output_buffer.should have_tag('form li select option', :count => ::Post.find(:all).size)
263
+ ::Post.find(:all).each do |post|
264
+ output_buffer.should have_tag("form li select option[@value='#{post.id}']", /#{post.to_label}/)
265
+ end
266
+ end
267
+
268
+ it 'should not have a blank option' do
269
+ output_buffer.should_not have_tag("form li select option[@value='']")
270
+ end
271
+
272
+ it 'should have one option with a "selected" attribute' do
273
+ output_buffer.should have_tag('form li select option[@selected]', :count => 1)
274
+ end
275
+ end
276
+
277
+ describe 'for a has_and_belongs_to_many association' do
278
+ before do
279
+ semantic_form_for(@freds_post) do |builder|
280
+ concat(builder.input(:authors, :as => :select))
281
+ end
282
+ end
283
+
284
+ it_should_have_input_wrapper_with_class("select")
285
+ it_should_have_input_wrapper_with_id("post_authors_input")
286
+ it_should_have_label_with_text(/Author/)
287
+ it_should_have_label_for('post_author_ids')
288
+ it_should_apply_error_logic_for_input_type(:select)
289
+ it_should_call_find_on_association_class_when_no_collection_is_provided(:select)
290
+ it_should_use_the_collection_when_provided(:select, 'option')
291
+
292
+ it 'should have a select inside the wrapper' do
293
+ output_buffer.should have_tag('form li select')
294
+ output_buffer.should have_tag('form li select#post_author_ids')
295
+ end
296
+
297
+ it 'should have a multi-select select' do
298
+ output_buffer.should have_tag('form li select[@multiple="multiple"]')
299
+ end
300
+
301
+ it 'should have a select option for each Author' do
302
+ output_buffer.should have_tag('form li select option', :count => ::Author.find(:all).size)
303
+ ::Author.find(:all).each do |author|
304
+ output_buffer.should have_tag("form li select option[@value='#{author.id}']", /#{author.to_label}/)
305
+ end
306
+ end
307
+
308
+ it 'should not have a blank option' do
309
+ output_buffer.should_not have_tag("form li select option[@value='']")
310
+ end
311
+
312
+ it 'should have one option with a "selected" attribute' do
313
+ output_buffer.should have_tag('form li select option[@selected]', :count => 1)
314
+ end
315
+ end
316
+
317
+ describe 'when :prompt => "choose something" is set' do
318
+ before do
319
+ @new_post.stub!(:author_id).and_return(nil)
320
+ semantic_form_for(@new_post) do |builder|
321
+ concat(builder.input(:author, :as => :select, :prompt => "choose author"))
322
+ end
323
+ end
324
+
325
+ it 'should have a select with prompt' do
326
+ output_buffer.should have_tag("form li select option[@value='']", /choose author/)
327
+ end
328
+
329
+ it 'should not have a blank select option' do
330
+ output_buffer.should_not have_tag("form li select option[@value='']", "")
331
+ end
332
+ end
333
+
334
+ describe 'when no object is given' do
335
+ before(:each) do
336
+ semantic_form_for(:project, :url => 'http://test.host') do |builder|
337
+ concat(builder.input(:author, :as => :select, :collection => ::Author.find(:all)))
338
+ end
339
+ end
340
+
341
+ it 'should generate label' do
342
+ output_buffer.should have_tag('form li label', /Author/)
343
+ output_buffer.should have_tag("form li label[@for='project_author']")
344
+ end
345
+
346
+ it 'should generate select inputs' do
347
+ output_buffer.should have_tag('form li select#project_author')
348
+ output_buffer.should have_tag('form li select option', :count => ::Author.find(:all).size + 1)
349
+ end
350
+
351
+ it 'should generate an option to each item' do
352
+ ::Author.find(:all).each do |author|
353
+ output_buffer.should have_tag("form li select option[@value='#{author.id}']", /#{author.to_label}/)
354
+ end
355
+ end
356
+ end
357
+
358
+ describe 'when :selected is set' do
359
+ before do
360
+ @output_buffer = ActiveSupport::SafeBuffer.new
361
+ end
362
+
363
+ describe "no selected items" do
364
+ before do
365
+ @new_post.stub!(:author_id).and_return(nil)
366
+ semantic_form_for(@new_post) do |builder|
367
+ concat(builder.input(:author, :as => :select, :selected => nil))
368
+ end
369
+ end
370
+
371
+ it 'should not have any selected item(s)' do
372
+ output_buffer.should_not have_tag("form li select option[@selected='selected']")
373
+ end
374
+ end
375
+
376
+ describe "single selected item" do
377
+ before do
378
+ @new_post.stub!(:author_id).and_return(nil)
379
+ semantic_form_for(@new_post) do |builder|
380
+ concat(builder.input(:author, :as => :select, :selected => @bob.id))
381
+ end
382
+ end
383
+
384
+ it 'should have a selected item; the specified one' do
385
+ output_buffer.should have_tag("form li select option[@selected='selected']", :count => 1)
386
+ output_buffer.should have_tag("form li select option[@selected='selected']", /bob/i)
387
+ output_buffer.should have_tag("form li select option[@selected='selected'][@value='#{@bob.id}']")
388
+ end
389
+ end
390
+
391
+ describe "multiple selected items" do
392
+
393
+ describe "when :multiple => false" do
394
+ before do
395
+ @new_post.stub!(:author_ids).and_return(nil)
396
+
397
+ semantic_form_for(@new_post) do |builder|
398
+ concat(builder.input(:authors, :as => :select, :selected => [@bob.id, @fred.id], :multiple => false))
399
+ end
400
+ end
401
+
402
+ it "should only select the first value" do
403
+ output_buffer.should have_tag("form li select option[@selected='selected']", :count => 1)
404
+ output_buffer.should have_tag("form li select:not([@multiple]) option[@selected='selected']", /bob/i)
405
+ output_buffer.should have_tag("form li select:not([@multiple]) option[@selected='selected'][@value='#{@bob.id}']")
406
+ end
407
+ end
408
+
409
+ describe "when :multiple => true" do
410
+ before do
411
+ @new_post.stub!(:author_ids).and_return(nil)
412
+
413
+ semantic_form_for(@new_post) do |builder|
414
+ concat(builder.input(:authors, :as => :select, :selected => [@bob.id, @fred.id]))
415
+ end
416
+ end
417
+
418
+ it "should have multiple items selected; the specified ones" do
419
+ output_buffer.should have_tag("form li select option[@selected='selected']", :count => 2)
420
+ output_buffer.should have_tag("form li select[@multiple] option[@selected='selected']", /bob/i)
421
+ output_buffer.should have_tag("form li select[@multiple] option[@selected='selected'][@value='#{@bob.id}']")
422
+ output_buffer.should have_tag("form li select[@multiple] option[@selected='selected']", /fred/i)
423
+ output_buffer.should have_tag("form li select[@multiple] option[@selected='selected'][@value='#{@fred.id}']")
424
+ end
425
+ end
426
+
427
+ end
428
+
429
+ end
430
+
431
+ describe "enum" do
432
+ before do
433
+ @output_buffer = ActiveSupport::SafeBuffer.new
434
+ @some_meta_descriptions = ["One", "Two", "Three"]
435
+ @new_post.stub!(:meta_description).any_number_of_times
436
+ end
437
+
438
+ describe ":as is not set" do
439
+ before do
440
+ semantic_form_for(@new_post) do |builder|
441
+ concat(builder.input(:meta_description, :collection => @some_meta_descriptions))
442
+ end
443
+ semantic_form_for(:project, :url => 'http://test.host') do |builder|
444
+ concat(builder.input(:meta_description, :collection => @some_meta_descriptions))
445
+ end
446
+ end
447
+
448
+ it "should render a select field" do
449
+ output_buffer.should have_tag("form li select", :count => 2)
450
+ end
451
+ end
452
+
453
+ describe ":as is set" do
454
+ before do
455
+ # Should not be a case, but just checking :as got highest priority in setting input type.
456
+ semantic_form_for(@new_post) do |builder|
457
+ concat(builder.input(:meta_description, :as => :string, :collection => @some_meta_descriptions))
458
+ end
459
+ semantic_form_for(:project, :url => 'http://test.host') do |builder|
460
+ concat(builder.input(:meta_description, :as => :string, :collection => @some_meta_descriptions))
461
+ end
462
+ end
463
+
464
+ it "should render a text field" do
465
+ output_buffer.should have_tag("form li input[@type='text']", :count => 2)
466
+ end
467
+ end
468
+ end
469
+
470
+ end