formtastic 3.1.3 → 3.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/.travis.yml +12 -10
  2. data/Appraisals +11 -7
  3. data/CHANGELOG +12 -0
  4. data/DEPRECATIONS +3 -0
  5. data/{README.textile → README.md} +629 -616
  6. data/formtastic.gemspec +3 -3
  7. data/gemfiles/rails_3.2.gemfile +1 -0
  8. data/gemfiles/rails_edge.gemfile +5 -0
  9. data/lib/formtastic.rb +4 -0
  10. data/lib/formtastic/form_builder.rb +3 -0
  11. data/lib/formtastic/helpers.rb +1 -1
  12. data/lib/formtastic/helpers/enum.rb +13 -0
  13. data/lib/formtastic/helpers/fieldset_wrapper.rb +6 -6
  14. data/lib/formtastic/helpers/form_helper.rb +1 -1
  15. data/lib/formtastic/helpers/input_helper.rb +5 -1
  16. data/lib/formtastic/helpers/inputs_helper.rb +16 -20
  17. data/lib/formtastic/inputs/base/choices.rb +1 -1
  18. data/lib/formtastic/inputs/base/collections.rb +41 -4
  19. data/lib/formtastic/inputs/base/html.rb +7 -6
  20. data/lib/formtastic/inputs/base/naming.rb +4 -4
  21. data/lib/formtastic/inputs/base/options.rb +2 -3
  22. data/lib/formtastic/inputs/base/validations.rb +19 -3
  23. data/lib/formtastic/inputs/check_boxes_input.rb +10 -2
  24. data/lib/formtastic/inputs/country_input.rb +3 -1
  25. data/lib/formtastic/inputs/radio_input.rb +20 -0
  26. data/lib/formtastic/inputs/select_input.rb +28 -0
  27. data/lib/formtastic/inputs/time_zone_input.rb +16 -6
  28. data/lib/formtastic/localizer.rb +15 -15
  29. data/lib/formtastic/namespaced_class_finder.rb +1 -1
  30. data/lib/formtastic/version.rb +1 -1
  31. data/lib/generators/formtastic/form/form_generator.rb +1 -1
  32. data/lib/generators/formtastic/input/input_generator.rb +46 -0
  33. data/lib/generators/templates/formtastic.rb +10 -7
  34. data/lib/generators/templates/input.rb +19 -0
  35. data/spec/fast_spec_helper.rb +12 -0
  36. data/spec/generators/formtastic/input/input_generator_spec.rb +124 -0
  37. data/spec/helpers/form_helper_spec.rb +4 -4
  38. data/spec/inputs/base/collections_spec.rb +76 -0
  39. data/spec/inputs/base/validations_spec.rb +342 -0
  40. data/spec/inputs/check_boxes_input_spec.rb +66 -20
  41. data/spec/inputs/country_input_spec.rb +4 -4
  42. data/spec/inputs/radio_input_spec.rb +28 -0
  43. data/spec/inputs/readonly_spec.rb +50 -0
  44. data/spec/inputs/select_input_spec.rb +71 -11
  45. data/spec/inputs/time_zone_input_spec.rb +35 -9
  46. data/spec/spec_helper.rb +2 -30
  47. data/spec/support/shared_examples.rb +69 -0
  48. metadata +23 -12
  49. data/spec/support/deferred_garbage_collection.rb +0 -21
