form 0.0.0 → 0.0.1.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +15 -1
- data/README.rdoc +1 -1
- data/examples/hash.rb +27 -0
- data/examples/object.rb +19 -0
- data/form.gemspec +4 -1
- data/lib/form/builder.rb +130 -0
- data/lib/form/component/base.rb +69 -0
- data/lib/form/component/button.rb +26 -0
- data/lib/form/component/input.rb +17 -0
- data/lib/form/component/label.rb +25 -0
- data/lib/form/component/text_area.rb +23 -0
- data/lib/form/component.rb +9 -0
- data/lib/form/extensions/hash.rb +10 -0
- data/lib/form/locales/en.yml +3 -0
- data/lib/form/tag.rb +106 -0
- data/lib/form/version.rb +2 -2
- data/lib/form.rb +22 -0
- data/spec/form/builder_spec.rb +95 -0
- data/spec/form/component/base_spec.rb +80 -0
- data/spec/form/component/input_spec.rb +4 -0
- data/spec/form/component/label_spec.rb +56 -0
- data/spec/form/component/text_area_spec.rb +62 -0
- data/spec/form/extensions/hash_spec.rb +9 -0
- data/spec/form/tag_spec.rb +119 -0
- data/spec/form_spec.rb +27 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/support/have_tag_matcher.rb +121 -0
- data/spec/support/helpers.rb +9 -0
- data/spec/support/input_instantiation_shared.rb +28 -0
- data/spec/support/input_shared.rb +13 -0
- metadata +89 -14
data/lib/form/version.rb
CHANGED
data/lib/form.rb
CHANGED
@@ -1,3 +1,25 @@
|
|
1
|
+
require "i18n"
|
2
|
+
require "forwardable"
|
3
|
+
|
1
4
|
module Form
|
5
|
+
autoload :Builder, "form/builder"
|
6
|
+
autoload :Component, "form/component"
|
7
|
+
autoload :Tag, "form/tag"
|
2
8
|
autoload :Version, "form/version"
|
9
|
+
|
10
|
+
# Lazily add locale file to I18n.
|
11
|
+
#
|
12
|
+
def self.add_locale(locale)
|
13
|
+
I18n.load_path << File.expand_path("../form/locales/#{locale}.yml", __FILE__)
|
14
|
+
end
|
15
|
+
|
16
|
+
add_locale :en
|
17
|
+
|
18
|
+
# Initialize a new Form::Builder object.
|
19
|
+
#
|
20
|
+
# form = Form.new(params[:user], "user")
|
21
|
+
#
|
22
|
+
def self.new(data = nil, base_name = nil)
|
23
|
+
Builder.new(data, base_name)
|
24
|
+
end
|
3
25
|
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Form::Builder do
|
4
|
+
describe "#initialize" do
|
5
|
+
let(:data) { mock }
|
6
|
+
let(:base_name) { mock }
|
7
|
+
subject { described_class.new(data, base_name) }
|
8
|
+
|
9
|
+
its(:data) { should eql(data) }
|
10
|
+
its(:base_name) { should eql(base_name) }
|
11
|
+
end
|
12
|
+
|
13
|
+
context "building fields" do
|
14
|
+
describe "#text" do
|
15
|
+
let(:component) { Form::Component::Input }
|
16
|
+
let(:type) { "text" }
|
17
|
+
let(:helper) { "text" }
|
18
|
+
it_behaves_like "input instantiation"
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "#password" do
|
22
|
+
let(:component) { Form::Component::Input }
|
23
|
+
let(:type) { "password" }
|
24
|
+
let(:helper) { "password" }
|
25
|
+
it_behaves_like "input instantiation"
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#file" do
|
29
|
+
let(:component) { Form::Component::Input }
|
30
|
+
let(:type) { "file" }
|
31
|
+
let(:helper) { "file" }
|
32
|
+
it_behaves_like "input instantiation"
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#hidden" do
|
36
|
+
let(:component) { Form::Component::Input }
|
37
|
+
let(:type) { "hidden" }
|
38
|
+
let(:helper) { "hidden" }
|
39
|
+
it_behaves_like "input instantiation"
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "#number" do
|
43
|
+
let(:component) { Form::Component::Input }
|
44
|
+
let(:type) { "number" }
|
45
|
+
let(:helper) { "number" }
|
46
|
+
it_behaves_like "input instantiation"
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "#email" do
|
50
|
+
let(:component) { Form::Component::Input }
|
51
|
+
let(:type) { "email" }
|
52
|
+
let(:helper) { "email" }
|
53
|
+
it_behaves_like "input instantiation"
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "#url" do
|
57
|
+
let(:component) { Form::Component::Input }
|
58
|
+
let(:type) { "url" }
|
59
|
+
let(:helper) { "url" }
|
60
|
+
it_behaves_like "input instantiation"
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "#search" do
|
64
|
+
let(:component) { Form::Component::Input }
|
65
|
+
let(:type) { "search" }
|
66
|
+
let(:helper) { "search" }
|
67
|
+
it_behaves_like "input instantiation"
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "#phone" do
|
71
|
+
let(:component) { Form::Component::Input }
|
72
|
+
let(:type) { "tel" }
|
73
|
+
let(:helper) { "phone" }
|
74
|
+
it_behaves_like "input instantiation"
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "#textarea" do
|
78
|
+
let(:data) { mock("dataset").as_null_object }
|
79
|
+
let(:options) { mock("textarea options").as_null_object }
|
80
|
+
let(:textarea) { mock("textarea").as_null_object }
|
81
|
+
subject { described_class.new(data, :user) }
|
82
|
+
|
83
|
+
it "instantiates Form::Component::TextArea" do
|
84
|
+
Form::Component::TextArea.should_receive(:new).with(subject, :bio, options).and_return(textarea)
|
85
|
+
subject.textarea :bio, options
|
86
|
+
end
|
87
|
+
|
88
|
+
it "calls #to_html" do
|
89
|
+
Form::Component::TextArea.stub :new => textarea
|
90
|
+
textarea.should_receive(:to_html)
|
91
|
+
subject.textarea :bio
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Form::Component::Base do
|
4
|
+
subject { described_class.new(nil, nil) }
|
5
|
+
|
6
|
+
describe "#composed_name" do
|
7
|
+
let(:form) { mock }
|
8
|
+
let(:name) { "name" }
|
9
|
+
subject { described_class.new(form, name) }
|
10
|
+
|
11
|
+
context "without base name" do
|
12
|
+
before { form.stub base_name: nil }
|
13
|
+
specify { subject.composed_name.should eql("name") }
|
14
|
+
end
|
15
|
+
|
16
|
+
context "for single base name" do
|
17
|
+
before { form.stub base_name: "user" }
|
18
|
+
specify { subject.composed_name.should eql("user[name]") }
|
19
|
+
end
|
20
|
+
|
21
|
+
context "for multiple base name" do
|
22
|
+
before { form.stub base_name: ["user", "profile"] }
|
23
|
+
specify { subject.composed_name.should eql("user[profile][name]") }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#data" do
|
28
|
+
let(:form) { mock.as_null_object }
|
29
|
+
subject { described_class.new(form, "name") }
|
30
|
+
|
31
|
+
it "delegates to form's data" do
|
32
|
+
form.should_receive(:data).once
|
33
|
+
subject.data
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "#value" do
|
38
|
+
let(:form) { mock }
|
39
|
+
let(:name) { "name" }
|
40
|
+
subject { described_class.new(form, name) }
|
41
|
+
|
42
|
+
context "with objects that respond to []" do
|
43
|
+
it "returns value for string key" do
|
44
|
+
form.stub data: {"name" => "John Doe"}
|
45
|
+
subject.value.should eql("John Doe")
|
46
|
+
end
|
47
|
+
|
48
|
+
it "returns value for symbol key" do
|
49
|
+
form.stub data: {:name => "John Doe"}
|
50
|
+
subject.value.should eql("John Doe")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "with objects that respond to the attribute" do
|
55
|
+
it "returns value" do
|
56
|
+
form.stub data: mock(name: "John Doe")
|
57
|
+
subject.value.should eql("John Doe")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "with no data objects" do
|
62
|
+
it "returns nil" do
|
63
|
+
form.stub :data
|
64
|
+
subject.value.should be_nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "#t" do
|
70
|
+
it "proxies arguments to I18n.t" do
|
71
|
+
I18n.should_receive(:t).with(:a, :b)
|
72
|
+
subject.t(:a, :b)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "#humanize" do
|
77
|
+
specify { subject.humanize("name").should eql("Name") }
|
78
|
+
specify { subject.humanize("full_name").should eql("Full name") }
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Form::Component::Label do
|
4
|
+
describe "#text" do
|
5
|
+
let(:form) { mock(base_name: nil) }
|
6
|
+
subject { described_class.new(form, :email) }
|
7
|
+
|
8
|
+
context "with simple name" do
|
9
|
+
it "returns specified text" do
|
10
|
+
subject.options[:text] = "E-mail address"
|
11
|
+
subject.text.should eql("E-mail address")
|
12
|
+
end
|
13
|
+
|
14
|
+
it "returns text for full scope" do
|
15
|
+
i18n :en, {form: {
|
16
|
+
labels: {email: "E-mail"}
|
17
|
+
}}
|
18
|
+
|
19
|
+
subject.text.should eql("E-mail")
|
20
|
+
end
|
21
|
+
|
22
|
+
it "returns humanized text" do
|
23
|
+
subject.text.should eql("Email")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "with composed name" do
|
28
|
+
before { form.stub base_name: %w[user profile] }
|
29
|
+
|
30
|
+
it "returns specified text" do
|
31
|
+
subject.options[:text] = "E-mail address"
|
32
|
+
subject.text.should eql("E-mail address")
|
33
|
+
end
|
34
|
+
|
35
|
+
it "returns text for full scope" do
|
36
|
+
i18n :en, {form: {
|
37
|
+
labels: {user: {profile: {email: "E-mail"}}}
|
38
|
+
}}
|
39
|
+
|
40
|
+
subject.text.should eql("E-mail")
|
41
|
+
end
|
42
|
+
|
43
|
+
it "returns text for root scope" do
|
44
|
+
i18n :en, {form: {
|
45
|
+
labels: {email: "E-mail"}
|
46
|
+
}}
|
47
|
+
|
48
|
+
subject.text.should eql("E-mail")
|
49
|
+
end
|
50
|
+
|
51
|
+
it "returns humanized text" do
|
52
|
+
subject.text.should eql("Email")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Form::Component::TextArea do
|
4
|
+
context "with defaults" do
|
5
|
+
let(:form) { mock(:base_name => nil, :data => nil) }
|
6
|
+
subject { described_class.new(form, :bio) }
|
7
|
+
|
8
|
+
it "includes cols" do
|
9
|
+
subject.attributes[:cols].should eql(50)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "includes rows" do
|
13
|
+
subject.attributes[:rows].should eql(5)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "includes name" do
|
17
|
+
subject.attributes[:name].should eql("bio")
|
18
|
+
end
|
19
|
+
|
20
|
+
it "returns html" do
|
21
|
+
html = subject.to_html
|
22
|
+
|
23
|
+
html.should have_tag(:textarea, :count => 1)
|
24
|
+
html.should have_tag("[cols='50']")
|
25
|
+
html.should have_tag("[rows='5']")
|
26
|
+
html.should have_tag("[name='bio']")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "with custom options" do
|
31
|
+
let(:form) { mock(:base_name => :user, :data => nil) }
|
32
|
+
subject {
|
33
|
+
described_class.new(form, :bio,
|
34
|
+
:autofocus => true,
|
35
|
+
:rows => 10,
|
36
|
+
:cols => 25
|
37
|
+
)
|
38
|
+
}
|
39
|
+
|
40
|
+
it "includes cols" do
|
41
|
+
subject.attributes[:cols].should eql(25)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "includes rows" do
|
45
|
+
subject.attributes[:rows].should eql(10)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "includes name" do
|
49
|
+
subject.attributes[:name].should eql("user[bio]")
|
50
|
+
end
|
51
|
+
|
52
|
+
it "returns html" do
|
53
|
+
html = subject.to_html
|
54
|
+
|
55
|
+
html.should have_tag(:textarea, :count => 1)
|
56
|
+
html.should have_tag("[cols='25']")
|
57
|
+
html.should have_tag("[rows='10']")
|
58
|
+
html.should have_tag("[name='user[bio]']")
|
59
|
+
html.should have_tag("[autofocus]")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Form::Tag do
|
4
|
+
subject { described_class.new(:p) }
|
5
|
+
|
6
|
+
describe "#initialize" do
|
7
|
+
it "yields instance" do
|
8
|
+
tag = nil
|
9
|
+
described_class.new(:p) {|t| tag = t}
|
10
|
+
tag.should be_a(described_class)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "evaluates block" do
|
14
|
+
tag = nil
|
15
|
+
described_class.new(:p) { tag = self }
|
16
|
+
tag.should be_a(described_class)
|
17
|
+
end
|
18
|
+
|
19
|
+
context "argument position" do
|
20
|
+
it "moves position" do
|
21
|
+
tag = described_class.new(:p, class: "hello")
|
22
|
+
tag.attributes.should eql(class: "hello")
|
23
|
+
tag.content.should eql("")
|
24
|
+
end
|
25
|
+
|
26
|
+
it "keeps position" do
|
27
|
+
tag = described_class.new(:p, "Hello", class: "hello")
|
28
|
+
tag.attributes.should eql(class: "hello")
|
29
|
+
tag.content.should eql("Hello")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#<<" do
|
35
|
+
it "appends content" do
|
36
|
+
tag = described_class.new(:p)
|
37
|
+
tag << "Hello"
|
38
|
+
tag.to_s.should eql("<p>Hello</p>")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "#void?" do
|
43
|
+
it "detects void tag" do
|
44
|
+
subject = described_class.new(:br)
|
45
|
+
subject.should be_void
|
46
|
+
end
|
47
|
+
|
48
|
+
it "rejects as open tag" do
|
49
|
+
subject = described_class.new(:p)
|
50
|
+
subject.should_not be_void
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "#to_s" do
|
55
|
+
it "renders tag" do
|
56
|
+
subject = described_class.new(:p, "Hello")
|
57
|
+
subject.to_s.should eql("<p>Hello</p>")
|
58
|
+
end
|
59
|
+
|
60
|
+
it "renders tag with attributes" do
|
61
|
+
subject = described_class.new(:p, "Hello", class: "hello")
|
62
|
+
subject.to_s.should eql(%[<p class="hello">Hello</p>])
|
63
|
+
end
|
64
|
+
|
65
|
+
it "renders open tag" do
|
66
|
+
subject = described_class.new(:hr)
|
67
|
+
subject.to_s.should eql("<hr>")
|
68
|
+
end
|
69
|
+
|
70
|
+
it "renders open tag with attributes" do
|
71
|
+
subject = described_class.new(:hr, class: "separator")
|
72
|
+
subject.to_s.should eql(%[<hr class="separator">])
|
73
|
+
end
|
74
|
+
|
75
|
+
it "renders boolean attributes" do
|
76
|
+
subject = described_class.new(:input, autofocus: true)
|
77
|
+
subject.to_s.should eql("<input autofocus>")
|
78
|
+
end
|
79
|
+
|
80
|
+
it "skips attributes with false value" do
|
81
|
+
subject = described_class.new(:input, autofocus: false)
|
82
|
+
subject.to_s.should eql("<input>")
|
83
|
+
end
|
84
|
+
|
85
|
+
it "skips attributes with empty value" do
|
86
|
+
subject = described_class.new(:input, value: "")
|
87
|
+
subject.to_s.should eql("<input>")
|
88
|
+
end
|
89
|
+
|
90
|
+
it "escapes attribute value" do
|
91
|
+
subject = described_class.new(:img, alt: "<3")
|
92
|
+
subject.to_s.should eql(%[<img alt="<3">])
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe "#tag" do
|
97
|
+
it "renders simple tag" do
|
98
|
+
subject = described_class.new(:p)
|
99
|
+
subject.tag(:strong, "Hello")
|
100
|
+
subject.to_s.should eql("<p><strong>Hello</strong></p>")
|
101
|
+
end
|
102
|
+
|
103
|
+
it "renders complex tag" do
|
104
|
+
subject = described_class.new(:form, action: "some/path") do |form|
|
105
|
+
form.tag :p do |p|
|
106
|
+
p.tag :label, "E-mail", for: "user_email"
|
107
|
+
p.tag :input, type: "email", autofocus: true
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
subject.to_s.should have_tag("form[action='some/path']") do |form|
|
112
|
+
form.should have_tag("p") do |p|
|
113
|
+
p.should have_tag("label[for=user_email]")
|
114
|
+
p.should have_tag("input[type=email][autofocus]")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|