simple_bootstrap_form 0.0.2 → 0.0.3

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