forme 0.5.0

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.
@@ -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