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 +4 -4
- data/Gemfile +1 -0
- data/Guardfile +4 -0
- data/README.md +33 -2
- data/lib/simple_bootstrap_form/field_factory.rb +2 -0
- data/lib/simple_bootstrap_form/horizontal_form/fields/base_field.rb +8 -4
- data/lib/simple_bootstrap_form/horizontal_form/fields/boolean_field.rb +1 -1
- data/lib/simple_bootstrap_form/horizontal_form/fields/datetime_field.rb +4 -1
- data/lib/simple_bootstrap_form/horizontal_form/fields/email_field.rb +1 -1
- data/lib/simple_bootstrap_form/horizontal_form/fields/password_field.rb +1 -1
- data/lib/simple_bootstrap_form/horizontal_form/fields/select_field.rb +40 -0
- data/lib/simple_bootstrap_form/horizontal_form/fields/text_field.rb +1 -1
- data/lib/simple_bootstrap_form/horizontal_form/form_builder.rb +2 -2
- data/lib/simple_bootstrap_form/version.rb +1 -1
- data/lib/simple_bootstrap_form.rb +1 -0
- data/spec/field_factory_spec.rb +9 -2
- data/spec/have_element_spec.rb +122 -0
- data/spec/horizontal_form/fields/base_field_spec.rb +191 -0
- data/spec/horizontal_form/fields/select_field_spec.rb +63 -0
- data/spec/simple_bootstrap_form_spec.rb +19 -6
- data/spec/support/have_element.rb +66 -20
- data/spec/support/pretty_html.rb +19 -0
- metadata +11 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1ec18e4dcc4888081e0d602db082eedff9afced8
|
4
|
+
data.tar.gz: 44da0c8960e49510e754f7b4d7f955d4ad5c3070
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a06e26323f3f4eb7c0e2874c804ac335454228dc209a9641b11923834ca83e7a584d2d09465cf49401253be32b0f0de993e35c49b1c819fb3bc1d065262656c3
|
7
|
+
data.tar.gz: 22ab8c478c9c90450ef14bc66093b55ad67ebc25fbc7d26d68fc4dc682a98ccdd4e152b09797da2acf791073b2b1fe0193e0e230a34814dbf3de59088ce32687
|
data/Gemfile
CHANGED
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
|
@@ -4,7 +4,7 @@ module SimpleBootstrapForm
|
|
4
4
|
class BaseField
|
5
5
|
|
6
6
|
class << self
|
7
|
-
attr_accessor :
|
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,
|
53
|
+
@form_builder.label @name, label_text, label_options
|
51
54
|
end
|
52
55
|
|
53
56
|
def label_text
|
54
|
-
text = @
|
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.
|
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
|
@@ -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.
|
9
|
+
self.input_type = 'datetime'
|
7
10
|
|
8
11
|
def input_tag
|
9
12
|
@template.content_tag(:div, class: 'col-sm-6') do
|
@@ -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
|
@@ -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={}
|
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
|
8
|
+
super object_name, object, template, options_for_rails_form_builder
|
9
9
|
end
|
10
10
|
|
11
11
|
def input(name, supplied_options = {})
|
@@ -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
|
|
data/spec/field_factory_spec.rb
CHANGED
@@ -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(:
|
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) {
|
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(
|
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
|
-
|
12
|
-
|
13
|
-
|
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 #{
|
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
|
-
|
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
|
-
|
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.
|
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-
|
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
|