@@ -0,0 +1,12 @@
1
+ # encoding: utf-8
2
+ $LOAD_PATH << 'lib/formtastic'
3
+ require 'active_support/all'
4
+ require 'localized_string'
5
+ require 'inputs'
6
+ require 'helpers'
7
+
8
+ class MyInput
9
+ include Formtastic::Inputs::Base
10
+ end
11
+
12
+ I18n.enforce_available_locales = false if I18n.respond_to?(:enforce_available_locales)
@@ -0,0 +1,124 @@
1
+ require 'spec_helper'
2
+
3
+ require 'generators/formtastic/input/input_generator'
4
+
5
+ describe Formtastic::InputGenerator do
6
+ include FormtasticSpecHelper
7
+
8
+ destination File.expand_path("../../../../../tmp", __FILE__)
9
+
10
+ before do
11
+ prepare_destination
12
+ end
13
+
14
+ after do
15
+ FileUtils.rm_rf(File.expand_path("../../../../../tmp", __FILE__))
16
+ end
17
+
18
+ describe 'without file name' do
19
+ it 'should raise Thor::RequiredArgumentMissingError' do
20
+ lambda { run_generator }.should raise_error(Thor::RequiredArgumentMissingError)
21
+ end
22
+ end
23
+
24
+ describe "input generator with underscore definition" do
25
+ before { run_generator %w(hat_size)}
26
+
27
+ describe 'generate an input in its respective folder' do
28
+ subject{ file('app/inputs/hat_size_input.rb')}
29
+ it { should exist}
30
+ it { should contain "class HatSizeInput"}
31
+ it { should contain "def to_html"}
32
+ it { should contain "include Formtastic::Inputs::Base"}
33
+ it { should_not contain "super"}
34
+ end
35
+ end
36
+
37
+ describe "input generator with camelcase definition" do
38
+ before { run_generator %w(HatSize)}
39
+
40
+ describe 'generate an input in its respective folder' do
41
+ subject{ file('app/inputs/hat_size_input.rb')}
42
+ it { should exist}
43
+ it { should contain "class HatSizeInput"}
44
+ end
45
+ end
46
+
47
+ describe "input generator with camelcase Input name sufixed" do
48
+ before { run_generator %w(HatSizeInput)}
49
+
50
+ describe 'generate an input in its respective folder' do
51
+ subject{ file('app/inputs/hat_size_input.rb')}
52
+ it { should exist}
53
+ it { should contain "class HatSizeInput"}
54
+ end
55
+ end
56
+
57
+ describe "input generator with underscore _input name sufixed" do
58
+ before { run_generator %w(hat_size_input)}
59
+
60
+ describe 'generate an input in its respective folder' do
61
+ subject{ file('app/inputs/hat_size_input.rb')}
62
+ it { should exist}
63
+ it { should contain "class HatSizeInput"}
64
+ end
65
+ end
66
+
67
+ describe "input generator with underscore input name sufixed" do
68
+ before { run_generator %w(hat_sizeinput)}
69
+
70
+ describe 'generate an input in its respective folder' do
71
+ subject{ file('app/inputs/hat_size_input.rb')}
72
+ it { should exist}
73
+ it { should contain "class HatSizeInput"}
74
+ end
75
+ end
76
+
77
+ describe "override an existing input using extend" do
78
+ before { run_generator %w(string --extend)}
79
+
80
+ describe 'app/inputs/string_input.rb' do
81
+ subject{ file('app/inputs/string_input.rb')}
82
+ it { should exist }
83
+ it { should contain "class StringInput < Formtastic::Inputs::StringInput" }
84
+ it { should contain "def to_html" }
85
+ it { should_not contain "include Formtastic::Inputs::Base" }
86
+ it { should contain "super" }
87
+ it { should_not contain "def input_html_options" }
88
+ end
89
+ end
90
+
91
+ describe "extend an existing input" do
92
+ before { run_generator %w(FlexibleText --extend string)}
93
+
94
+ describe 'app/inputs/flexible_text_input.rb' do
95
+ subject{ file('app/inputs/flexible_text_input.rb')}
96
+ it { should contain "class FlexibleTextInput < Formtastic::Inputs::StringInput" }
97
+ it { should contain "def input_html_options" }
98
+ it { should_not contain "include Formtastic::Inputs::Base" }
99
+ it { should_not contain "def to_html" }
100
+ end
101
+ end
102
+
103
+ describe "provide a slashed namespace" do
104
+ before { run_generator %w(stuff/foo)}
105
+
106
+ describe 'app/inputs/stuff/foo_input.rb' do
107
+ subject{ file('app/inputs/stuff/foo_input.rb')}
108
+ it {should exist}
109
+ it { should contain "class Stuff::FooInput" }
110
+ it { should contain "include Formtastic::Inputs::Base" }
111
+ end
112
+ end
113
+
114
+ describe "provide a camelized namespace" do
115
+ before { run_generator %w(Stuff::Foo)}
116
+
117
+ describe 'app/inputs/stuff/foo_input.rb' do
118
+ subject{ file('app/inputs/stuff/foo_input.rb')}
119
+ it {should exist}
120
+ it { should contain "class Stuff::FooInput" }
121
+ it { should contain "include Formtastic::Inputs::Base" }
122
+ end
123
+ end
124
+ end
@@ -24,7 +24,7 @@ describe 'FormHelper' do
24
24
  output_buffer.should have_tag("form.formtastic")
