simple_bootstrap_form 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/Gemfile +9 -0
  4. data/Guardfile +9 -0
  5. data/README.md +84 -1
  6. data/Rakefile +9 -0
  7. data/circle.yml +5 -0
  8. data/lib/simple_bootstrap_form/action_view_extensions.rb +5 -5
  9. data/lib/simple_bootstrap_form/css_class_list.rb +1 -1
  10. data/lib/simple_bootstrap_form/field_factory.rb +58 -0
  11. data/lib/simple_bootstrap_form/horizontal_form/fields/base_field.rb +118 -0
  12. data/lib/simple_bootstrap_form/horizontal_form/fields/boolean_field.rb +16 -0
  13. data/lib/simple_bootstrap_form/horizontal_form/fields/datetime_field.rb +55 -0
  14. data/lib/simple_bootstrap_form/horizontal_form/fields/email_field.rb +10 -0
  15. data/lib/simple_bootstrap_form/horizontal_form/fields/password_field.rb +10 -0
  16. data/lib/simple_bootstrap_form/horizontal_form/fields/text_field.rb +10 -0
  17. data/lib/simple_bootstrap_form/horizontal_form/fields/textarea_field.rb +14 -0
  18. data/lib/simple_bootstrap_form/horizontal_form/form_builder.rb +59 -0
  19. data/lib/simple_bootstrap_form/version.rb +1 -1
  20. data/lib/simple_bootstrap_form.rb +9 -8
  21. data/simple_bootstrap_form.gemspec +1 -1
  22. data/spec/field_factory_spec.rb +65 -0
  23. data/spec/simple_bootstrap_form_spec.rb +360 -83
  24. data/spec/spec_helper.rb +3 -0
  25. data/spec/support/have_element.rb +109 -0
  26. metadata +19 -13
  27. data/lib/simple_bootstrap_form/fields/base_field.rb +0 -103
  28. data/lib/simple_bootstrap_form/fields/boolean_field.rb +0 -14
  29. data/lib/simple_bootstrap_form/fields/datetime_field.rb +0 -53
  30. data/lib/simple_bootstrap_form/fields/email_field.rb +0 -8
  31. data/lib/simple_bootstrap_form/fields/password_field.rb +0 -8
  32. data/lib/simple_bootstrap_form/fields/text_field.rb +0 -8
  33. data/lib/simple_bootstrap_form/fields/textarea_field.rb +0 -12
  34. data/lib/simple_bootstrap_form/form_builder.rb +0 -51
  35. data/spec/support/input_matchers.rb +0 -72
@@ -1,5 +1,9 @@
1
1
  require 'spec_helper'
2
2
 
3
+ def pretty_print(html)
4
+ Nokogiri::XML(html, &:noblanks).to_xhtml
5
+ end
6
+
3
7
  describe SimpleBootstrapForm, type: :helper do
4
8
 
5
9
  def account_form
@@ -23,133 +27,406 @@ describe SimpleBootstrapForm, type: :helper do
23
27
  let(:model) { Account.new }
24
28
  let(:form_id) { 'new_account' }
25
29
 
