forme 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,405 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'spec_helper.rb')
2
+
3
+ require 'rubygems'
4
+ require 'sequel'
5
+
6
+ DB = Sequel.sqlite
7
+ Sequel.default_timezone = :utc
8
+ DB.create_table(:artists) do
9
+ primary_key :id
10
+ String :name
11
+ end
12
+ DB.create_table(:albums) do
13
+ primary_key :id
14
+ foreign_key :artist_id, :artists
15
+ String :name
16
+ TrueClass :gold
17
+ TrueClass :platinum, :null=>false, :default=>false
18
+ Date :release_date
19
+ DateTime :created_at
20
+ Integer :copies_sold
21
+ end
22
+ DB.create_table(:album_infos) do
23
+ primary_key :id
24
+ foreign_key :album_id, :albums
25
+ String :info
26
+ end
27
+ DB.create_table(:tracks) do
28
+ primary_key :id
29
+ foreign_key :album_id, :albums
30
+ String :name
31
+ end
32
+ DB.create_table(:tags) do
33
+ primary_key :id
34
+ String :name
35
+ end
36
+ DB.create_table(:albums_tags) do
37
+ foreign_key :album_id, :albums
38
+ foreign_key :tag_id, :tags
39
+ end
40
+
41
+ a = DB[:artists].insert(:name=>'a')
42
+ d = DB[:artists].insert(:name=>'d')
43
+ b = DB[:albums].insert(:name=>'b', :artist_id=>a, :gold=>false, :release_date=>Date.new(2011, 6, 5), :created_at=>Date.new(2011, 6, 5), :copies_sold=>10)
44
+ DB[:tracks].insert(:name=>'m', :album_id=>b)
45
+ DB[:tracks].insert(:name=>'n', :album_id=>b)
46
+ c = DB[:albums].insert(:name=>'c', :artist_id=>d, :gold=>true, :platinum=>true)
47
+ DB[:tracks].insert(:name=>'o', :album_id=>c)
48
+ s = DB[:tags].insert(:name=>'s')
49
+ t = DB[:tags].insert(:name=>'t')
50
+ u = DB[:tags].insert(:name=>'u')
51
+ [[b, s], [b, t], [c, t]].each{|k, v| DB[:albums_tags].insert(k, v)}
52
+
53
+ Sequel::Model.plugin :forme
54
+ class Album < Sequel::Model
55
+ many_to_one :artist, :order=>:name
56
+ one_to_one :album_info
57
+ one_to_many :tracks
58
+ many_to_many :tags
59
+
60
+ def artist_name
61
+ artist.name if artist
62
+ end
63
+ end
64
+ class Artist < Sequel::Model
65
+ one_to_many :albums
66
+ def idname() "#{id}#{name}" end
67
+ end
68
+ class Track < Sequel::Model; end
69
+ class Tag < Sequel::Model; end
70
+ class AlbumInfo < Sequel::Model; end
71
+
72
+ describe "Forme Sequel::Model forms" do
73
+ before do
74
+ @ab = Album[b]
75
+ @b = Forme::Form.new(@ab)
76
+ @ac = Album[c]
77
+ @c = Forme::Form.new(@ac)
78
+ end
79
+
80
+ specify "should add appropriate attributes by default" do
81
+ @b.form.to_s.should == '<form class="forme album" method="post"></form>'
82
+ end
83
+
84
+ specify "should allow overriding of attributes" do
85
+ @b.form(:class=>:foo, :method=>:get).to_s.should == '<form class="foo forme album" method="get"></form>'
86
+ end
87
+
88
+ specify "should use a text field for strings" do
89
+ @b.input(:name).to_s.should == '<label>Name: <input id="album_name" name="album[name]" type="text" value="b"/></label>'
90
+ @c.input(:name).to_s.should == '<label>Name: <input id="album_name" name="album[name]" type="text" value="c"/></label>'
91
+ end
92
+
93
+ specify "should allow :as=>:textarea to use a textarea" do
94
+ @b.input(:name, :as=>:textarea).to_s.should == '<label>Name: <textarea id="album_name" name="album[name]">b</textarea></label>'
95
+ @c.input(:name, :as=>:textarea).to_s.should == '<label>Name: <textarea id="album_name" name="album[name]">c</textarea></label>'
96
+ end
97
+
98
+ specify "should use number inputs for integers" do
99
+ @b.input(:copies_sold).to_s.should == '<label>Copies sold: <input id="album_copies_sold" name="album[copies_sold]" type="number" value="10"/></label>'
100
+ end
101
+
102
+ specify "should use date inputs for Dates" do
103
+ @b.input(:release_date).to_s.should == '<label>Release date: <input id="album_release_date" name="album[release_date]" type="date" value="2011-06-05"/></label>'
104
+ end
105
+
106
+ specify "should use datetime inputs for Time" do
107
+ @b.input(:created_at).to_s.should =~ %r{<label>Created at: <input id="album_created_at" name="album\[created_at\]" type="datetime" value="2011-06-05 00:00:00(GMT|UTC)"/></label>}
108
+ end
109
+
110
+ specify "should use datetime inputs for DateTimes" do
111
+ @ab.values[:created_at] = DateTime.new(2011, 6, 5)
112
+ @b.input(:created_at).to_s.should == '<label>Created at: <input id="album_created_at" name="album[created_at]" type="datetime" value="2011-06-05 00:00:00+00:00"/></label>'
113
+ end
114
+
115
+ specify "should include type as wrapper class" do
116
+ @ab.values[:created_at] = DateTime.new(2011, 6, 5)
117
+ f = Forme::Form.new(@ab, :wrapper=>:li)
118
+ f.input(:name).to_s.should == '<li class="string"><label>Name: <input id="album_name" name="album[name]" type="text" value="b"/></label></li>'
119
+ f.input(:release_date).to_s.should == '<li class="date"><label>Release date: <input id="album_release_date" name="album[release_date]" type="date" value="2011-06-05"/></label></li>'
120
+ f.input(:created_at).to_s.should == '<li class="datetime"><label>Created at: <input id="album_created_at" name="album[created_at]" type="datetime" value="2011-06-05 00:00:00+00:00"/></label></li>'
121
+ end
122
+
123
+ specify "should include required * in label if required" do
124
+ @b.input(:name, :required=>true).to_s.should == '<label>Name<abbr title="required">*</abbr>: <input id="album_name" name="album[name]" required="required" type="text" value="b"/></label>'
125
+ end
126
+
127
+ specify "should include required wrapper class if required" do
128
+ f = Forme::Form.new(@ab, :wrapper=>:li)
129
+ f.input(:name, :required=>true).to_s.should == '<li class="string required"><label>Name<abbr title="required">*</abbr>: <input id="album_name" name="album[name]" required="required" type="text" value="b"/></label></li>'
130
+ end
131
+
132
+ specify "should use a select box for tri-valued boolean fields" do
133
+ @b.input(:gold).to_s.should == '<label>Gold: <select id="album_gold" name="album[gold]"><option value=""></option><option value="t">True</option><option selected="selected" value="f">False</option></select></label>'
134
+ @c.input(:gold).to_s.should == '<label>Gold: <select id="album_gold" name="album[gold]"><option value=""></option><option selected="selected" value="t">True</option><option value="f">False</option></select></label>'
135
+ end
136
+
137
+ specify "should use a checkbox for dual-valued boolean fields" do
138
+ @b.input(:platinum).to_s.should == '<label><input id="album_platinum_hidden" name="album[platinum]" type="hidden" value="f"/><input id="album_platinum" name="album[platinum]" type="checkbox" value="t"/> Platinum</label>'
139
+ @c.input(:platinum).to_s.should == '<label><input id="album_platinum_hidden" name="album[platinum]" type="hidden" value="f"/><input checked="checked" id="album_platinum" name="album[platinum]" type="checkbox" value="t"/> Platinum</label>'
140
+ end
141
+
142
+ specify "should use a select box for many_to_one associations" do
143
+ @b.input(:artist).to_s.should == '<label>Artist: <select id="album_artist_id" name="album[artist_id]"><option value=""></option><option selected="selected" value="1">a</option><option value="2">d</option></select></label>'
144
+ @c.input(:artist).to_s.should == '<label>Artist: <select id="album_artist_id" name="album[artist_id]"><option value=""></option><option value="1">a</option><option selected="selected" value="2">d</option></select></label>'
145
+ end
146
+
147
+ specify "should use a required wrapper tag for many_to_one required associations" do
148
+ @b.input(:artist, :required=>true, :wrapper=>:li).to_s.should == '<li class="many_to_one required"><label>Artist<abbr title="required">*</abbr>: <select id="album_artist_id" name="album[artist_id]" required="required"><option value=""></option><option selected="selected" value="1">a</option><option value="2">d</option></select></label></li>'
149
+ end
150
+
151
+ specify "should use a set of radio buttons for many_to_one associations with :as=>:radio option" do
152
+ @b.input(:artist, :as=>:radio).to_s.should == 'Artist: <label><input checked="checked" name="album[artist_id]" type="radio" value="1"/> a</label><label><input name="album[artist_id]" type="radio" value="2"/> d</label>'
153
+ @c.input(:artist, :as=>:radio).to_s.should == 'Artist: <label><input name="album[artist_id]" type="radio" value="1"/> a</label><label><input checked="checked" name="album[artist_id]" type="radio" value="2"/> d</label>'
154
+ end
155
+
156
+ specify "should correctly use the forms wrapper for wrapping radio buttons for many_to_one associations with :as=>:radio option" do
157
+ @b = Forme::Form.new(@ab, :wrapper=>:li)
158
+ @b.input(:artist, :as=>:radio).to_s.should == '<li class="many_to_one">Artist: <label><input checked="checked" name="album[artist_id]" type="radio" value="1"/> a</label><label><input name="album[artist_id]" type="radio" value="2"/> d</label></li>'
159
+ end
160
+
161
+ specify "should support custom wrappers for many_to_one associations with :as=>:radio via :tag_wrapper option" do
162
+ @b = Forme::Form.new(@ab, :wrapper=>:li)
163
+ @b.input(:artist, :as=>:radio, :wrapper=>proc{|t, i| i.tag(:div, {}, [t])}, :tag_wrapper=>proc{|t, i| i.tag(:span, {}, [t])}).to_s.should == '<div>Artist: <span><label><input checked="checked" name="album[artist_id]" type="radio" value="1"/> a</label></span><span><label><input name="album[artist_id]" type="radio" value="2"/> d</label></span></div>'
164
+ end
165
+
166
+ specify "should respect an :options entry" do
167
+ @b.input(:artist, :options=>Artist.order(:name).map{|a| [a.name, a.id+1]}).to_s.should == '<label>Artist: <select id="album_artist_id" name="album[artist_id]"><option value=""></option><option value="2">a</option><option value="3">d</option></select></label>'
168
+ @c.input(:artist, :options=>Artist.order(:name).map{|a| [a.name, a.id+1]}).to_s.should == '<label>Artist: <select id="album_artist_id" name="album[artist_id]"><option value=""></option><option selected="selected" value="2">a</option><option value="3">d</option></select></label>'
169
+ end
170
+
171
+ specify "should support :name_method option for choosing name method" do
172
+ @b.input(:artist, :name_method=>:idname).to_s.should == '<label>Artist: <select id="album_artist_id" name="album[artist_id]"><option value=""></option><option selected="selected" value="1">1a</option><option value="2">2d</option></select></label>'
173
+ @c.input(:artist, :name_method=>:idname).to_s.should == '<label>Artist: <select id="album_artist_id" name="album[artist_id]"><option value=""></option><option value="1">1a</option><option selected="selected" value="2">2d</option></select></label>'
174
+ end
175
+
176
+ specify "should try a list of methods to get a suitable one for select box naming" do
177
+ al = Class.new(Album){def self.name() 'Album' end}
178
+ ar = Class.new(Artist)
179
+ al.many_to_one :artist, :class=>ar
180
+ ar.class_eval{undef_method(:name)}
181
+ f = Forme::Form.new(al.new)
182
+
183
+ ar.class_eval{def number() "#{self[:name]}1" end}
184
+ f.input(:artist).to_s.should == '<label>Artist: <select id="album_artist_id" name="album[artist_id]"><option value=""></option><option value="1">a1</option><option value="2">d1</option></select></label>'
185
+
186
+ ar.class_eval{def title() "#{self[:name]}2" end}
187
+ f.input(:artist).to_s.should == '<label>Artist: <select id="album_artist_id" name="album[artist_id]"><option value=""></option><option value="1">a2</option><option value="2">d2</option></select></label>'
188
+
189
+ ar.class_eval{def name() "#{self[:name]}3" end}
190
+ f.input(:artist).to_s.should == '<label>Artist: <select id="album_artist_id" name="album[artist_id]"><option value=""></option><option value="1">a3</option><option value="2">d3</option></select></label>'
191
+
192
+ ar.class_eval{def forme_name() "#{self[:name]}4" end}
193
+ f.input(:artist).to_s.should == '<label>Artist: <select id="album_artist_id" name="album[artist_id]"><option value=""></option><option value="1">a4</option><option value="2">d4</option></select></label>'
194
+ end
195
+
196
+ specify "should raise an error when using an association without a usable name method" do
197
+ al = Class.new(Album)
198
+ ar = Class.new(Artist)
199
+ al.many_to_one :artist, :class=>ar
200
+ ar.class_eval{undef_method(:name)}
201
+ proc{Forme::Form.new(al.new).input(:artist)}.should raise_error(Forme::Error)
202
+ end
203
+
204
+ specify "should use a multiple select box for one_to_many associations" do
205
+ @b.input(:tracks).to_s.should == '<label>Tracks: <select id="album_track_pks" multiple="multiple" name="album[track_pks][]"><option selected="selected" value="1">m</option><option selected="selected" value="2">n</option><option value="3">o</option></select></label>'
206
+ @c.input(:tracks).to_s.should == '<label>Tracks: <select id="album_track_pks" multiple="multiple" name="album[track_pks][]"><option value="1">m</option><option value="2">n</option><option selected="selected" value="3">o</option></select></label>'
207
+ end
208
+
209
+ specify "should use a multiple select box for many_to_many associations" do
210
+ @b.input(:tags).to_s.should == '<label>Tags: <select id="album_tag_pks" multiple="multiple" name="album[tag_pks][]"><option selected="selected" value="1">s</option><option selected="selected" value="2">t</option><option value="3">u</option></select></label>'
211
+ @c.input(:tags).to_s.should == '<label>Tags: <select id="album_tag_pks" multiple="multiple" name="album[tag_pks][]"><option value="1">s</option><option selected="selected" value="2">t</option><option value="3">u</option></select></label>'
212
+ end
213
+
214
+ specify "should use multiple checkboxes for one_to_many associations if :as=>:checkbox" do
215
+ @b.input(:tracks, :as=>:checkbox).to_s.should == 'Tracks: <label><input checked="checked" name="album[track_pks][]" type="checkbox" value="1"/> m</label><label><input checked="checked" name="album[track_pks][]" type="checkbox" value="2"/> n</label><label><input name="album[track_pks][]" type="checkbox" value="3"/> o</label>'
216
+ @c.input(:tracks, :as=>:checkbox).to_s.should == 'Tracks: <label><input name="album[track_pks][]" type="checkbox" value="1"/> m</label><label><input name="album[track_pks][]" type="checkbox" value="2"/> n</label><label><input checked="checked" name="album[track_pks][]" type="checkbox" value="3"/> o</label>'
217
+ end
218
+
219
+ specify "should use multiple checkboxes for many_to_many associations if :as=>:checkbox" do
220
+ @b.input(:tags, :as=>:checkbox).to_s.should == 'Tags: <label><input checked="checked" name="album[tag_pks][]" type="checkbox" value="1"/> s</label><label><input checked="checked" name="album[tag_pks][]" type="checkbox" value="2"/> t</label><label><input name="album[tag_pks][]" type="checkbox" value="3"/> u</label>'
221
+ @c.input(:tags, :as=>:checkbox).to_s.should == 'Tags: <label><input name="album[tag_pks][]" type="checkbox" value="1"/> s</label><label><input checked="checked" name="album[tag_pks][]" type="checkbox" value="2"/> t</label><label><input name="album[tag_pks][]" type="checkbox" value="3"/> u</label>'
222
+ end
223
+
224
+ specify "should correctly use the forms wrapper for wrapping radio buttons for one_to_many associations with :as=>:checkbox option" do
225
+ @b = Forme::Form.new(@ab, :wrapper=>:li)
226
+ @b.input(:tracks, :as=>:checkbox).to_s.should == '<li class="one_to_many">Tracks: <label><input checked="checked" name="album[track_pks][]" type="checkbox" value="1"/> m</label><label><input checked="checked" name="album[track_pks][]" type="checkbox" value="2"/> n</label><label><input name="album[track_pks][]" type="checkbox" value="3"/> o</label></li>'
227
+ end
228
+
229
+ specify "should support custom wrappers for one_to_many associations with :as=>:checkbox via :tag_wrapper option" do
230
+ @b = Forme::Form.new(@ab, :wrapper=>:li)
231
+ @b.input(:tracks, :as=>:checkbox, :wrapper=>proc{|t, i| i.tag(:div, i.opts[:wrapper_attr], [t])}, :tag_wrapper=>proc{|t, i| i.tag(:span, {}, [t])}).to_s.should == '<div class="one_to_many">Tracks: <span><label><input checked="checked" name="album[track_pks][]" type="checkbox" value="1"/> m</label></span><span><label><input checked="checked" name="album[track_pks][]" type="checkbox" value="2"/> n</label></span><span><label><input name="album[track_pks][]" type="checkbox" value="3"/> o</label></span></div>'
232
+ end
233
+
234
+ specify "should use a text field methods not backed by columns" do
235
+ @b.input(:artist_name).to_s.should == '<label>Artist name: <input id="album_artist_name" name="album[artist_name]" type="text" value="a"/></label>'
236
+ @c.input(:artist_name).to_s.should == '<label>Artist name: <input id="album_artist_name" name="album[artist_name]" type="text" value="d"/></label>'
237
+ end
238
+
239
+ specify "should correctly show an error message if there is one" do
240
+ @ab.errors.add(:name, 'tis not valid')
241
+ @b.input(:name).to_s.should == '<label>Name: <input class="error" id="album_name" name="album[name]" type="text" value="b"/><span class="error_message">tis not valid</span></label>'
242
+ end
243
+
244
+ specify "should correctly show an error message for many_to_one associations if there is one" do
245
+ @ab.errors.add(:artist_id, 'tis not valid')
246
+ @b.input(:artist).to_s.should == '<label>Artist: <select class="error" id="album_artist_id" name="album[artist_id]"><option value=""></option><option selected="selected" value="1">a</option><option value="2">d</option></select><span class="error_message">tis not valid</span></label>'
247
+ end
248
+
249
+ specify "should raise an error for unhandled associations" do
250
+ al = Class.new(Album)
251
+ al.association_reflection(:tags)[:type] = :foo
252
+ proc{Forme::Form.new(al.new).input(:tags)}.should raise_error(Forme::Error)
253
+ end
254
+
255
+ specify "should raise an error for unhandled fields" do
256
+ proc{@b.input(:foo)}.should raise_error(Forme::Error)
257
+ end
258
+
259
+ specify "should add required attribute if the column doesn't support nil values" do
260
+ def @ab.db_schema; h = super.dup; h[:name] = h[:name].merge(:allow_null=>false); h end
261
+ @b.input(:name).to_s.should == '<label>Name<abbr title="required">*</abbr>: <input id="album_name" name="album[name]" required="required" type="text" value="b"/></label>'
262
+ end
263
+
264
+ specify "should use allow nested forms with many_to_one associations" do
265
+ Forme.form(@ab){|f| f.subform(:artist){f.input(:name)}}.to_s.should == '<form class="forme album" method="post"><input id="album_artist_attributes_id" name="album[artist_attributes][id]" type="hidden" value="1"/><label>Name: <input id="album_artist_attributes_name" name="album[artist_attributes][name]" type="text" value="a"/></label></form>'
266
+ end
267
+
268
+ specify "should have #subform respect an :inputs option" do
269
+ Forme.form(@ab){|f| f.subform(:artist, :inputs=>[:name])}.to_s.should == '<form class="forme album" method="post"><input id="album_artist_attributes_id" name="album[artist_attributes][id]" type="hidden" value="1"/><fieldset class="inputs"><legend>Artist</legend><label>Name: <input id="album_artist_attributes_name" name="album[artist_attributes][name]" type="text" value="a"/></label></fieldset></form>'
270
+ end
271
+
272
+ specify "should have #subform respect an :obj option overriding the object to use" do
273
+ Forme.form(@ab){|f| f.subform(:artist, :inputs=>[:name], :obj=>Artist.new(:name=>'b'))}.to_s.should == '<form class="forme album" method="post"><fieldset class="inputs"><legend>Artist</legend><label>Name: <input id="album_artist_attributes_name" name="album[artist_attributes][name]" type="text" value="b"/></label></fieldset></form>'
274
+ end
275
+
276
+ specify "should have #subform respect a :legend option if :inputs is used" do
277
+ Forme.form(@ab){|f| f.subform(:artist, :inputs=>[:name], :legend=>'Foo')}.to_s.should == '<form class="forme album" method="post"><input id="album_artist_attributes_id" name="album[artist_attributes][id]" type="hidden" value="1"/><fieldset class="inputs"><legend>Foo</legend><label>Name: <input id="album_artist_attributes_name" name="album[artist_attributes][name]" type="text" value="a"/></label></fieldset></form>'
278
+ end
279
+
280
+ specify "should have #subform respect a callable :legend option if :inputs is used" do
281
+ Forme.form(@ab){|f| f.subform(:artist, :inputs=>[:name], :legend=>proc{|o| "Foo - #{o.name}"})}.to_s.should == '<form class="forme album" method="post"><input id="album_artist_attributes_id" name="album[artist_attributes][id]" type="hidden" value="1"/><fieldset class="inputs"><legend>Foo - a</legend><label>Name: <input id="album_artist_attributes_name" name="album[artist_attributes][name]" type="text" value="a"/></label></fieldset></form>'
282
+ end
283
+
284
+ specify "should not add hidden primary key field for new many_to_one associated objects" do
285
+ @ab.associations[:artist] = Artist.new(:name=>'a')
286
+ Forme.form(@ab){|f| f.subform(:artist){f.input(:name)}}.to_s.should == '<form class="forme album" method="post"><label>Name: <input id="album_artist_attributes_name" name="album[artist_attributes][name]" type="text" value="a"/></label></form>'
287
+ end
288
+
289
+ specify "should use allow nested forms with one_to_one associations" do
290
+ Forme.form(@ab){|f| f.subform(:album_info, :obj=>AlbumInfo.new(:info=>'a')){f.input(:info)}}.to_s.should == '<form class="forme album" method="post"><label>Info: <input id="album_album_info_attributes_info" name="album[album_info_attributes][info]" type="text" value="a"/></label></form>'
291
+ end
292
+
293
+ specify "should use allow nested forms with one_to_many associations" do
294
+ Forme.form(@ab){|f| f.subform(:tracks){f.input(:name)}}.to_s.should == '<form class="forme album" method="post"><input id="album_tracks_attributes_0_id" name="album[tracks_attributes][0][id]" type="hidden" value="1"/><label>Name: <input id="album_tracks_attributes_0_name" name="album[tracks_attributes][0][name]" type="text" value="m"/></label><input id="album_tracks_attributes_1_id" name="album[tracks_attributes][1][id]" type="hidden" value="2"/><label>Name: <input id="album_tracks_attributes_1_name" name="album[tracks_attributes][1][name]" type="text" value="n"/></label></form>'
295
+ end
296
+
297
+ specify "should support :obj option for *_to_many associations" do
298
+ Forme.form(@ab){|f| f.subform(:tracks, :obj=>[Track.new(:name=>'x'), Track.load(:id=>5, :name=>'y')]){f.input(:name)}}.to_s.should == '<form class="forme album" method="post"><label>Name: <input id="album_tracks_attributes_0_name" name="album[tracks_attributes][0][name]" type="text" value="x"/></label><input id="album_tracks_attributes_1_id" name="album[tracks_attributes][1][id]" type="hidden" value="5"/><label>Name: <input id="album_tracks_attributes_1_name" name="album[tracks_attributes][1][name]" type="text" value="y"/></label></form>'
299
+ end
300
+
301
+ specify "should auto number legends when using subform with inputs for *_to_many associations" do
302
+ Forme.form(@ab){|f| f.subform(:tracks, :inputs=>[:name])}.to_s.should == '<form class="forme album" method="post"><input id="album_tracks_attributes_0_id" name="album[tracks_attributes][0][id]" type="hidden" value="1"/><fieldset class="inputs"><legend>Track #1</legend><label>Name: <input id="album_tracks_attributes_0_name" name="album[tracks_attributes][0][name]" type="text" value="m"/></label></fieldset><input id="album_tracks_attributes_1_id" name="album[tracks_attributes][1][id]" type="hidden" value="2"/><fieldset class="inputs"><legend>Track #2</legend><label>Name: <input id="album_tracks_attributes_1_name" name="album[tracks_attributes][1][name]" type="text" value="n"/></label></fieldset></form>'
303
+ end
304
+
305
+ specify "should support callable :legend option when using subform with inputs for *_to_many associations" do
306
+ Forme.form(@ab){|f| f.subform(:tracks, :inputs=>[:name], :legend=>proc{|o, i| "Track #{i} (#{o.name})"})}.to_s.should == '<form class="forme album" method="post"><input id="album_tracks_attributes_0_id" name="album[tracks_attributes][0][id]" type="hidden" value="1"/><fieldset class="inputs"><legend>Track 0 (m)</legend><label>Name: <input id="album_tracks_attributes_0_name" name="album[tracks_attributes][0][name]" type="text" value="m"/></label></fieldset><input id="album_tracks_attributes_1_id" name="album[tracks_attributes][1][id]" type="hidden" value="2"/><fieldset class="inputs"><legend>Track 1 (n)</legend><label>Name: <input id="album_tracks_attributes_1_name" name="album[tracks_attributes][1][name]" type="text" value="n"/></label></fieldset></form>'
307
+ end
308
+
309
+ specify "should not add hidden primary key field for nested forms with one_to_many associations" do
310
+ @ab.associations[:tracks] = [Track.new(:name=>'m'), Track.new(:name=>'n')]
311
+ Forme.form(@ab){|f| f.subform(:tracks){f.input(:name)}}.to_s.should == '<form class="forme album" method="post"><label>Name: <input id="album_tracks_attributes_0_name" name="album[tracks_attributes][0][name]" type="text" value="m"/></label><label>Name: <input id="album_tracks_attributes_1_name" name="album[tracks_attributes][1][name]" type="text" value="n"/></label></form>'
312
+ end
313
+
314
+ specify "should use allow nested forms with many_to_many associations" do
315
+ Forme.form(@ab){|f| f.subform(:tags){f.input(:name)}}.to_s.should == '<form class="forme album" method="post"><input id="album_tags_attributes_0_id" name="album[tags_attributes][0][id]" type="hidden" value="1"/><label>Name: <input id="album_tags_attributes_0_name" name="album[tags_attributes][0][name]" type="text" value="s"/></label><input id="album_tags_attributes_1_id" name="album[tags_attributes][1][id]" type="hidden" value="2"/><label>Name: <input id="album_tags_attributes_1_name" name="album[tags_attributes][1][name]" type="text" value="t"/></label></form>'
316
+ end
317
+
318
+ specify "should not add hidden primary key field for nested forms with many_to_many associations" do
319
+ @ab.associations[:tags] = [Tag.new(:name=>'s'), Tag.new(:name=>'t')]
320
+ Forme.form(@ab){|f| f.subform(:tags){f.input(:name)}}.to_s.should == '<form class="forme album" method="post"><label>Name: <input id="album_tags_attributes_0_name" name="album[tags_attributes][0][name]" type="text" value="s"/></label><label>Name: <input id="album_tags_attributes_1_name" name="album[tags_attributes][1][name]" type="text" value="t"/></label></form>'
321
+ end
322
+
323
+ specify "should handle multiple nested levels" do
324
+ Forme.form(Artist[1]){|f| f.subform(:albums){f.input(:name); f.subform(:tracks){f.input(:name)}}}.to_s.to_s.should == '<form class="forme artist" method="post"><input id="artist_albums_attributes_0_id" name="artist[albums_attributes][0][id]" type="hidden" value="1"/><label>Name: <input id="artist_albums_attributes_0_name" name="artist[albums_attributes][0][name]" type="text" value="b"/></label><input id="artist_albums_attributes_0_tracks_attributes_0_id" name="artist[albums_attributes][0][tracks_attributes][0][id]" type="hidden" value="1"/><label>Name: <input id="artist_albums_attributes_0_tracks_attributes_0_name" name="artist[albums_attributes][0][tracks_attributes][0][name]" type="text" value="m"/></label><input id="artist_albums_attributes_0_tracks_attributes_1_id" name="artist[albums_attributes][0][tracks_attributes][1][id]" type="hidden" value="2"/><label>Name: <input id="artist_albums_attributes_0_tracks_attributes_1_name" name="artist[albums_attributes][0][tracks_attributes][1][name]" type="text" value="n"/></label></form>'
325
+ end
326
+ end
327
+
328
+ describe "Forme Sequel plugin default input types based on column names" do
329
+ def f(name)
330
+ DB.create_table!(:test){String name}
331
+ Forme::Form.new(Class.new(Sequel::Model){def self.name; 'Test' end; set_dataset :test}.new).input(name, :value=>'foo')
332
+ end
333
+
334
+ specify "should use password input with no value for string columns with name password" do
335
+ f(:password).to_s.should == '<label>Password: <input id="test_password" name="test[password]" type="password"/></label>'
336
+ end
337
+
338
+ specify "should use email input for string columns with name email" do
339
+ f(:email).to_s.should == '<label>Email: <input id="test_email" name="test[email]" type="email" value="foo"/></label>'
340
+ end
341
+
342
+ specify "should use tel input for string columns with name phone or fax" do
343
+ f(:phone).to_s.should == '<label>Phone: <input id="test_phone" name="test[phone]" type="tel" value="foo"/></label>'
344
+ f(:fax).to_s.should == '<label>Fax: <input id="test_fax" name="test[fax]" type="tel" value="foo"/></label>'
345
+ end
346
+
347
+ specify "should use url input for string columns with name url, uri, or website" do
348
+ f(:url).to_s.should == '<label>Url: <input id="test_url" name="test[url]" type="url" value="foo"/></label>'
349
+ f(:uri).to_s.should == '<label>Uri: <input id="test_uri" name="test[uri]" type="url" value="foo"/></label>'
350
+ f(:website).to_s.should == '<label>Website: <input id="test_website" name="test[website]" type="url" value="foo"/></label>'
351
+ end
352
+ end
353
+
354
+ describe "Forme Sequel plugin default input types based on column type" do
355
+ def f(type)
356
+ DB.create_table!(:test){column :foo, type}
357
+ Forme::Form.new(Class.new(Sequel::Model){def self.name; 'Test' end; set_dataset :test}.new).input(:foo, :value=>'foo')
358
+ end
359
+
360
+ specify "should use password input with no value for string columns with name password" do
361
+ f(File).to_s.should == '<label>Foo: <input id="test_foo" name="test[foo]" type="file"/></label>'
362
+ end
363
+ end
364
+
365
+ describe "Forme Sequel::Model validation parsing" do
366
+ def f(*a)
367
+ c = Class.new(Album){def self.name; "Album"; end}
368
+ c.plugin :validation_class_methods
369
+ c.send(*a)
370
+ Forme::Form.new(c.new)
371
+ end
372
+
373
+ specify "should turn format into a pattern" do
374
+ f(:validates_format_of, :name, :with=>/[A-z]+/).input(:name).to_s.should == '<label>Name: <input id="album_name" name="album[name]" pattern="[A-z]+" type="text"/></label>'
375
+ end
376
+
377
+ specify "should respect :title option for format" do
378
+ f(:validates_format_of, :name, :with=>/[A-z]+/, :title=>'Foo').input(:name).to_s.should == '<label>Name: <input id="album_name" name="album[name]" pattern="[A-z]+" title="Foo" type="text"/></label>'
379
+ end
380
+
381
+ specify "should use maxlength for length :maximum, :is, and :within" do
382
+ f(:validates_length_of, :name, :maximum=>10).input(:name).to_s.should == '<label>Name: <input id="album_name" maxlength="10" name="album[name]" type="text"/></label>'
383
+ f(:validates_length_of, :name, :is=>10).input(:name).to_s.should == '<label>Name: <input id="album_name" maxlength="10" name="album[name]" type="text"/></label>'
384
+ f(:validates_length_of, :name, :within=>2..10).input(:name).to_s.should == '<label>Name: <input id="album_name" maxlength="10" name="album[name]" type="text"/></label>'
385
+ f(:validates_length_of, :name, :within=>2...11).input(:name).to_s.should == '<label>Name: <input id="album_name" maxlength="10" name="album[name]" type="text"/></label>'
386
+ end
387
+
388
+ specify "should turn numericality into a pattern" do
389
+ f(:validates_numericality_of, :name).input(:name).to_s.should == '<label>Name: <input id="album_name" name="album[name]" pattern="^[+\-]?\d+(\.\d+)?$" title="must be a number" type="text"/></label>'
390
+ end
391
+
392
+ specify "should turn numericality :only_integer into a pattern" do
393
+ f(:validates_numericality_of, :name, :only_integer=>true).input(:name).to_s.should == '<label>Name: <input id="album_name" name="album[name]" pattern="^[+\-]?\d+$" title="must be a number" type="text"/></label>'
394
+ end
395
+
396
+ specify "should respect :title option for numericality" do
397
+ f(:validates_numericality_of, :name, :title=>'Foo').input(:name).to_s.should == '<label>Name: <input id="album_name" name="album[name]" pattern="^[+\-]?\d+(\.\d+)?$" title="Foo" type="text"/></label>'
398
+ end
399
+
400
+ specify "should respect :placeholder option for any validation" do
401
+ f(:validates_uniqueness_of, :name, :placeholder=>'must be unique').input(:name).to_s.should == '<label>Name: <input id="album_name" name="album[name]" placeholder="must be unique" type="text"/></label>'
402
+ end
403
+
404
+ end
405
+
@@ -0,0 +1,96 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'spec_helper.rb')
2
+
3
+ require 'rubygems'
4
+ require 'sinatra/base'
5
+ require 'forme/sinatra'
6
+ require(ENV['ERUBIS'] ? 'erubis' : 'erb')
7
+
8
+ class FormeSinatraTest < Sinatra::Base
9
+ helpers(ENV['ERUBIS'] ? Forme::Sinatra::Erubis : Forme::Sinatra::ERB)
10
+ disable :show_exceptions
11
+ enable :raise_errors
12
+
13
+ get '/' do
14
+ erb <<END
15
+ <% form([:foo, :bar], :action=>'/baz') do |f| %>
16
+ <p>FBB</p>
17
+ <%= f.input(:first) %>
18
+ <%= f.input(:last) %>
19
+ <% end %>
20
+ END
21
+ end
22
+
23
+ get '/nest' do
24
+ erb <<END
25
+ <% form([:foo, :bar], :action=>'/baz') do |f| %>
26
+ <%= f.tag(:p, {}, 'FBB') %>
27
+ <% f.tag(:div) do %>
28
+ <%= f.input(:first) %>
29
+ <%= f.input(:last) %>
30
+ <% end %>
31
+
32
+ <% end %>
33
+ END
34
+ end
35
+
36
+ get '/hash' do
37
+ erb "<% form({:action=>'/baz'}, :obj=>[:foo]) do |f| %> <%= f.input(:first) %> <% end %>"
38
+ end
39
+
40
+ get '/legend' do
41
+ erb <<END
42
+ <% form([:foo, :bar], :action=>'/baz') do |f| %>
43
+ <p>FBB</p>
44
+ <%= f.inputs([:first, :last], :legend=>'Foo') %>
45
+ <p>FBB2</p>
46
+ <% end %>
47
+ END
48
+ end
49
+
50
+ get '/combined' do
51
+ erb <<END
52
+ <% form([:foo, :bar], {:action=>'/baz'}, :inputs=>[:first], :button=>'xyz', :legend=>'123') do |f| %>
53
+ <p>FBB</p>
54
+ <%= f.input(:last) %>
55
+ <% end %>
56
+ END
57
+ end
58
+
59
+ get '/noblock' do
60
+ erb "<%= form([:foo, :bar], {:action=>'/baz'}, :inputs=>[:first], :button=>'xyz', :legend=>'123') %>"
61
+ end
62
+ end
63
+
64
+ describe "Forme Sinatra ERB integration" do
65
+ def sin_get(path)
66
+ FormeSinatraTest.new.call(@rack.merge('PATH_INFO'=>path))[2].join.gsub(/\s+/, ' ').strip
67
+ end
68
+ before do
69
+ o = Object.new
70
+ def o.puts(*) end
71
+ @rack = {'rack.input'=>'', 'REQUEST_METHOD'=>'GET', 'rack.errors'=>o}
72
+ end
73
+ specify "#form should add start and end tags and yield Forme::Form instance" do
74
+ sin_get('/').should == '<form action="/baz"> <p>FBB</p> <input id="first" name="first" type="text" value="foo"/> <input id="last" name="last" type="text" value="bar"/> </form>'
75
+ end
76
+
77
+ specify "#form should add start and end tags and yield Forme::Form instance" do
78
+ sin_get('/nest').should == '<form action="/baz"> <p>FBB</p> <div> <input id="first" name="first" type="text" value="foo"/> <input id="last" name="last" type="text" value="bar"/> </div> </form>'
79
+ end
80
+
81
+ specify "#form should accept two hashes instead of requiring obj as first argument" do
82
+ sin_get('/hash').should == '<form action="/baz"> <input id="first" name="first" type="text" value="foo"/> </form>'
83
+ end
84
+
85
+ specify "#form should deal with emitted code" do
86
+ sin_get('/legend').should == '<form action="/baz"> <p>FBB</p> <fieldset class="inputs"><legend>Foo</legend><input id="first" name="first" type="text" value="foo"/><input id="last" name="last" type="text" value="bar"/></fieldset> <p>FBB2</p> </form>'
87
+ end
88
+
89
+ specify "#form should work with :inputs, :button, and :legend options" do
90
+ sin_get('/combined').should == '<form action="/baz"><fieldset class="inputs"><legend>123</legend><input id="first" name="first" type="text" value="foo"/></fieldset> <p>FBB</p> <input id="last" name="last" type="text" value="bar"/> <input type="submit" value="xyz"/></form>'
91
+ end
92
+
93
+ specify "#form should work without a block" do
94
+ sin_get('/noblock').should == '<form action="/baz"><fieldset class="inputs"><legend>123</legend><input id="first" name="first" type="text" value="foo"/></fieldset><input type="submit" value="xyz"/></form>'
95
+ end
96
+ end