formtastic 3.1.3 → 3.1.4

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 (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
+