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,48 @@
1
+ # coding: utf-8
2
+ require File.dirname(__FILE__) + '/spec_helper'
3
+
4
+ describe 'SemanticFormBuilder#label' do
5
+
6
+ include FormtasticSpecHelper
7
+
8
+ before do
9
+ @output_buffer = ActiveSupport::SafeBuffer.new
10
+ mock_everything
11
+ end
12
+
13
+ it 'should humanize the given attribute' do
14
+ semantic_form_for(@new_post) do |builder|
15
+ builder.label(:login).should have_tag('label', :with => /Login/)
16
+ end
17
+ end
18
+
19
+ describe 'when required is given' do
20
+ it 'should append a required note' do
21
+ semantic_form_for(@new_post) do |builder|
22
+ builder.label(:login, nil, :required => true).should have_tag('label abbr')
23
+ end
24
+ end
25
+
26
+ it 'should allow require option to be given as second argument' do
27
+ semantic_form_for(@new_post) do |builder|
28
+ builder.label(:login, :required => true).should have_tag('label abbr')
29
+ end
30
+ end
31
+ end
32
+
33
+ describe 'when label is given' do
34
+ it 'should allow the text to be given as label option' do
35
+ semantic_form_for(@new_post) do |builder|
36
+ builder.label(:login, :required => true, :label => 'My label').should have_tag('label', :with => /My label/)
37
+ end
38
+ end
39
+
40
+ it 'should return nil if label is false' do
41
+ semantic_form_for(@new_post) do |builder|
42
+ builder.label(:login, :label => false).should be_blank
43
+ end
44
+ end
45
+ end
46
+
47
+ end
48
+
@@ -0,0 +1,42 @@
1
+ # coding: utf-8
2
+ require File.dirname(__FILE__) + '/spec_helper'
3
+
4
+ describe 'LayoutHelper' do
5
+
6
+ include FormtasticSpecHelper
7
+ include Formtastic::LayoutHelper
8
+
9
+ before do
10
+ @output_buffer = ActiveSupport::SafeBuffer.new
11
+ end
12
+
13
+ describe '#formtastic_stylesheet_link_tag' do
14
+
15
+ before do
16
+ concat(formtastic_stylesheet_link_tag())
17
+ end
18
+
19
+ it 'should render a link to formtastic.css' do
20
+ output_buffer.should have_tag("link[@href='/stylesheets/formtastic.css']")
21
+ end
22
+
23
+ it 'should render a link to formtastic_changes.css' do
24
+ output_buffer.should have_tag("link[@href='/stylesheets/formtastic_changes.css']")
25
+ end
26
+
27
+ end
28
+
29
+ # FIXME: Rspec issue?
30
+ def controller
31
+ mock('controller')
32
+ end
33
+
34
+ # FIXME: Rspec issue?
35
+ def config
36
+ returning mock('config') do |config|
37
+ config.stub!(:assets_dir).and_return('')
38
+ end
39
+ end
40
+
41
+ end
42
+
@@ -0,0 +1,98 @@
1
+ # coding: utf-8
2
+ require File.dirname(__FILE__) + '/spec_helper'
3
+
4
+ describe 'SemanticFormBuilder#semantic_errors' do
5
+
6
+ include FormtasticSpecHelper
7
+
8
+ before do
9
+ @output_buffer = ActiveSupport::SafeBuffer.new
10
+ mock_everything
11
+ @title_errors = ['must not be blank', 'must be awesome']
12
+ @base_errors = ['base error message', 'nasty error']
13
+ @base_error = 'one base error'
14
+ @errors = mock('errors')
15
+ @new_post.stub!(:errors).and_return(@errors)
16
+ end
17
+
18
+ describe 'when there is only one error on base' do
19
+ before do
20
+ @errors.stub!(:on_base).and_return(@base_error)
21
+ end
22
+
23
+ it 'should render an unordered list' do
24
+ semantic_form_for(@new_post) do |builder|
25
+ builder.semantic_errors.should have_tag('ul.errors li', @base_error)
26
+ end
27
+ end
28
+ end
29
+
30
+ describe 'when there is more than one error on base' do
31
+ before do
32
+ @errors.stub!(:on_base).and_return(@base_errors)
33
+ end
34
+
35
+ it 'should render an unordered list' do
36
+ semantic_form_for(@new_post) do |builder|
37
+ builder.semantic_errors.should have_tag('ul.errors')
38
+ @base_errors.each do |error|
39
+ builder.semantic_errors.should have_tag('ul.errors li', error)
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ describe 'when there are errors on title' do
46
+ before do
47
+ @errors.stub!(:[]).with(:title).and_return(@title_errors)
48
+ @errors.stub!(:on_base).and_return([])
49
+ end
50
+
51
+ it 'should render an unordered list' do
52
+ semantic_form_for(@new_post) do |builder|
53
+ title_name = builder.send(:localized_string, :title, :title, :label) || builder.send(:humanized_attribute_name, :title)
54
+ builder.semantic_errors(:title).should have_tag('ul.errors li', title_name << " " << @title_errors.to_sentence)
55
+ end
56
+ end
57
+ end
58
+
59
+ describe 'when there are errors on title and base' do
60
+ before do
61
+ @errors.stub!(:[]).with(:title).and_return(@title_errors)
62
+ @errors.stub!(:on_base).and_return(@base_error)
63
+ end
64
+
65
+ it 'should render an unordered list' do
66
+ semantic_form_for(@new_post) do |builder|
67
+ title_name = builder.send(:localized_string, :title, :title, :label) || builder.send(:humanized_attribute_name, :title)
68
+ builder.semantic_errors(:title).should have_tag('ul.errors li', title_name << " " << @title_errors.to_sentence)
69
+ builder.semantic_errors(:title).should have_tag('ul.errors li', @base_error)
70
+ end
71
+ end
72
+ end
73
+
74
+ describe 'when there are no errors' do
75
+ before do
76
+ @errors.stub!(:[]).with(:title).and_return(nil)
77
+ @errors.stub!(:on_base).and_return(nil)
78
+ end
79
+
80
+ it 'should return nil' do
81
+ semantic_form_for(@new_post) do |builder|
82
+ builder.semantic_errors(:title).should be_nil
83
+ end
84
+ end
85
+ end
86
+
87
+ describe 'when there is one error on base and options with class is passed' do
88
+ before do
89
+ @errors.stub!(:on_base).and_return(@base_error)
90
+ end
91
+
92
+ it 'should render an unordered list with given class' do
93
+ semantic_form_for(@new_post) do |builder|
94
+ builder.semantic_errors(:class => "awesome").should have_tag('ul.awesome li', @base_error)
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,44 @@
1
+ # coding: utf-8
2
+ require File.dirname(__FILE__) + '/spec_helper'
3
+
4
+ describe 'SemanticFormBuilder#semantic_fields_for' do
5
+
6
+ include FormtasticSpecHelper
7
+
8
+ before do
9
+ @output_buffer = ActiveSupport::SafeBuffer.new
10
+ mock_everything
11
+ @new_post.stub!(:author).and_return(::Author.new)
12
+ end
13
+
14
+ it 'yields an instance of SemanticFormHelper.builder' do
15
+ semantic_form_for(@new_post) do |builder|
16
+ builder.semantic_fields_for(:author) do |nested_builder|
17
+ nested_builder.class.should == ::Formtastic::SemanticFormHelper.builder
18
+ end
19
+ end
20
+ end
21
+
22
+ it 'nests the object name' do
23
+ semantic_form_for(@new_post) do |builder|
24
+ builder.semantic_fields_for(@bob) do |nested_builder|
25
+ nested_builder.object_name.should == 'post[author]'
26
+ end
27
+ end
28
+ end
29
+
30
+ it 'should sanitize html id for li tag' do
31
+ @bob.stub!(:column_for_attribute).and_return(mock('column', :type => :string, :limit => 255))
32
+ semantic_form_for(@new_post) do |builder|
33
+ builder.semantic_fields_for(@bob, :index => 1) do |nested_builder|
34
+ concat(nested_builder.inputs(:login))
35
+ end
36
+ end
37
+ output_buffer.should have_tag('form fieldset.inputs #post_author_1_login_input')
38
+ # Not valid selector, so using good ol' regex
39
+ output_buffer.should_not =~ /id="post\[author\]_1_login_input"/
40
+ # <=> output_buffer.should_not have_tag('form fieldset.inputs #post[author]_1_login_input')
41
+ end
42
+
43
+ end
44
+
data/spec/spec.opts ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format=progress
@@ -0,0 +1,238 @@
1
+ # coding: utf-8
2
+ require 'rubygems'
3
+
4
+ gem 'activesupport', '3.0.0.beta'
5
+ gem 'actionpack', '3.0.0.beta'
6
+ gem 'activemodel', '3.0.0.beta'
7
+ require 'active_support'
8
+ require 'action_pack'
9
+ require 'action_view'
10
+ require 'action_controller'
11
+ require 'active_model'
12
+
13
+ gem 'rspec', '>= 2.0.0.beta.2'
14
+ gem 'rspec-rails', '>= 2.0.0.beta.2'
15
+ gem 'hpricot', '>= 0.6.1'
16
+ gem 'rspec_tag_matchers', '>= 1.0.0'
17
+ require 'rspec_tag_matchers'
18
+
19
+ require 'custom_macros'
20
+
21
+ Rspec.configure do |config|
22
+ config.include RspecTagMatchers
23
+ config.include CustomMacros
24
+ config.mock_with :rspec
25
+ end
26
+
27
+ require File.expand_path(File.join(File.dirname(__FILE__), '../lib/formtastic'))
28
+ require File.expand_path(File.join(File.dirname(__FILE__), '../lib/formtastic/layout_helper'))
29
+
30
+
31
+ module FormtasticSpecHelper
32
+ include ActionView::Context
33
+ include ActionView::Helpers::FormHelper
34
+ include ActionView::Helpers::FormTagHelper
35
+ include ActionView::Helpers::FormOptionsHelper
36
+ include ActionView::Helpers::UrlHelper
37
+ include ActionView::Helpers::TagHelper
38
+ include ActionView::Helpers::TextHelper
39
+ include ActionView::Helpers::ActiveModelHelper
40
+ include ActionView::Helpers::RecordIdentificationHelper
41
+ include ActionView::Helpers::DateHelper
42
+ include ActionView::Helpers::CaptureHelper
43
+ include ActionView::Helpers::AssetTagHelper
44
+ include ActiveSupport
45
+ include ActionController::PolymorphicRoutes
46
+
47
+ include Formtastic::SemanticFormHelper
48
+
49
+ def default_input_type(column_type, column_name = :generic_column_name)
50
+ @new_post.stub!(column_name)
51
+ @new_post.stub!(:column_for_attribute).and_return(mock('column', :type => column_type)) unless column_type.nil?
52
+
53
+ semantic_form_for(@new_post) do |builder|
54
+ @default_type = builder.send(:default_input_type, column_name)
55
+ end
56
+
57
+ return @default_type
58
+ end
59
+
60
+ class ::Post
61
+ extend ActiveModel::Naming
62
+ include ActiveModel::Conversion
63
+
64
+ def id
65
+ end
66
+ end
67
+
68
+ module ::Namespaced
69
+ class Post
70
+ extend ActiveModel::Naming
71
+ include ActiveModel::Conversion
72
+
73
+ def id
74
+ end
75
+ end
76
+ end
77
+
78
+ class ::Author
79
+ extend ActiveModel::Naming
80
+ include ActiveModel::Conversion
81
+
82
+ def to_label
83
+ end
84
+ end
85
+
86
+ class ::Continent
87
+ extend ActiveModel::Naming
88
+ include ActiveModel::Conversion
89
+ end
90
+
91
+ def mock_everything
92
+
93
+ # Resource-oriented styles like form_for(@post) will expect a path method for the object,
94
+ # so we're defining some here.
95
+ def post_path(o); "/posts/1"; end
96
+ def posts_path; "/posts"; end
97
+ def new_post_path; "/posts/new"; end
98
+
99
+ def author_path(o); "/authors/1"; end
100
+ def authors_path; "/authors"; end
101
+ def new_author_path; "/authors/new"; end
102
+
103
+ @fred = mock('user')
104
+ @fred.stub!(:class).and_return(::Author)
105
+ @fred.stub!(:to_label).and_return('Fred Smith')
106
+ @fred.stub!(:login).and_return('fred_smith')
107
+ @fred.stub!(:id).and_return(37)
108
+ @fred.stub!(:new_record?).and_return(false)
109
+ @fred.stub!(:errors).and_return(mock('errors', :[] => nil))
110
+
111
+ @bob = mock('user')
112
+ @bob.stub!(:class).and_return(::Author)
113
+ @bob.stub!(:to_label).and_return('Bob Rock')
114
+ @bob.stub!(:login).and_return('bob')
115
+ @bob.stub!(:created_at)
116
+ @bob.stub!(:id).and_return(42)
117
+ @bob.stub!(:posts).and_return([])
118
+ @bob.stub!(:post_ids).and_return([])
119
+ @bob.stub!(:new_record?).and_return(false)
120
+ @bob.stub!(:errors).and_return(mock('errors', :[] => nil))
121
+
122
+ @james = mock('user')
123
+ @james.stub!(:class).and_return(::Author)
124
+ @james.stub!(:to_label).and_return('James Shock')
125
+ @james.stub!(:login).and_return('james')
126
+ @james.stub!(:id).and_return(75)
127
+ @james.stub!(:posts).and_return([])
128
+ @james.stub!(:post_ids).and_return([])
129
+ @james.stub!(:new_record?).and_return(false)
130
+ @james.stub!(:errors).and_return(mock('errors', :[] => nil))
131
+
132
+
133
+ ::Author.stub!(:find).and_return([@fred, @bob])
134
+ ::Author.stub!(:human_attribute_name).and_return { |column_name| column_name.humanize }
135
+ ::Author.stub!(:human_name).and_return('::Author')
136
+ ::Author.stub!(:validators_on).and_return([])
137
+ ::Author.stub!(:reflect_on_association).and_return { |column_name| mock('reflection', :options => {}, :klass => Post, :macro => :has_many) if column_name == :posts }
138
+ ::Author.stub!(:content_columns).and_return([mock('column', :name => 'login'), mock('column', :name => 'created_at')])
139
+
140
+ # Sometimes we need a mock @post object and some Authors for belongs_to
141
+ @new_post = mock('post')
142
+ @new_post.stub!(:class).and_return(::Post)
143
+ @new_post.stub!(:id).and_return(nil)
144
+ @new_post.stub!(:new_record?).and_return(true)
145
+ @new_post.stub!(:errors).and_return(mock('errors', :[] => nil))
146
+ @new_post.stub!(:author).and_return(nil)
147
+ @new_post.stub!(:main_post).and_return(nil)
148
+ @new_post.stub!(:sub_posts).and_return([]) #TODO should be a mock with methods for adding sub posts
149
+ @new_post.stub!(:to_model).and_return(@new_post)
150
+
151
+ @freds_post = mock('post')
152
+ @freds_post.stub!(:class).and_return(::Post)
153
+ @freds_post.stub!(:to_label).and_return('Fred Smith')
154
+ @freds_post.stub!(:id).and_return(19)
155
+ @freds_post.stub!(:author).and_return(@fred)
156
+ @freds_post.stub!(:author_id).and_return(@fred.id)
157
+ @freds_post.stub!(:authors).and_return([@fred])
158
+ @freds_post.stub!(:author_ids).and_return([@fred.id])
159
+ @freds_post.stub!(:new_record?).and_return(false)
160
+ @freds_post.stub!(:errors).and_return(mock('errors', :[] => nil))
161
+ @fred.stub!(:posts).and_return([@freds_post])
162
+ @fred.stub!(:post_ids).and_return([@freds_post.id])
163
+
164
+ ::Post.stub!(:human_attribute_name).and_return { |column_name| column_name.humanize }
165
+ ::Post.stub!(:human_name).and_return('Post')
166
+ ::Post.stub!(:validators_on).and_return([])
167
+ ::Post.stub!(:reflect_on_association).and_return do |column_name|
168
+ case column_name
169
+ when :author, :author_status
170
+ mock = mock('reflection', :options => {}, :klass => ::Author, :macro => :belongs_to)
171
+ mock.stub!(:[]).with(:class_name).and_return("Author")
172
+ mock
173
+ when :authors
174
+ mock('reflection', :options => {}, :klass => ::Author, :macro => :has_and_belongs_to_many)
175
+ when :sub_posts
176
+ mock('reflection', :options => {}, :klass => ::Post, :macro => :has_many)
177
+ when :main_post
178
+ mock('reflection', :options => {}, :klass => ::Post, :macro => :belongs_to)
179
+ end
180
+
181
+ end
182
+ ::Post.stub!(:find).and_return([@freds_post])
183
+ ::Post.stub!(:content_columns).and_return([mock('column', :name => 'title'), mock('column', :name => 'body'), mock('column', :name => 'created_at')])
184
+
185
+ @new_post.stub!(:title)
186
+ @new_post.stub!(:body)
187
+ @new_post.stub!(:published)
188
+ @new_post.stub!(:publish_at)
189
+ @new_post.stub!(:created_at)
190
+ @new_post.stub!(:secret)
191
+ @new_post.stub!(:time_zone)
192
+ @new_post.stub!(:category_name)
193
+ @new_post.stub!(:allow_comments)
194
+ @new_post.stub!(:column_for_attribute).with(:meta_description).and_return(mock('column', :type => :string, :limit => 255))
195
+ @new_post.stub!(:column_for_attribute).with(:title).and_return(mock('column', :type => :string, :limit => 50))
196
+ @new_post.stub!(:column_for_attribute).with(:body).and_return(mock('column', :type => :text))
197
+ @new_post.stub!(:column_for_attribute).with(:published).and_return(mock('column', :type => :boolean))
198
+ @new_post.stub!(:column_for_attribute).with(:publish_at).and_return(mock('column', :type => :date))
199
+ @new_post.stub!(:column_for_attribute).with(:time_zone).and_return(mock('column', :type => :string))
200
+ @new_post.stub!(:column_for_attribute).with(:allow_comments).and_return(mock('column', :type => :boolean))
201
+
202
+ @new_post.stub!(:author).and_return(@bob)
203
+ @new_post.stub!(:author_id).and_return(@bob.id)
204
+
205
+ @new_post.should_receive(:publish_at=).any_number_of_times
206
+ @new_post.should_receive(:title=).any_number_of_times
207
+ @new_post.stub!(:main_post_id).and_return(nil)
208
+
209
+ end
210
+
211
+ def self.included(base)
212
+ base.class_eval do
213
+
214
+ attr_accessor :output_buffer
215
+
216
+ def protect_against_forgery?
217
+ false
218
+ end
219
+
220
+ end
221
+ end
222
+
223
+ def with_config(config_method_name, value, &block)
224
+ old_value = ::Formtastic::SemanticFormBuilder.send(config_method_name)
225
+ ::Formtastic::SemanticFormBuilder.send(:"#{config_method_name}=", value)
226
+ yield
227
+ ::Formtastic::SemanticFormBuilder.send(:"#{config_method_name}=", old_value)
228
+ end
229
+
230
+ def with_deprecation_silenced(&block)
231
+ ::ActiveSupport::Deprecation.silenced = true
232
+ yield
233
+ ::ActiveSupport::Deprecation.silenced = false
234
+ end
235
+
236
+ end
237
+
238
+ ::ActiveSupport::Deprecation.silenced = false