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,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