simple_bootstrap_form 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 49612ff415db9c67ef7d7fddec5ce1628a89bd56
4
- data.tar.gz: f44b2d057f9fb6c315271314a119245dab29f9d1
3
+ metadata.gz: 1ec18e4dcc4888081e0d602db082eedff9afced8
4
+ data.tar.gz: 44da0c8960e49510e754f7b4d7f955d4ad5c3070
5
5
  SHA512:
6
- metadata.gz: 5d98ef3d428ad7ced926485824bc37ba490d531d2d7adbae27ea7b3b7a065566155fa1aff84a68dc366e0129e8f203a13541f97c8a74030ad26ae9f4d47bf402
7
- data.tar.gz: 70730348cf4eb5997e8de562623fc2cb76e44d48de10d412cbac84e77ec06ca38e7cc86be8785fa6dfffddbee28b8cdcb477e149d486f79a86951036c2469e42
6
+ metadata.gz: a06e26323f3f4eb7c0e2874c804ac335454228dc209a9641b11923834ca83e7a584d2d09465cf49401253be32b0f0de993e35c49b1c819fb3bc1d065262656c3
7
+ data.tar.gz: 22ab8c478c9c90450ef14bc66093b55ad67ebc25fbc7d26d68fc4dc682a98ccdd4e152b09797da2acf791073b2b1fe0193e0e230a34814dbf3de59088ce32687
data/Gemfile CHANGED
@@ -7,6 +7,7 @@ group :development, :test do
7
7
  # Gems required to run guard-rspec and send notifications to OS Notification
8
8
  # System
9
9
  gem 'guard-rspec', require: false
10
+ gem 'guard-markdown'
10
11
  gem 'terminal-notifier-guard'
11
12
 
12
13
  gem 'byebug'
data/Guardfile CHANGED
@@ -7,3 +7,7 @@ guard 'rspec', all_on_start: false, all_after_pass: false , failed_mode: :keep d
7
7
  watch(%r{^lib/(.+)\.rb$}) { |m| "spec/simple_bootstrap_form_spec.rb" }
8
8
  watch('spec/spec_helper.rb') { "spec" }
9
9
  end
10
+
11
+ guard :markdown, convert_on_start: true do
12
+ watch(/(README)\.md/) { |match| "#{match[1]}.md|tmp/#{match[1]}.html" }
13
+ end
data/README.md CHANGED
@@ -135,6 +135,18 @@ Generates (spaces added for clarity):
135
135
  </form>