25
25
  end
26
26
 
27
- it 'adds a "novalidate" attribute to the generated form when configured to do so' do
27
+ it 'does not add "novalidate" attribute to the generated form when configured to do so' do
28
28
  with_config :perform_browser_validations, true do
29
29
  concat(semantic_form_for(@new_post, :url => '/hello') do |builder|
30
30
  end)
@@ -32,7 +32,7 @@ describe 'FormHelper' do
32
32
  end
33
33
  end
34
34
 
35
- it 'omits a "novalidate" attribute to the generated form when configured to do so' do
35
+ it 'adds "novalidate" attribute to the generated form when configured to do so' do
36
36
  with_config :perform_browser_validations, false do
37
37
  concat(semantic_form_for(@new_post, :url => '/hello') do |builder|
38
38
  end)
@@ -40,7 +40,7 @@ describe 'FormHelper' do
40
40
  end
41
41
  end
42
42
 
43
- it 'allows form HTML to override "novalidate" attribute when configure to not show' do
43
+ it 'allows form HTML to override "novalidate" attribute when configured to validate' do
44
44
  with_config :perform_browser_validations, false do
45
45
  concat(semantic_form_for(@new_post, :url => '/hello', :html => { :novalidate => true }) do |builder|
46
46
  end)
@@ -48,7 +48,7 @@ describe 'FormHelper' do
48
48
  end
49
49
  end
50
50
 
51
- it 'allows form HTML to override "novalidate" attribute when configure to show' do
51
+ it 'allows form HTML to override "novalidate" attribute when configured to not validate' do
52
52
  with_config :perform_browser_validations, true do
53
53
  concat(semantic_form_for(@new_post, :url => '/hello', :html => { :novalidate => false }) do |builder|
54
54
  end)
