formtastic-rails3 0.9.7

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