form 0.0.0 → 0.0.1.alpha1
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.
- 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
|