@@ -0,0 +1,76 @@
1
+ require 'fast_spec_helper'
2
+ require 'inputs/base/collections'
3
+
4
+ class MyInput
5
+ include Formtastic::Inputs::Base::Collections
6
+ end
7
+
8
+ describe MyInput do
9
+ let(:builder) { double }
10
+ let(:template) { double }
11
+ let(:model_class) { double }
12
+ let(:model) { double(:class => model_class) }
13
+ let(:model_name) { "post" }
14
+ let(:method) { double }
15
+ let(:options) { Hash.new }
16
+
17
+ let(:instance) { MyInput.new(builder, template, model, model_name, method, options) }
18
+
19
+ # class Whatever < ActiveRecord::Base
20
+ # enum :status => [:active, :archived]
21
+ # end
22
+ #
23
+ # Whatever.statuses
24
+ #
25
+ # Whatever.new.status
26
+ #
27
+ # f.input :status
28
+ describe "#collection_from_enum" do
29
+
30
+ let(:method) { :status }
31
+
32
+ context "when an enum is defined for the method" do
33
+ before do
34
+ statuses = ActiveSupport::HashWithIndifferentAccess.new("active"=>0, "inactive"=>1)
35
+ model_class.stub(:statuses) { statuses }
36
+ model.stub(:defined_enums) { {"status" => statuses } }
37
+ end
38
+
39
+ context 'no translations available' do
40
+ it 'returns an Array of EnumOption objects based on the enum options hash' do
41
+ instance.collection_from_enum.should eq [["Active", "active"],["Inactive", "inactive"]]
42
+ end
43
+ end
44
+
45
+ context 'with translations' do
46
+ before do
47
+ ::I18n.backend.store_translations :en, :activerecord => {
48
+ :attributes => {
49
+ :post => {
50
+ :statuses => {
51
+ :active => "I am active",
52
+ :inactive => "I am inactive"
53
+ }
54
+ }
55
+ }
56
+ }
57
+ end
58
+ it 'returns an Array of EnumOption objects based on the enum options hash' do
59
+ instance.collection_from_enum.should eq [["I am active", "active"],["I am inactive", "inactive"]]
60
+ end
61
+
62
+ after do
63
+ ::I18n.backend.store_translations :en, {}
64
+ end
65
+ end
66
+ end
67
+
68
+ context "when an enum is not defined" do
69
+ it 'returns nil' do
70
+ instance.collection_from_enum.should eq nil
71
+ end
72
+ end
73
+ end
74
+
75
+ end
76
+
@@ -0,0 +1,342 @@
1
+ require 'fast_spec_helper'
2
+ require 'inputs/base/validations'
3
+
4
+ class MyInput
5
+ include Formtastic::Inputs::Base::Validations
6
+ end
7
+
8
+ describe MyInput do
9
+ let(:builder) { double }
10
+ let(:template) { double }
11
+ let(:model_class) { double }
12
+ let(:model) { double(:class => model_class) }
13
+ let(:model_name) { "post" }
14
+ let(:method) { double }
15
+ let(:options) { Hash.new }
16
+ let(:validator) { double }
17
+ let(:instance) { MyInput.new(builder, template, model, model_name, method, options) }
18
+
19
+ describe '#required?' do
20
+ context 'with a single validator' do
21
+ before :each do
22
+ allow(instance).to receive(:validations?).and_return(:true)
23
+ allow(instance).to receive(:validations).and_return([validator])
24
+ end
25
+
26
+ context 'with options[:required] being true' do
27
+ let(:options) { {required: true} }
28
+
29
+ it 'is required' do
30
+ expect(instance.required?).to be_truthy
31
+ end
32
+ end
33
+
34
+ context 'with options[:required] being false' do
35
+ let(:options) { {required: false} }
36
+
37
+ it 'is not required' do
38
+ expect(instance.required?).to be_falsey
39
+ end
40
+ end
41
+
42
+ context 'with negated validation' do
43
+ it 'is not required' do
44
+ instance.not_required_through_negated_validation!
45
+ expect(instance.required?).to be_falsey
46
+ end
47
+ end
48
+
49
+ context 'with presence validator' do
50
+ let (:validator) { double(options: {}, kind: :presence) }
51
+
52
+ it 'is required' do
53
+ expect(instance.required?).to be_truthy
54
+ end
55
+
56
+ context 'with options[:on] as symbol' do
57
+ context 'with save context' do
58
+ let (:validator) { double(options: {on: :save}, kind: :presence) }
59
+
60
+ it 'is required' do
61
+ expect(instance.required?).to be_truthy
62
+ end
63
+ end
64
+
65
+ context 'with create context' do
66
+ let (:validator) { double(options: {on: :create}, kind: :presence) }
67
+
68
+ it 'is required for new records' do
69
+ allow(model).to receive(:new_record?).and_return(true)
70
+ expect(instance.required?).to be_truthy
71
+ end
72
+
73
+ it 'is not required for existing records' do
74
+ allow(model).to receive(:new_record?).and_return(false)
75
+ expect(instance.required?).to be_falsey
76
+ end
77
+ end
78
+
79
+ context 'with update context' do
80
+ let (:validator) { double(options: {on: :update}, kind: :presence) }
81
+
82
+ it 'is not required for new records' do
83
+ allow(model).to receive(:new_record?).and_return(true)
84
+ expect(instance.required?).to be_falsey
85
+ end
86
+
87
+ it 'is required for existing records' do
88
+ allow(model).to receive(:new_record?).and_return(false)
89
+ expect(instance.required?).to be_truthy
90
+ end
91
+ end
92
+ end
93
+
94
+ context 'with options[:on] as array' do
95
+ context 'with save context' do
96
+ let (:validator) { double(options: {on: [:save]}, kind: :presence) }
97
+
98
+ it 'is required' do
99
+ expect(instance.required?).to be_truthy
100
+ end
101
+ end
102
+
103
+ context 'with create context' do
104
+ let (:validator) { double(options: {on: [:create]}, kind: :presence) }
105
+
106
+ it 'is required for new records' do
107
+ allow(model).to receive(:new_record?).and_return(true)
108
+ expect(instance.required?).to be_truthy
109
+ end
110
+
111
+ it 'is not required for existing records' do
112
+ allow(model).to receive(:new_record?).and_return(false)
113
+ expect(instance.required?).to be_falsey
114
+ end
115
+ end
116
+
117
+ context 'with update context' do
118
+ let (:validator) { double(options: {on: [:update]}, kind: :presence) }
119
+
120
+ it 'is not required for new records' do
121
+ allow(model).to receive(:new_record?).and_return(true)
122
+ expect(instance.required?).to be_falsey
123
+ end
124
+
125
+ it 'is required for existing records' do
126
+ allow(model).to receive(:new_record?).and_return(false)
127
+ expect(instance.required?).to be_truthy
128
+ end
129
+ end
130
+
131
+ context 'with save and create context' do
132
+ let (:validator) { double(options: {on: [:save, :create]}, kind: :presence) }
133
+
134
+ it 'is required for new records' do
135
+ allow(model).to receive(:new_record?).and_return(true)
136
+ expect(instance.required?).to be_truthy
137
+ end
138
+
139
+ it 'is required for existing records' do
140
+ allow(model).to receive(:new_record?).and_return(false)
141
+ expect(instance.required?).to be_truthy
142
+ end
143
+ end
144
+
145
+ context 'with save and update context' do
146
+ let (:validator) { double(options: {on: [:save, :create]}, kind: :presence) }
147
+
148
+ it 'is required for new records' do
149
+ allow(model).to receive(:new_record?).and_return(true)
150
+ expect(instance.required?).to be_truthy
151
+ end
152
+
153
+ it 'is required for existing records' do
154
+ allow(model).to receive(:new_record?).and_return(false)
155
+ expect(instance.required?).to be_truthy
156
+ end
157
+ end
158
+
159
+ context 'with create and update context' do
160
+ let (:validator) { double(options: {on: [:create, :update]}, kind: :presence) }
161
+
162
+ it 'is required for new records' do
163
+ allow(model).to receive(:new_record?).and_return(true)
164
+ expect(instance.required?).to be_truthy
165
+ end
166
+
167
+ it 'is required for existing records' do
168
+ allow(model).to receive(:new_record?).and_return(false)
169
+ expect(instance.required?).to be_truthy
170
+ end
171
+ end
172
+
173
+ context 'with save and other context' do
174
+ let (:validator) { double(options: {on: [:save, :foo]}, kind: :presence) }
175
+
176
+ it 'is required for new records' do
177
+ allow(model).to receive(:new_record?).and_return(true)
178
+ expect(instance.required?).to be_truthy
179
+ end
180
+
181
+ it 'is required for existing records' do
182
+ allow(model).to receive(:new_record?).and_return(false)
183
+ expect(instance.required?).to be_truthy
184
+ end
185
+ end
186
+
187
+ context 'with create and other context' do
188
+ let (:validator) { double(options: {on: [:create, :foo]}, kind: :presence) }
189
+
190
+ it 'is required for new records' do
191
+ allow(model).to receive(:new_record?).and_return(true)
192
+ expect(instance.required?).to be_truthy
193
+ end
194
+
195
+ it 'is not required for existing records' do
196
+ allow(model).to receive(:new_record?).and_return(false)
197
+ expect(instance.required?).to be_falsey
198
+ end
199
+ end
200
+
201
+ context 'with update and other context' do
202
+ let (:validator) { double(options: {on: [:update, :foo]}, kind: :presence) }
203
+
204
+ it 'is not required for new records' do
205
+ allow(model).to receive(:new_record?).and_return(true)
206
+ expect(instance.required?).to be_falsey
207
+ end
208
+
209
+ it 'is required for existing records' do
210
+ allow(model).to receive(:new_record?).and_return(false)
211
+ expect(instance.required?).to be_truthy
212
+ end
213
+ end
214
+ end
215
+ end
216
+
217
+ context 'with inclusion validator' do
218
+ context 'with allow blank' do
219
+ let (:validator) { double(options: {allow_blank: true}, kind: :inclusion) }
220
+
221
+ it 'is not required' do
222
+ expect(instance.required?).to be_falsey
223
+ end
224
+ end
225
+
226
+ context 'without allow blank' do
227
+ let (:validator) { double(options: {allow_blank: false}, kind: :inclusion) }
228
+
229
+ it 'is required' do
230
+ expect(instance.required?).to be_truthy
231
+ end
232
+ end
233
+ end
234
+
235
+ context 'with a length validator' do
236
+ context 'with allow blank' do
237
+ let (:validator) { double(options: {allow_blank: true}, kind: :length) }
238
+
239
+ it 'is not required' do
240
+ expect(instance.required?).to be_falsey
241
+ end
242
+ end
243
+
244
+ context 'without allow blank' do
245
+ let (:validator) { double(options: {allow_blank: false}, kind: :length) }
246
+
247
+ it 'is not required' do
248
+ expect(instance.required?).to be_falsey
249
+ end
250
+
251
+ context 'with a minimum > 0' do
252
+ let (:validator) { double(options: {allow_blank: false, minimum: 1}, kind: :length) }
253
+
254
+ it 'is required' do
255
+ expect(instance.required?).to be_truthy
256
+ end
257
+ end
258
+
259
+ context 'with a minimum <= 0' do
260
+ let (:validator) { double(options: {allow_blank: false, minimum: 0}, kind: :length) }
261
+
262
+ it 'is not required' do
263
+ expect(instance.required?).to be_falsey
264
+ end
265
+ end
266
+
267
+ context 'with a defined range starting with > 0' do
268
+ let (:validator) { double(options: {allow_blank: false, within: 1..5}, kind: :length) }
269
+
270
+ it 'is required' do
271
+ expect(instance.required?).to be_truthy
272
+ end
273
+ end
274
+
275
+ context 'with a defined range starting with <= 0' do
276
+ let (:validator) { double(options: {allow_blank: false, within: 0..5}, kind: :length) }
277
+
278
+ it 'is not required' do
279
+ expect(instance.required?).to be_falsey
280
+ end
281
+ end
282
+ end
283
+ end
284
+
285
+ context 'with another validator' do
286
+ let (:validator) { double(options: {allow_blank: true}, kind: :foo) }
287
+
288
+ it 'is not required' do
289
+ expect(instance.required?).to be_falsey
290
+ end
291
+ end
292
+ end
293
+
294
+ context 'with multiple validators' do
295
+ context 'with a on create presence validator and a on update presence validator' do
296
+ let (:validator1) { double(options: {on: :create}, kind: :presence) }
297
+ let (:validator2) { double(options: {}, kind: :presence) }
298
+
299
+ before :each do
300
+ allow(model).to receive(:new_record?).and_return(false)
301
+ allow(instance).to receive(:validations?).and_return(:true)
302
+ allow(instance).to receive(:validations).and_return([validator1, validator2])
303
+ end
304
+
305
+ it 'is required' do
306
+ expect(instance.required?).to be_truthy
307
+ end
308
+ end
309
+
310
+ context 'with a on create presence validator and a presence validator' do
311
+ let (:validator1) { double(options: {on: :create}, kind: :presence) }
312
+ let (:validator2) { double(options: {}, kind: :presence) }
313
+
314
+ before :each do
315
+ allow(model).to receive(:new_record?).and_return(false)
316
+ allow(instance).to receive(:validations?).and_return(:true)
317
+ allow(instance).to receive(:validations).and_return([validator1, validator2])
318
+ end
319
+
320
+ it 'is required' do
321
+ expect(instance.required?).to be_truthy
322
+ end
323
+ end
324
+
325
+ context 'with a on create presence validator and a allow blank inclusion validator' do
326
+ let (:validator1) { double(options: {on: :create}, kind: :presence) }
327
+ let (:validator2) { double(options: {allow_blank: true}, kind: :inclusion) }
328
+
329
+ before :each do
330
+ allow(model).to receive(:new_record?).and_return(false)
331
+ allow(instance).to receive(:validations?).and_return(:true)
332
+ allow(instance).to receive(:validations).and_return([validator1, validator2])
333
+ end
334
+
335
+ it 'is required' do
336
+ expect(instance.required?).to be_falsey
337
+ end
338
+ end
339
+ end
340
+ end
341
+ end
342
+