26
- subject do
27
- account_form
28
- end
29
-
30
- #it "should generate a form" do
31
- # expect(subject).to eq(
32
- # '<form accept-charset="UTF-8" action="/accounts" class="form-horizontal" id="new_account" method="post">' +
33
- # '<div style="margin:0;padding:0;display:inline">'+
34
- # '<input name="utf8" type="hidden" value="&#x2713;" />'+
35
- # '</div>'+
36
- # '<div class="form-group">'+
37
- # '<label class="control-label col-sm-3" for="account_email"><abbr title="required">*</abbr> Email</label>'+
38
- # '<div class="col-sm-6">'+
39
- # '<input class="form-control" id="account_email" name="account[email]" placeholder="Email" required="required" type="email" />'+
40
- # '</div>'+
41
- # '</div>'+
42
- # '</form>'
43
- # )
44
- #end
45
-
46
- describe "the form" do
47
- it "should have and ID" do
48
- should have_selector %(form##{form_id}[action="/accounts"])
30
+ describe "all forms" do
31
+ subject do
32
+ account_form
49
33
  end
50
34
 
51
- it "should be a horizontal form" do
52
- should have_selector "form##{form_id}.form-horizontal"
35
+ it "should have an ID" do
36
+ should have_element %(form##{form_id}[action="/accounts"])
53
37
  end
54
38
 
55
39
  it "should have role 'form'" do
56
- should have_selector "form##{form_id}[role=form]"
40
+ should have_element "form##{form_id}[role=form]"
57
41
  end
58
- end
59
42
 
60
- describe "f.input" do
61
- let(:field_id) { "account_email" }
43
+ describe "f.input" do
44
+ let(:field_id) { 'account_email' }
45
+
46
+ describe "form-group" do
47
+ it "should add a class describing the form group" do
48
+ should have_element '.form-group.account_email_group'
49
+ end
50
+
51
+ context "when a :group_class option is provided" do
52
+ subject {
53
+ helper.bootstrap_form_for model do |f|
54
+ f.input :email, group_class: "my_class"
55
+ end
56
+ }
57
+
58
+ it "should add that value as a class to the group" do
59
+ should have_element '.form-group.my_class'
60
+ end
61
+ end
62
62
 
63
- describe "form-group" do
64
- it "should add a class describing the form group" do
65
- should have_selector ".form-group.account_email_group"
63
+ context "when :group_class is set to false on the input" do
64
+ subject {
65
+ helper.bootstrap_form_for model do |f|
66
+ f.input :email, group_class: false
67
+ end
68
+ }
69
+
70
+ it "should not add a field-specific class to that group" do
71
+ should have_element('.form-group').with_only_classes('form-group')
72
+ end
73
+ end
74
+
75
+ context "when :group_class is set to false on the form" do
76
+ subject {
77
+ helper.bootstrap_form_for model, group_class: false do |f|
78
+ f.input :email
79
+ end
80
+ }
81
+
82
+ it "should not add a field-specific class to the groups" do
83
+ should have_element('.form-group').with_only_classes('form-group')
84
+ end
85
+ end
66
86
  end
67
- end
68
87
 
69
- describe "label" do
70
- it { should have_selector "form##{form_id} > .form-group > label.control-label[for=#{field_id}]" }
88
+ describe "label" do
89
+ it { should have_element "form##{form_id} > .form-group " +
90
+ "> label.control-label[for=#{field_id}]" }
91
+ end
92
+
93
+ describe "string field" do
94
+ let(:field_id) { 'account_first_name' }
71
95
 
72
- it "should make the label 3 columns wide" do
73
- should have_selector "label.col-sm-3[for=#{field_id}]"
96
+ it "should give the input an ID, and class form-control" do
97
+ should have_element %(input##{field_id}.form-control[name="account[first_name]"])
98
+ end
99
+
100
+ it { should have_element("input##{field_id}").with_type('text') }
101
+ it { should have_element("input##{field_id}").with_placeholder('First name') }
74
102
  end
75
- end
76
103
 
77
- describe "string field" do
78
- let(:field_id) { 'account_first_name' }
104
+ describe "integer field" do
105
+ let(:field_id) { "account_id" }
106
+ subject do
107
+ helper.bootstrap_form_for model do |f|
108
+ f.input :id
109
+ end
110
+ end
111
+ it { should have_element("input##{field_id}").with_type('text') }
112
+ end
79
113
 
80
- it "should give the input an ID, and class form-control" do
81
- should have_selector %(input##{field_id}.form-control[name="account[first_name]"])
114
+ describe "email field (required)" do
115
+ let(:field_id) { "account_email" }
116
+
117
+ it { should have_element("input##{field_id}").with_type('email') }
118
+ it { should have_element("input##{field_id}").with_placeholder('Email') }
119
+ it { should have_element("input##{field_id}").with_attr_value(:required, 'required') }
82
120
  end
83
121
 
84
- it "should place the input inside a 6 column div" do
85
- should have_selector "form##{form_id} > .form-group > .col-sm-6 > input##{field_id}"
122
+ describe "option :as =>" do
123
+ let(:field_id) { "account_email" }
124
+ subject do
125
+ helper.bootstrap_form_for model do |f|
126
+ f.input :email, as: :text
127
+ end
128
+ end
129
+ it "should override the type" do
130
+ should have_element("input##{field_id}").with_type('text')
131
+ end
86
132
  end
87
133
 
88
- it { should have_input("##{field_id}").with_type('text') }
89
- it { should have_input("##{field_id}").with_placeholder('First name') }
90
- end
134
+ describe "password field" do
135
+ let(:field_id) { "account_password" }
136
+
137
+ it { should have_element("input##{field_id}").with_type('password') }
138
+ end
139
+
140
+ context "Using the Article form" do
141
+ let(:model) { Article.new }
142
+ subject { article_form }
143
+
144
+ describe "text field (optional)" do
145
+ let(:field_id) { 'article_body' }
146
+
147
+ it { should have_element(:textarea).with_id(field_id) }
148
+ it { should have_element(:textarea).with_id(field_id)
149
+ .with_placeholder('Body') }
150
+ it { should_not have_element(:textarea).with_id(field_id)
151
+ .with_attr_value(:required, 'required') }
152
+ end
91
153
 
92
- describe "integer field" do
93
- let(:field_id) { "account_id" }
94
- subject do
95
- helper.bootstrap_form_for model do |f|
96
- f.input :id
154
+ describe "datetime field" do
155
+ let(:field_id) { "article_published_at" }
156
+
157
+ it { should have_element("input##{field_id}").with_type('datetime') }
158
+ end
159
+
160
+ describe "boolean field" do
161
+ let(:field_id) { 'article_visible' }
162
+
163
+ it { should have_element("input##{field_id}").with_type('checkbox') }
97
164
  end
98
165
  end
99
- it { should have_input("##{field_id}").with_type('text') }
100
166
  end
167
+ end
101
168
 
102
- describe "email field (required)" do
103
- let(:field_id) { "account_email" }
169
+ #describe "for vertical forms" do
170
+ #end
171
+
172
+ #describe "for inline forms" do
173
+ # subject {
174
+ # helper.bootstrap_form_for model, layout: 'inline' do |f|
175
+ # f.input(:email)
176
+ # end
177
+ # }
178
+ # it { should have_element "form.form-inline" }
179
+ #end
104
180
 
105
- it { should have_input("##{field_id}").with_type('email') }
106
- it { should have_input("##{field_id}").with_placeholder('Email') }
107
- it { should have_input("##{field_id}").with_attr_value(:required, 'required') }
181
+ describe "horizontal forms" do
182
+ subject {
183
+ helper.bootstrap_form_for(model, layout: 'horizontal') {}
184
+ }
185
+
186
+ describe "the form" do
187
+ it { should have_element "form.form-horizontal" }
108
188
  end
109
189
 
110
- describe "option :as =>" do
190
+ describe "f.input" do
111
191
  let(:field_id) { "account_email" }
112
- subject do
113
- helper.bootstrap_form_for model do |f|
114
- f.input :email, as: :text
192
+
193
+ context "using size defaults" do
194
+ subject {
195
+ helper.bootstrap_form_for model, layout: 'horizontal' do |f|
196
+ f.input(:email)
197
+ end
198
+ }
199
+
200
+ it "should make the label col-sm-3 wide" do
201
+ should have_element "label.col-sm-3[for=#{field_id}]"
202
+ end
203
+
204
+ it "should place the input inside a col-sm-6" do
205
+ should have_element "form##{form_id} > .form-group > .col-sm-6 > input##{field_id}"
115
206
  end
116
207
  end
117
- it "should override the type" do
118
- should have_input("##{field_id}").with_type('text')
208
+
209
+ context "when sizes are supplied to the form" do
210
+ subject {
211
+ helper.bootstrap_form_for model, layout: 'horizontal',
212
+ label_size: 'col-md-3',
213
+ input_size: 'col-md-6' do |f|
214
+ f.input :email
215
+ end
216
+ }
217
+
218
+ it "should use the form sizes for label and input" do
219
+ should have_element "label.col-md-3[for=#{field_id}]"
220
+ should have_element ".form-group > .col-md-6 > input##{field_id}"
221
+ end
222
+
223
+ context "when sizes are supplied to the input" do
224
+ subject {
225
+ helper.bootstrap_form_for model, layout: 'horizontal' do |f|
226
+ f.input :email, label_size: 'col-xs-2', input_size: 'col-xs-4'
227
+ end
228
+ }
229
+
230
+ it "input sizes should override the form sizes" do
231
+ should have_element "label.col-xs-2[for=#{field_id}]"
232
+ should have_element ".form-group > .col-xs-4 > input##{field_id}"
233
+ end
234
+ end
119
235
  end
120
236
  end
237
+ end
238
+ end
121
239
 
122
- describe "password field" do
123
- let(:field_id) { "account_password" }
240
+ describe "getbootstrap.com examples" do
241
+ describe "Basic example" do
242
+ class Model1
243
+ include ActiveModel::Validations
244
+ include ActiveModel::Conversion
245
+ include ActiveModel::Naming
124
246
 
125
- it { should have_input("##{field_id}").with_type('password') }
247
+ def self.model_path
248
+ "foo"
249
+ end
250
+
251
+ attr_accessor :exampleInputEmail1
252
+ attr_accessor :exampleInputPassword1
253
+ attr_accessor :exampleInputFile
254
+ attr_accessor :check_me_out
126
255
  end
127
256
 
128
- context "Using the Article form" do
129
- let(:model) { Article.new }
130
- subject { article_form }
257
+ let!(:model) do
258
+ Model1.new
259
+ end
131
260
 
132
- describe "text field (optional)" do
133
- let(:field_id) { 'article_body' }
261
+ let(:basic_example_output_from_getbootstrap_dot_com) {
262
+ <<-BASIC_EXAMPLE
263
+ <form role="form">
264
+ <div class="form-group">
265
+ <label for="exampleInputEmail1">Email address</label>
266
+ <input type="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email">
267
+ </div>
268
+ <div class="form-group">
269
+ <label for="exampleInputPassword1">Password</label>
270
+ <input type="password" class="form-control" id="exampleInputPassword1" placeholder="Password">
271
+ </div>
272
+ <div class="form-group">
273
+ <label for="exampleInputFile">File input</label>
274
+ <input type="file" id="exampleInputFile">
275
+ <p class="help-block">Example block-level help text here.</p>
276
+ </div>
277
+ <div class="checkbox">
278
+ <label>
279
+ <input type="checkbox"> Check me out
280
+ </label>
281
+ </div>
282
+ <button type="submit" class="btn btn-default">Submit</button>
283
+ </form>
284
+ BASIC_EXAMPLE
285
+ }
134
286
 
135
- it { should have_tag(:textarea).with_id(field_id) }
136
- it { should have_tag(:textarea).with_id(field_id)
137
- .with_placeholder('Body') }
138
- it { should_not have_tag(:textarea).with_id(field_id)
139
- .with_attr_value(:required, 'required') }
287
+ subject {
288
+ helper.bootstrap_form_for model, url: '/foo' do |f|
289
+ f.input(:exampleInputEmail1) +
290
+ f.input(:exampleInputPassword1) +
291
+ f.input(:exampleInputFile, help: "Example block-level help text here.") +
292
+ f.input(:check_me_out, as: :boolean)
140
293
  end
294
+ }
141
295
 
142
- describe "datetime field" do
143
- let(:field_id) { "article_published_at" }
296
+ it "should generate the correct output"
297
+ end
144
298
 
145
- it { should have_input("##{field_id}").with_type('datetime') }
299
+ describe "Inline form" do
300
+ let(:inline_form_from_getbootstrap_dot_com) {
301
+ <<-INLINE_FORM
302
+ <form class="form-inline" role="form">
303
+ <div class="form-group">
304
+ <label class="sr-only" for="exampleInputEmail2">Email address</label>
305
+ <input type="email" class="form-control" id="exampleInputEmail2" placeholder="Enter email">
306
+ </div>
307
+ <div class="form-group">
308
+ <label class="sr-only" for="exampleInputPassword2">Password</label>
309
+ <input type="password" class="form-control" id="exampleInputPassword2" placeholder="Password">
310
+ </div>
311
+ <div class="checkbox">
312
+ <label>
313
+ <input type="checkbox"> Remember me
314
+ </label>
315
+ </div>
316
+ <button type="submit" class="btn btn-default">Sign in</button>
317
+ </form>
318
+ INLINE_FORM
319
+ }
320
+ end
321
+
322
+ describe "Horizontal form" do
323
+ class Model3
324
+ include ActiveModel::Validations
325
+ include ActiveModel::Conversion
326
+ include ActiveModel::Naming
327
+
328
+ def self.model_path
329
+ "foo"
146
330
  end
147
331
 
148
- describe "boolean field" do
149
- let(:field_id) { 'article_visible' }
332
+ attr_accessor :inputEmail3
333
+ attr_accessor :inputPassword3
334
+ attr_accessor :remember_me
335
+ end
336
+
337
+ let!(:model) do
338
+ Model3.new
339
+ end
340
+
341
+ let(:horizontal_form_output) {
342
+ # Original copy from getbootstrap.com
343
+ #<form class="form-horizontal" role="form">
344
+ # <div class="form-group">
345
+ # <label for="inputEmail3" class="col-sm-2 control-label">Email</label>
346
+ # <div class="col-sm-10">
347
+ # <input type="email" class="form-control" id="inputEmail3" placeholder="Email">
348
+ # </div>
349
+ # </div>
350
+ # <div class="form-group">
351
+ # <label for="inputPassword3" class="col-sm-2 control-label">Password</label>
352
+ # <div class="col-sm-10">
353
+ # <input type="password" class="form-control" id="inputPassword3" placeholder="Password">
354
+ # </div>
355
+ # </div>
356
+ # <div class="form-group">
357
+ # <div class="col-sm-offset-2 col-sm-10">
358
+ # <div class="checkbox">
359
+ # <label>
360
+ # <input type="checkbox"> Remember me
361
+ # </label>
362
+ # </div>
363
+ # </div>
364
+ # </div>
365
+ # <div class="form-group">
366
+ # <div class="col-sm-offset-2 col-sm-10">
367
+ # <button type="submit" class="btn btn-default">Sign in</button>
368
+ # </div>
369
+ # </div>
370
+ #</form>
150
371
 
151
- it { should have_input("##{field_id}").with_type('checkbox') }
372
+ # Railsified version
373
+ # <form>
374
+ # add accept-charset, action, id, method, model-specific class
375
+ # <label>
376
+ # changed for= to model_field
377
+ # <input>
378
+ # reorder input attributes to match Rails' order
379
+ # add name=
380
+ <<-HORIZONTAL_FORM
381
+ <form accept-charset="UTF-8" action="/foo" class="new_model3 form-horizontal" id="new_model3" method="post" role="form">
382
+ <div style=\"margin:0;padding:0;display:inline\">
383
+ <input name="utf8" type="hidden" value="&#x2713;" />
384
+ </div>
385
+ <div class="form-group">
386
+ <label class="col-sm-2 control-label" for="model3_inputEmail3">Email</label>
387
+ <div class="col-sm-10">
388
+ <input class="form-control" id="model3_inputEmail3" name="model3[inputEmail3]" placeholder="Email" type="email" />
389
+ </div>
390
+ </div>
391
+ <div class="form-group">
392
+ <label class="col-sm-2 control-label" for="model3_inputPassword3">Password</label>
393
+ <div class="col-sm-10">
394
+ <input class="form-control" id="model3_inputPassword3" name="model3[inputPassword3]" placeholder="Password" type="password" />
395
+ </div>
396
+ </div>
397
+ <div class="form-group">
398
+ <div class="col-sm-offset-2 col-sm-10">
399
+ <div class="checkbox">
400
+ <label>
401
+ <input type="checkbox"> Remember me
402
+ </label>
403
+ </div>
404
+ </div>
405
+ </div>
406
+ <div class="form-group">
407
+ <div class="col-sm-offset-2 col-sm-10">
408
+ <button type="submit" class="btn btn-default">Sign in</button>
409
+ </div>
410
+ </div>
411
+ </form>
412
+ HORIZONTAL_FORM
413
+ }
414
+
415
+ subject {
416
+ helper.bootstrap_form_for model,
417
+ layout: :horizontal,
418
+ label_size: 'col-sm-2',
419
+ input_size: 'col-sm-10',
420
+ group_class: false,
421
+ url: '/foo' do |f|
422
+ f.input(:inputEmail3, label: "Email", placeholder: "Email") +
423
+ f.input(:inputPassword3, label: "Password", placeholder: "Password") +
424
+ f.input(:remember_me, as: :boolean, label_size: 'col-sm-offset-2 col-sm-10')
152
425
  end
426
+ }
427
+
428
+ it "should generate the correct output" do
429
+ #expect(pretty_print subject).to eq horizontal_form_output
153
430
  end
154
431
  end
155
432
  end
data/spec/spec_helper.rb CHANGED
@@ -44,4 +44,7 @@ RSpec.configure do |config|
44
44
  # the seed, which is printed after each run.
45
45
  # --seed 1234
46
46
  config.order = "random"
47
+
48
+ config.filter_run :focus => true
49
+ config.run_all_when_everything_filtered = true
47
50
  end
@@ -0,0 +1,109 @@
1
+ require 'nokogiri'
2
+
3
+ RSpec::Matchers.define :have_element do |selector|
4
+ match_for_should do |markup|
5
+ @selector = selector
6
+ @markup = markup
7
+ element_is_present && classes_match
8
+ end
9
+
10
+ match_for_should_not do |markup|
11
+ @selector = selector
12
+ @markup = markup
13
+ !element_is_present || !classes_match
14
+ end
15
+
16
+ failure_message_for_should do |markup|
17
+ if the_element.nil?
18
+ "expected to find an element matching #{selector} in #{markup}"
19
+ elsif @expected_classes
20
+ actual_classes = @element['class'].split(' ')
21
+ "expected #{inspect_the_element} to have classes \"#{@expected_classes.sort.join(' ')}\" but it has classes \"#{actual_classes.sort.join(' ')}\""
22
+ else
23
+ end
24
+ end
25
+
26
+ failure_message_for_should_not do |markup|
27
+ "expected #{inspect_the_element} not to have classes \"#{@expected_classes.sort.join(' ')}\" in #{markup}"
28
+ end
29
+
30
+ def element_is_present
31
+ the_element.is_a? Nokogiri::XML::Element
32
+ end
33
+
34
+ def check_element(element)
35
+ if @expected_classes
36
+ else
37
+ true
38
+ end
39
+ end
40
+
41
+ ########## Chain assertions that will affect the selector
42
+
43
+ chain :with_id do |id|
44
+ @id = id
45
+ end
46
+
47
+ chain :with_class do |css_class|
48
+ @class = css_class
49
+ end
50
+
51
+ def attrs
52
+ @attrs ||= {}
53
+ end
54
+
55
+ chain :with_attr_value do |attr, value|
56
+ attrs[attr] = value
57
+ end
58
+
59
+ chain :with_type do |type|
60
+ attrs['type'] = type
61
+ end
62
+
63
+ chain :with_placeholder do |placeholder|
64
+ attrs['placeholder'] = placeholder
65
+ end
66
+
67
+ def actual_selector
68
+ @actual_selector ||= begin
69
+ s = @selector.to_s.dup
70
+ s << "##{@id}" if @id
71
+ s << ".#{@class}" if @class
72
+ attrs.each do |attr, value|
73
+ s << "[#{attr}=\"#{value}\"]"
74
+ end
75
+ s
76
+ end
77
+ end
78
+
79
+ ########## Chain assertions that are run once the tag has been recovered
80
+
81
+ chain :with_only_classes do |classes|
82
+ @expected_classes = classes.split ' '
83
+ end
84
+
85
+ def actual_classes
86
+ the_element['class'].split ' '
87
+ end
88
+
89
+ def classes_match
90
+ return true unless @expected_classes
91
+ actual_classes.sort == @expected_classes.sort
92
+ end
93
+
94
+ ########## Utility methods
95
+
96
+ def the_element
97
+ @element ||= begin
98
+ doc = Nokogiri::XML @markup
99
+ elements = doc.css actual_selector
100
+ raise "found multiple elements matching \"#{actual_selector}\"" if elements.count > 1
101
+ elements.first
102
+ end
103
+ end
104
+
105
+ def inspect_the_element
106
+ the_element.children = ""
107
+ the_element
108
+ end
109
+ end