136
136
  ```
137
137
 
138
+ ### Input Options
139
+
140
+ | Option | Meaning | Default
141
+ |----------------|--------------------------------|------------
142
+ | `:as` | Override column type. Use on of the types from the _Supported Field Types_ table below.
143
+ | `:label` | Set custom label. | Humanized and titlized attribute name.
144
+ | `:placeholder` | Set custom placeholder. | Humanized and titlized attribute name.
145
+ | `:group_class` | For ease of testing in Capybara, a class is added to each `form-group` div, constructed from the object name and attribute name, e.g. `article_title_group`. This option allows you to override that class name, or suppress using the value `false`. | (_model_)\_(_attribute_)\_group
146
+ | `:label_size` | Bootstrap CSS class to use to size the label for this field. By default this is set by the form. This option is only needed if you need a label to be differently sized from the rest of the form's labels.
147
+ | `:input_size` | Bootstrap CSS class to use to size the input for this field. By default these are set by the form. This option is only needed if you need an input to be differently sized from the rest of the form's inputs.
148
+ | `:required` | Set this to `true`/`false` to override whether this field should be marked required. | If an attribute has a presence validator, it will be marked required.
149
+
138
150
  ## Support
139
151
 
140
152
  #### Bootstrap Support
@@ -143,11 +155,23 @@ Generates (spaces added for clarity):
143
155
 
144
156
  #### Simple Form API Support
145
157
 
146
- * Input types: boolean, datetime, email, password, text, textarea. These are
147
- trivial to add. Just take a look in fields/.
148
158
  * Required fields.
149
159
  * Placeholders, automatic and custom.
150
160
 
161
+ #### Supported Field Types
162
+
163
+ * These are straighforward to add. Just take a look in fields/.
164
+
165
+ Field Type | Generated HTML Element | Database Column Type
166
+ --------------- |:---------------------------------------------------|:--------------------
167
+ `boolean` | `input[type=checkbox]` | `boolean`
168
+ `email` | `input[type=email]` | `string` with `name =~ /email/`
169
+ `password` | `input[type=password]` | `string` with `name =~ /password/`
170
+ `string` | `input[type=text]` | `string`
171
+ `text` | `textarea` | `text`
172
+ `datetime` | `input[type=text]` setup for jquery.datetimepicker | `datetime`
173
+ `select` | `select` | just provide a `collection:` option
174
+
151
175
  ## Contributing
152
176
 
153
177
  1. Fork it ( http://github.com/<my-github-username>/my_gem/fork )
@@ -155,3 +179,10 @@ Generates (spaces added for clarity):
155
179
  3. Commit your changes (`git commit -am 'Add some feature'`)
156
180
  4. Push to the branch (`git push origin my-new-feature`)
157
181
  5. Create new Pull Request
182
+
183
+ ## TODO
184
+
185
+ * Vertical Form
186
+ * Inline Form
187
+ * bootstrap\_fields\_for
188
+ * Suppress placeholder
@@ -28,6 +28,8 @@ module SimpleBootstrapForm
28
28
  def field_class_type_prefix(attr, options)
29
29
  if options[:as]
30
30
  options[:as].to_s.capitalize
31
+ elsif options.has_key? :collection
32
+ 'Select'
31
33
  else
32
34
  derive_field_class_prefix attr
33
35
  end
@@ -4,7 +4,7 @@ module SimpleBootstrapForm
4
4
  class BaseField
5
5
 
6
6
  class << self
7
- attr_accessor :type
7
+ attr_accessor :input_type
8
8
  end
9
9
 
10
10
  def initialize(form_builder, template, name, options)
@@ -30,6 +30,9 @@ module SimpleBootstrapForm
30
30
  @input_size = @options.delete :input_size
31
31
  @label_text = @options.delete :label
32
32
  @group_class = @options.delete :group_class
33
+ unless @label_size && @input_size
34
+ raise "label_size and input_size are required options"
35
+ end
33
36
  end
34
37
 
35
38
  def group_options
@@ -47,11 +50,11 @@ module SimpleBootstrapForm
47
50
  end
48
51
 
49
52
  def field_label
50
- @form_builder.label @name, @label_text, label_options
53
+ @form_builder.label @name, label_text, label_options
51
54
  end
52
55
 
53
56
  def label_text
54
- text = @options[:label] || @name.to_s.humanize.capitalize
57
+ text = @label_text || @name.to_s.humanize.capitalize
55
58
  required_asterisk + text
56
59
  end
57
60
 
@@ -78,7 +81,7 @@ module SimpleBootstrapForm
78
81
  def input_options
79
82
  @options.merge! class: 'form-control',
80
83
  placeholder: placeholder,
81
- type: self.class.type
84
+ type: self.class.input_type
82
85
  @options.merge! required: 'required' if required?
83
86
  @options
84
87
  end
@@ -104,6 +107,7 @@ module SimpleBootstrapForm
104
107
 
105
108
  def required?
106
109
  return false unless model
110
+ return @options[:required] if @options.has_key?(:required)
107
111
  model.class.validators_on(@name).any? do |validator|
108
112
  validator.kind_of? ActiveModel::Validations::PresenceValidator
109
113
  end
@@ -3,7 +3,7 @@ module SimpleBootstrapForm
3
3
  module Fields
4
4
  class BooleanField < BaseField
5
5
 
6
- self.type = 'checkbox'
6
+ self.input_type = 'checkbox'
7
7
 
8
8
  def input_tag
9
9
  @template.content_tag(:div, class: 'col-sm-6') do
@@ -1,9 +1,12 @@
1
1
  module SimpleBootstrapForm
2
2
  module HorizontalForm
3
3
  module Fields
4
+
5
+ # Creates a text field that should be initialized with
6
+ # jquery.datetimepicker
4
7
  class DatetimeField < BaseField
5
8
 
6
- self.type = 'datetime'
9
+ self.input_type = 'datetime'
7
10
 
8
11
  def input_tag
9
12
  @template.content_tag(:div, class: 'col-sm-6') do
@@ -3,7 +3,7 @@ module SimpleBootstrapForm
3
3
  module Fields
4
4
  class EmailField < BaseField
5
5
 
6
- self.type = 'email'
6
+ self.input_type = 'email'
7
7
  end
8
8
  end
9
9
  end
@@ -3,7 +3,7 @@ module SimpleBootstrapForm
3
3
  module Fields
4
4
  class PasswordField < BaseField
5
5
 
6
- self.type = 'password'
6
+ self.input_type = 'password'
7
7
  end
8
8
  end
9
9
  end
@@ -0,0 +1,40 @@
1
+ module SimpleBootstrapForm
2
+ module HorizontalForm
3
+ module Fields
4
+ class SelectField < BaseField
5
+
6
+ def input_tag
7
+ @template.content_tag(:div, class: @input_size) do
8
+ @form_builder.select @name, choices, options, html_options
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def process_options(options)
15
+ super
16
+ @collection = @options.delete :collection
17
+ unless @collection
18
+ raise ":collection is a required option for select fields"
19
+ end
20
+ end
21
+
22
+ def choices
23
+ @template.options_for_select @collection, selected: model.send(@name)
24
+ end
25
+
26
+ def options
27
+ {}
28
+ end
29
+
30
+ def html_options
31
+ opts = {
32
+ class: 'form-control'
33
+ }
34
+ opts.merge! required: 'required' if required?
35
+ opts
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -3,7 +3,7 @@ module SimpleBootstrapForm
3
3
  module Fields
4
4
  class TextField < BaseField
5
5
 
6
- self.type = 'text'
6
+ self.input_type = 'text'
7
7
  end
8
8
  end
9
9
  end
@@ -2,10 +2,10 @@ module SimpleBootstrapForm
2
2
  module HorizontalForm
3
3
  class FormBuilder < ActionView::Helpers::FormBuilder
4
4
 
5
- def initialize(object_name, object, template, options={}, block=nil)
5
+ def initialize(object_name, object, template, options={})
6
6
  @field_factory = FieldFactory.new self, template
7
7
  process_options options
8
- super object_name, object, template, options_for_rails_form_builder, block
8
+ super object_name, object, template, options_for_rails_form_builder
9
9
  end
10
10
 
11
11
  def input(name, supplied_options = {})
@@ -1,3 +1,3 @@
1
1
  module SimpleBootstrapForm
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
@@ -8,6 +8,7 @@ require 'simple_bootstrap_form/horizontal_form/fields/boolean_field'
8
8
  require 'simple_bootstrap_form/horizontal_form/fields/datetime_field'
9
9
  require 'simple_bootstrap_form/horizontal_form/fields/email_field'
10
10
  require 'simple_bootstrap_form/horizontal_form/fields/password_field'
11
+ require 'simple_bootstrap_form/horizontal_form/fields/select_field'
11
12
  require 'simple_bootstrap_form/horizontal_form/fields/text_field'
12
13
  require 'simple_bootstrap_form/horizontal_form/fields/textarea_field'
13
14
 
@@ -7,7 +7,8 @@ describe SimpleBootstrapForm::FieldFactory do
7
7
  describe "#for_attribute" do
8
8
  let(:column) { double 'column' }
9
9
  let(:attr_name) { 'foo' }
10
- let(:options) { {} }
10
+ let(:required_options) { { label_size: 'foo', input_size: 'bar' } }
11
+ let(:options) { required_options }
11
12
 
12
13
  def setup_stubs
13
14
  allow(builder).to receive(:object).and_return(model)
@@ -27,7 +28,7 @@ describe SimpleBootstrapForm::FieldFactory do
27
28
  it { expect(subject).to be_a SimpleBootstrapForm::HorizontalForm::Fields::TextField }
28
29
 
29
30
  context "when given an :as option" do
30
- let(:options) { { as: :password } }
31
+ let(:options) { required_options.merge as: :password }
31
32
 
32
33
  it "should override the default field type with the supplied one" do
33
34
  expect(subject).to be_a SimpleBootstrapForm::HorizontalForm::Fields::PasswordField
@@ -60,6 +61,12 @@ describe SimpleBootstrapForm::FieldFactory do
60
61
 
61
62
  it { expect(subject).to be_a SimpleBootstrapForm::HorizontalForm::Fields::DatetimeField }
62
63
  end
64
+
65
+ context "when given a collection option" do
66
+ let(:options) { required_options.merge collection: %w[one two] }
67
+
68
+ it { expect(subject).to be_a SimpleBootstrapForm::HorizontalForm::Fields::SelectField }
69
+ end
63
70
  end
64
71
  end
65
72
  end
@@ -0,0 +1,122 @@
1
+ require 'spec_helper'
2
+
3
+ describe "have_element RSpec matcher" do
4
+ subject { '<input id="foo" class="bar baz" name="attr1" type="text" placeholder="Attr One"/>' }
5
+
6
+ it { should have_element("input") }
7
+ it { should_not have_element("label") }
8
+
9
+ it { should have_element("input").with_id('foo') }
10
+ it { should_not have_element("input").with_id('bad_id') }
11
+
12
+ it { should have_element("input").with_class('bar') }
13
+ it { should_not have_element("input").with_class('bad_class') }
14
+
15
+ it { should have_element("input").with_attr_value(:name, 'attr1') }
16
+ it { should have_element("input").with_attr_value('name', 'attr1') }
17
+ it { should_not have_element("input").with_attr_value(:name, 'bad_value') }
18
+ it { should_not have_element("input").with_attr_value('name', 'bad_value') }
19
+
20
+ it { should have_element("input").with_type('text') }
21
+ it { should_not have_element("input").with_type('radio') }
22
+
23
+ it { should have_element("input").with_placeholder('Attr One') }
24
+ it { should_not have_element("input").with_placeholder('bad placeholder') }
25
+
26
+ describe "#with_only_classes" do
27
+ it { should have_element("input").with_only_classes('bar baz') }
28
+ it { should_not have_element("input").with_only_classes('bar') }
29
+
30
+ it "should have an appropriate error message for should" do
31
+ matcher = have_element("input").with_only_classes('bar')
32
+ expect(matcher.matches? subject).to eq false
33
+ expect(matcher.failure_message_for_should).to eq(
34
+ %Q{expected #{subject} to have classes "bar" but it has classes "bar baz"}
35
+ )
36
+ end
37
+
38
+ it "should have an appropriate error message for should not" do
39
+ matcher = have_element("input").with_only_classes('bar baz')
40
+ expect(matcher.matches? subject).to eq true
41
+ expect(matcher.failure_message_for_should_not).to eq(
42
+ %Q{expected #{subject} not to have classes "bar baz" but it has classes "bar baz"}
43
+ )
44
+ end
45
+ end
46
+
47
+ describe "combo" do
48
+ it { should have_element("input").with_id('foo')
49
+ .with_class('bar')
50
+ .with_only_classes('bar baz')
51
+ .with_attr_value(:name, 'attr1')
52
+ .with_type('text')
53
+ .with_placeholder('Attr One')
54
+ }
55
+
56
+ it "should have an appropriate error message for should" do
57
+ matcher = have_element("input").with_id('foo')
58
+ .with_class('bar')
59
+ .with_only_classes('bad class list')
60
+ .with_attr_value(:name, 'attr1')
61
+ .with_type('text')
62
+ .with_placeholder('Attr One')
63
+
64
+ expect(matcher.matches? subject).to eq false
65
+ expect(matcher.failure_message_for_should).to eq(
66
+ %Q{expected #{subject} to have classes "bad class list" but it has classes "bar baz"}
67
+ )
68
+ end
69
+
70
+ it { should_not have_element("input").with_id('foo')
71
+ .with_class('bar')
72
+ .with_only_classes('bar baz')
73
+ .with_attr_value(:name, 'attr1')
74
+ .with_type('bad_type')
75
+ .with_placeholder('Attr One')
76
+ }
77
+ end
78
+
79
+ describe "content testing" do
80
+ subject { '<div id="foo" class="c1 c2">Some Content</div>' }
81
+ let(:inspected_subject) { '<div id="foo" class="c1 c2"/>' }
82
+
83
+ it { should have_element('div').with_content('Some Content') }
84
+ it { should_not have_element('div').with_content('Some Content with extra') }
85
+ it { should_not have_element('div').with_content('Bad Content') }
86
+
87
+ it { should have_element('div').with_id('foo').with_content('Some Content') }
88
+ it { should_not have_element('div').with_id('foo').with_content('Bad Content') }
89
+
90
+ describe "error messages" do
91
+ it "should have an appropriate error message for should" do
92
+ matcher = have_element("div").with_id('foo').with_content('Bad Content')
93
+ expect(matcher.matches? subject).to eq false
94
+ expect(matcher.failure_message_for_should).to eq(
95
+ %Q{expected #{inspected_subject} to } +
96
+ %Q{have content "Bad Content" but it has content "Some Content"}
97
+ )
98
+ end
99
+
100
+ it "should have an appropriate error message for should not" do
101
+ matcher = have_element("div").with_id('foo').with_content('Some Content')
102
+ expect(matcher.matches? subject).to eq true
103
+ expect(matcher.failure_message_for_should_not).to eq(
104
+ %Q{expected #{inspected_subject} not to } +
105
+ %Q{have content "Some Content" but it has content "Some Content"}
106
+ )
107
+ end
108
+
109
+ it "should generate correct combo error messages" do
110
+ matcher = have_element("div").with_id('foo')
111
+ .with_only_classes('bar')
112
+ .with_content('Bad Content')
113
+ expect(matcher.matches? subject).to eq false
114
+ expect(matcher.failure_message_for_should).to eq(
115
+ %Q{expected #{inspected_subject} to } +
116
+ %Q{have classes "bar" but it has classes "c1 c2"} +
117
+ %Q{ and have content "Bad Content" but it has content "Some Content"}
118
+ )
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,191 @@
1
+ require 'spec_helper'
2
+
3
+ class MockTemplate
4
+ attr_accessor :output_buffer
5
+ include ActionView::Helpers::TagHelper
6
+ include ActionView::Helpers::FormHelper
7
+ end
8
+
9
+ class Model1
10
+ include ActiveModel::Validations
11
+ include ActiveModel::Conversion
12
+ include ActiveModel::Naming
13
+
14
+ attr_accessor :attr1
15
+ end
16
+
17
+ describe SimpleBootstrapForm::HorizontalForm::Fields::BaseField do
18
+ let(:object) { Model1.new }
19
+ let(:object_name) { "model1" }
20
+ let(:template) { MockTemplate.new }
21
+ let(:form_builder) { SimpleBootstrapForm::HorizontalForm::FormBuilder.new object_name, object, template }
22
+ let(:attr_name) { "attr1" }
23
+ let(:required_options) { { label_size: 'col-sm-3', input_size: "col-sm-6" } }
24
+ let(:options) { required_options }
25
+
26
+ subject { described_class.new(form_builder, template, attr_name, options).to_s }
27
+
28
+ context "with no options" do
29
+ let(:options) { {} }
30
+
31
+ it "should raise an error" do
32
+ expect {
33
+ subject
34
+ }.to raise_error
35
+ end
36
+ end
37
+
38
+ describe "Full example demonstrating many options" do
39
+ let(:options) {
40
+ {
41
+ label_size: 'col-xs-4',
42
+ input_size: 'col-xs-6',
43
+ group_class: 'custom-group-class',
44
+ label: 'Custom Label',
45
+ placeholder: 'Custom Placeholder'
46
+ }
47
+ }
48
+
49
+ it "should generate the correct output" do
50
+ expect(pretty_html subject).to eq outdent <<-HTML
51
+ <div class="form-group custom-group-class">
52
+ <label class="col-xs-4 control-label" for="model1_attr1">Custom Label</label>
53
+ <div class="col-xs-6">
54
+ <input class="form-control" id="model1_attr1" name="model1[attr1]" placeholder="Custom Placeholder" type="text" />
55
+ </div>
56
+ </div>
57
+ HTML
58
+ end
59
+ end
60
+
61
+ describe "Group class" do
62
+ context "without a group class option" do
63
+ let(:options) { required_options.merge label: "Custom Label" }
64
+
65
+ it "should generate a group class using the object and field name" do
66
+ expect(subject).to have_element('div.form-group')
67
+ .with_class('model1_attr1_group')
68
+ end
69
+ end
70
+
71
+ context "given option group_class: false" do
72
+ let(:options) { required_options.merge group_class: false }
73
+
74
+ it "should only give the group class form-group" do
75
+ expect(subject).to have_element('div.form-group').with_only_classes('form-group')
76
+ end
77
+ end
78
+
79
+ context "given a custom group_class" do
80
+ let(:options) { required_options.merge group_class: "CUSTOM_GROUP_CLASS" }
81
+
82
+ it "should incorporate the custom group" do
83
+ expect(subject).to have_element('div.form-group')
84
+ .with_class('CUSTOM_GROUP_CLASS')
85
+ end
86
+ end
87
+ end
88
+
89
+ describe "Form columns" do
90
+ context "with label/input sizes" do
91
+ let(:options) { { label_size: 'LABEL_CSS_CLASS', input_size: "INPUT_CSS_CLASS" } }
92
+
93
+ it "should generate a horizontal field, defaulting to type text" do
94
+ expect(subject).to have_element "label.LABEL_CSS_CLASS"
95
+ expect(subject).to have_element "div.INPUT_CSS_CLASS > input"
96
+ end
97
+ end
98
+ end
99
+
100
+ describe "Label" do
101
+ context "with no label option" do
102
+ it "should generate the label by humanizing the object name" do
103
+ expect(subject).to have_element('label').with_content('Attr1')
104
+ end
105
+ end
106
+
107
+ context "for a required field" do
108
+ before do
109
+ allow(Model1).to receive(:validators_on).with('attr1').and_return(
110
+ [ ActiveModel::Validations::PresenceValidator.new(attributes:'') ]
111
+ )
112
+ end
113
+
114
+ it "should add an asterisk" do
115
+ expect(subject).to have_element('label').with_content("* Attr1")
116
+ end
117
+ end
118
+
119
+ context "Given a custom label" do
120
+ let(:options) { required_options.merge label: "Custom Label" }
121
+
122
+ it "should use the custom label" do
123
+ expect(subject).to have_element('label').with_content('Custom Label')
124
+ end
125
+
126
+ context "for a required field" do
127
+ before do
128
+ allow(Model1).to receive(:validators_on).with('attr1').and_return(
129
+ [ ActiveModel::Validations::PresenceValidator.new(attributes:'') ]
130
+ )
131
+ end
132
+
133
+ it "should add an asterisk" do
134
+ expect(subject).to have_element('label').with_content("* Custom Label")
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ describe "Placeholder" do
141
+ context "with no placeholder option" do
142
+ it "should generate the placeholder by humanizing the object name" do
143
+ expect(subject).to have_element('input').with_placeholder('Attr1')
144
+ end
145
+ end
146
+
147
+ context "Given a placeholder option" do
148
+ let(:options) { required_options.merge placeholder: "Custom Placeholder" }
149
+
150
+ it "should use the custom label" do
151
+ expect(subject).to have_element('input').with_placeholder('Custom Placeholder')
152
+ end
153
+ end
154
+ end
155
+
156
+ describe "Required Fields" do
157
+ context "for a required field" do
158
+ before do
159
+ allow(Model1).to receive(:validators_on).with('attr1').and_return(
160
+ [ ActiveModel::Validations::PresenceValidator.new(attributes:'') ]
161
+ )
162
+ end
163
+
164
+ it "should add required=required to the input" do
165
+ expect(subject).to have_element('input').with_attr_value(:required, 'required')
166
+ end
167
+
168
+ context "given option :required => false" do
169
+ let(:options) { required_options.merge required: false }
170
+
171
+ it "should not add required=required to the input" do
172
+ expect(subject).not_to have_element('input').with_attr_value(:required, 'required')
173
+ end
174
+ end
175
+ end
176
+
177
+ context "for an optional field" do
178
+ it "should not add required=required to the input" do
179
+ expect(subject).not_to have_element('input').with_attr_value(:required, 'required')
180
+ end
181
+
182
+ context "given option :required => true" do
183
+ let(:options) { required_options.merge required: true }
184
+
185
+ it "should not add required=required to the input" do
186
+ expect(subject).to have_element('input').with_attr_value(:required, 'required')
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,63 @@
1
+ require 'spec_helper'
2
+
3
+ class MockTemplate
4
+ attr_accessor :output_buffer
5
+ include ActionView::Helpers::TagHelper
6
+ include ActionView::Helpers::FormHelper
7
+ include ActionView::Helpers::FormOptionsHelper
8
+ end
9
+
10
+ class Model1
11
+ include ActiveModel::Validations
12
+ include ActiveModel::Conversion
13
+ include ActiveModel::Naming
14
+
15
+ attr_accessor :attr1
16
+ end
17
+
18
+ describe SimpleBootstrapForm::HorizontalForm::Fields::SelectField do
19
+
20
+ let(:object) { Model1.new }
21
+ let(:object_name) { "model1" }
22
+ let(:template) { MockTemplate.new }
23
+ let(:form_builder) { SimpleBootstrapForm::HorizontalForm::FormBuilder.new object_name, object, template }
24
+ let(:attr_name) { "attr1" }
25
+ let(:required_options) { { label_size: 'col-sm-3', input_size: "col-sm-6" } }
26
+ let(:options) { required_options }
27
+
28
+ subject { described_class.new(form_builder, template, attr_name, options).to_s }
29
+
30
+ context "without a collection: option" do
31
+ it { expect { subject.to_s }.to raise_error }
32
+ end
33
+
34
+ context "with a simple array collection" do
35
+ let(:options) { required_options.merge collection: [1,3,5] }
36
+ before { object.attr1 = 5 }
37
+
38
+ it "should generate a select tag" do
39
+ expect(pretty_html subject.to_s).to eq outdent <<-HTML
40
+ <div class="form-group model1_attr1_group">
41
+ <label class="col-sm-3 control-label" for="model1_attr1">Attr1</label>
42
+ <div class="col-sm-6">
43
+ <select class="form-control" id="model1_attr1" name="model1[attr1]">
44
+ <option value="1">1</option>
45
+ <option value="3">3</option>
46
+ <option selected="selected" value="5">5</option>
47
+ </select>
48
+ </div>
49
+ </div>
50
+ HTML
51
+ end
52
+
53
+ context "for a required attribute" do
54
+ before do
55
+ allow(Model1).to receive(:validators_on).with('attr1').and_return(
56
+ [ ActiveModel::Validations::PresenceValidator.new(attributes:'') ]
57
+ )
58
+ end
59
+
60
+ it { should have_element('select').with_attr_value(:required, 'required') }
61
+ end
62
+ end
63
+ end
@@ -1,9 +1,5 @@
1
1
  require 'spec_helper'
2
2
 
3
- def pretty_print(html)
4
- Nokogiri::XML(html, &:noblanks).to_xhtml
5
- end
6
-
7
3
  describe SimpleBootstrapForm, type: :helper do
8
4
 
9
5
  def account_form
@@ -234,6 +230,23 @@ describe SimpleBootstrapForm, type: :helper do
234
230
  end
235
231
  end
236
232
  end
233
+
234
+ describe "select" do
235
+ let(:field_id) { "account_email" }
236
+ before { model.email = "foo" }
237
+
238
+ subject {
239
+ helper.bootstrap_form_for model, layout: 'horizontal' do |f|
240
+ f.input :email, collection: %w[foo bar]
241
+ end
242
+ }
243
+
244
+ it "should generate a select tag with options" do
245
+ should have_element '.form-group.account_email_group > div.col-sm-6 > select#account_email'
246
+ should have_element('select#account_email > option[value="foo"][selected="selected"]').with_content("foo")
247
+ should have_element('select#account_email > option[value="bar"]').with_content("bar")
248
+ end
249
+ end
237
250
  end
238
251
  end
239
252
 
@@ -293,7 +306,7 @@ describe SimpleBootstrapForm, type: :helper do
293
306
  end
294
307
  }
295
308
 
296
- it "should generate the correct output"
309
+ #it "should generate the correct output"
297
310
  end
298
311
 
299
312
  describe "Inline form" do
@@ -426,7 +439,7 @@ describe SimpleBootstrapForm, type: :helper do
426
439
  }
427
440
 
428
441
  it "should generate the correct output" do
429
- #expect(pretty_print subject).to eq horizontal_form_output
442
+ #expect(pretty_html subject).to eq horizontal_form_output
430
443
  end
431
444
  end
432
445
  end
@@ -4,41 +4,49 @@ RSpec::Matchers.define :have_element do |selector|
4
4
  match_for_should do |markup|
5
5
  @selector = selector
6
6
  @markup = markup
7
- element_is_present && classes_match
7
+ element_is_present && classes_match && content_matches
8
8
  end
9
9
 
10
- match_for_should_not do |markup|
11
- @selector = selector
12
- @markup = markup
13
- !element_is_present || !classes_match
14
- end
10
+ #match_for_should_not do |markup|
11
+ # @selector = selector
12
+ # @markup = markup
13
+ # !element_is_present || !classes_match || !content_matches
14
+ #end
15
15
 
16
16
  failure_message_for_should do |markup|
17
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(' ')}\""
18
+ "expected to find an element matching #{actual_selector} in #{markup}"
22
19
  else
20
+ error_messages = [
21
+ classes_match_error_message,
22
+ content_matches_error_message(:should)
23
+ ].reject(&:blank?)
24
+ "expected #{inspect_the_element} to " + error_messages.join(' and ')
23
25
  end
24
26
  end
25
27
 
26
28
  failure_message_for_should_not do |markup|
27
- "expected #{inspect_the_element} not to have classes \"#{@expected_classes.sort.join(' ')}\" in #{markup}"
29
+ if checking_classes? || checking_content?
30
+ if the_element.nil?
31
+ # If we have post tag-retrieval checks, it is an error not to find the tag
32
+ "expected to find an element matching #{actual_selector} in #{markup}"
33
+ else
34
+ error_messages = [
35
+ classes_match_error_message,
36
+ content_matches_error_message(:should_not)
37
+ ].reject(&:blank?)
38
+ "expected #{inspect_the_element} not to " + error_messages.join(' and ')
39
+ end
40
+ else
41
+ "expected not to find an element matching #{actual_selector} in #{markup}"
42
+ end
28
43
  end
29
44
 
30
45
  def element_is_present
31
46
  the_element.is_a? Nokogiri::XML::Element
32
47
  end
33
48
 
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
49
+ ########## Chain assertions used to build the selector
42
50
 
43
51
  chain :with_id do |id|
44
52
  @id = id
@@ -82,15 +90,53 @@ RSpec::Matchers.define :have_element do |selector|
82
90
  @expected_classes = classes.split ' '
83
91
  end
84
92
 
93
+ def checking_classes?
94
+ !!@expected_classes
95
+ end
96
+
85
97
  def actual_classes
86
- the_element['class'].split ' '
98
+ @actual_classes ||= the_element['class'].split ' '
87
99
  end
88
100
 
89
101
  def classes_match
90
102
  return true unless @expected_classes
91
103
  actual_classes.sort == @expected_classes.sort
92
104
  end
105
+
106
+ def classes_match_error_message
107
+ return nil unless checking_classes?
108
+ "have classes \"%s\" but it has classes \"%s\"" % [
109
+ @expected_classes.sort.join(' '),
110
+ @actual_classes.sort.join(' ')
111
+ ]
112
+ end
113
+
114
+ chain :with_content do |content|
115
+ @expected_content = content
116
+ end
117
+
118
+ def checking_content?
119
+ !!@expected_content
120
+ end
93
121
 
122
+ def actual_content
123
+ @actual_content ||= the_element.text
124
+ end
125
+
126
+ def content_matches
127
+ return true unless @expected_content
128
+ actual_content == @expected_content
129
+ end
130
+
131
+ def content_matches_error_message(comparison=:should)
132
+ return nil unless checking_content?
133
+ return nil if comparison == :should && content_matches
134
+ return nil if comparison == :should_not && !content_matches
135
+ "have content \"%s\" but it has content \"%s\"" % [
136
+ @expected_content, @actual_content
137
+ ]
138
+ end
139
+
94
140
  ########## Utility methods
95
141
 
96
142
  def the_element
@@ -0,0 +1,19 @@
1
+ module PrettyHtml
2
+
3
+ # Methods for matching HTML output with blocks of HTML in spec files.
4
+
5
+ def pretty_html(html)
6
+ Nokogiri::XML(html, &:noblanks).to_xhtml
7
+ end
8
+
9
+ # Outdent all lines by the amount of whitespace before the first line
10
+ def outdent(text)
11
+ lines = text.split("\n")
12
+ indented_with = /^ +/.match(lines.first)[0]
13
+ lines.map { |line| line.gsub(/^#{indented_with}/, '') }.join("\n") + "\n"
14
+ end
15
+ end
16
+
17
+ RSpec.configure do |config|
18
+ config.include PrettyHtml
19
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple_bootstrap_form
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Pierson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-02-20 00:00:00.000000000 Z
11
+ date: 2014-03-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -121,6 +121,7 @@ files:
121
121
  - lib/simple_bootstrap_form/horizontal_form/fields/datetime_field.rb
122
122
  - lib/simple_bootstrap_form/horizontal_form/fields/email_field.rb
123
123
  - lib/simple_bootstrap_form/horizontal_form/fields/password_field.rb
124
+ - lib/simple_bootstrap_form/horizontal_form/fields/select_field.rb
124
125
  - lib/simple_bootstrap_form/horizontal_form/fields/text_field.rb
125
126
  - lib/simple_bootstrap_form/horizontal_form/fields/textarea_field.rb
126
127
  - lib/simple_bootstrap_form/horizontal_form/form_builder.rb
@@ -170,9 +171,13 @@ files:
170
171
  - spec/dummy/public/500.html
171
172
  - spec/dummy/public/favicon.ico
172
173
  - spec/field_factory_spec.rb
174
+ - spec/have_element_spec.rb
175
+ - spec/horizontal_form/fields/base_field_spec.rb
176
+ - spec/horizontal_form/fields/select_field_spec.rb
173
177
  - spec/simple_bootstrap_form_spec.rb
174
178
  - spec/spec_helper.rb
175
179
  - spec/support/have_element.rb
180
+ - spec/support/pretty_html.rb
176
181
  homepage: https://github.com/Piersonally/simple_bootstrap_form
177
182
  licenses:
178
183
  - MIT
@@ -242,6 +247,10 @@ test_files:
242
247
  - spec/dummy/public/500.html
243
248
  - spec/dummy/public/favicon.ico
244
249
  - spec/field_factory_spec.rb
250
+ - spec/have_element_spec.rb
251
+ - spec/horizontal_form/fields/base_field_spec.rb
252
+ - spec/horizontal_form/fields/select_field_spec.rb
245
253
  - spec/simple_bootstrap_form_spec.rb
246
254
  - spec/spec_helper.rb
247
255
  - spec/support/have_element.rb
256
+ - spec/support/pretty_html.rb