form_input 0.9.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/LICENSE +19 -0
  4. data/README.md +3160 -0
  5. data/Rakefile +19 -0
  6. data/example/controllers/ramaze/press_release.rb +104 -0
  7. data/example/controllers/ramaze/profile.rb +38 -0
  8. data/example/controllers/sinatra/press_release.rb +114 -0
  9. data/example/controllers/sinatra/profile.rb +39 -0
  10. data/example/forms/change_password_form.rb +17 -0
  11. data/example/forms/login_form.rb +14 -0
  12. data/example/forms/lost_password_form.rb +14 -0
  13. data/example/forms/new_password_form.rb +15 -0
  14. data/example/forms/password_form.rb +18 -0
  15. data/example/forms/press_release_form.rb +153 -0
  16. data/example/forms/profile_form.rb +21 -0
  17. data/example/forms/signup_form.rb +25 -0
  18. data/example/views/press_release.slim +65 -0
  19. data/example/views/profile.slim +28 -0
  20. data/example/views/snippets/form_block.slim +27 -0
  21. data/example/views/snippets/form_chunked.slim +25 -0
  22. data/example/views/snippets/form_hidden.slim +21 -0
  23. data/example/views/snippets/form_panel.slim +89 -0
  24. data/form_input.gemspec +32 -0
  25. data/lib/form_input/core.rb +1165 -0
  26. data/lib/form_input/localize.rb +49 -0
  27. data/lib/form_input/r18n/cs.yml +97 -0
  28. data/lib/form_input/r18n/en.yml +70 -0
  29. data/lib/form_input/r18n/pl.yml +122 -0
  30. data/lib/form_input/r18n/sk.yml +120 -0
  31. data/lib/form_input/r18n.rb +163 -0
  32. data/lib/form_input/steps.rb +365 -0
  33. data/lib/form_input/types.rb +176 -0
  34. data/lib/form_input/version.rb +12 -0
  35. data/lib/form_input.rb +5 -0
  36. data/test/helper.rb +21 -0
  37. data/test/localize/en.yml +63 -0
  38. data/test/r18n/cs.yml +60 -0
  39. data/test/r18n/xx.yml +51 -0
  40. data/test/reference/cs.txt +352 -0
  41. data/test/reference/cs.yml +14 -0
  42. data/test/reference/en.txt +76 -0
  43. data/test/reference/en.yml +8 -0
  44. data/test/reference/pl.txt +440 -0
  45. data/test/reference/pl.yml +16 -0
  46. data/test/reference/sk.txt +352 -0
  47. data/test/reference/sk.yml +14 -0
  48. data/test/test_core.rb +1272 -0
  49. data/test/test_localize.rb +27 -0
  50. data/test/test_r18n.rb +373 -0
  51. data/test/test_steps.rb +482 -0
  52. data/test/test_types.rb +307 -0
  53. metadata +145 -0
data/test/test_core.rb ADDED
@@ -0,0 +1,1272 @@
1
+ # encoding: UTF-8
2
+
3
+ require_relative 'helper'
4
+
5
+ require 'form_input/core'
6
+ require 'rack/test'
7
+
8
+ class TestForm < FormInput
9
+ param! :query, :q
10
+ param :email, "Email",
11
+ form_title: "Your email",
12
+ error_title: "email address",
13
+ match: /@/,
14
+ type: :email
15
+ param :age,
16
+ min: 1,
17
+ max: 200,
18
+ filter: ->{ Integer( self, 10 ) rescue self },
19
+ class: Integer,
20
+ tag: :filter
21
+ param :rate,
22
+ inf: 0,
23
+ sup: 1,
24
+ reject: [ /[a-z]/i, /[-+]/ ],
25
+ tags: ->{ [ :filter, :float ] },
26
+ tag: :mix,
27
+ disabled: true
28
+ param :text, 1000,
29
+ min_bytesize: 2,
30
+ max_bytesize: 1999,
31
+ filter: nil
32
+ param :password, "Password",
33
+ min_size: 6,
34
+ max_size: 16,
35
+ match: [ /[A-Z]/, /[a-z]/, /\d/ ],
36
+ msg: 'Password must contain one lowercase and one uppercase letter and one digit',
37
+ type: :password do
38
+ chomp
39
+ end
40
+ array :opts,
41
+ min_count: 2,
42
+ max_count: 3,
43
+ match: /\A[01]\z/,
44
+ check: ->{ report( "Only one option may be set" ) unless value.one?{ |x| x.to_i == 1 } }
45
+ hash :on,
46
+ min_count: 2,
47
+ max_count: 4,
48
+ match: /\A\d\z/,
49
+ type: :hidden,
50
+ test: ->( value ){ report( "%p value is too large" ) if value.to_i > 8 }
51
+ end
52
+
53
+ class TestFormInputApp
54
+ def content( request )
55
+ form = TestForm.new( request )
56
+ if form.valid?
57
+ form.url_query
58
+ else
59
+ form.error_messages.join( ':' )
60
+ end
61
+ end
62
+ def call( env )
63
+ request = Rack::Request.new( env )
64
+ response = Rack::Response.new
65
+ response.write content( request )
66
+ response.finish
67
+ end
68
+ end
69
+
70
+ describe FormInput do
71
+
72
+ VALID_PARAMS = [
73
+ [ 'q=2', q: 2 ],
74
+ [ 'q=-', q: "\n-\n" ],
75
+ [ 'q=%2B', q: " + " ],
76
+ [ 'q=a+b', q: " a b " ],
77
+ [ 'q=a+b', q: " \t\r\na \t\r\n b\r\n\t " ],
78
+ [ 'q=1&email=foo%40bar.com', email: 'foo@bar.com' ],
79
+ [ 'q=1&age=12&rate=0.3', age: 12, rate: 0.3 ],
80
+ [ 'q=1&age=1&rate=0.00001', age: 1, rate: '0.00001' ],
81
+ [ 'q=1&age=200&rate=0.99999', age: 200, rate: '0.99999' ],
82
+ [ 'q=1&text=%22%27%3C%3E%26%3B%23%40%25+%09%0D%0A%2B', text: "\"'<>&;#\@% \t\r\n+" ],
83
+ [ 'q=1&text=aa', text: "aa" ],
84
+ [ 'q=1&text=' + 'a' * 1000, text: "a" * 1000 ],
85
+ [ 'q=1', text: nil ],
86
+ [ 'q=1', text: "" ],
87
+ [ 'q=1&text=++', text: " " ],
88
+ [ 'q=1', email: nil ],
89
+ [ 'q=1', email: "" ],
90
+ [ 'q=1', email: " " ],
91
+ [ 'q=1&password=Abc123', password: "Abc123" ],
92
+ [ 'q=1&password=Abc123', password: "Abc123\n" ],
93
+ [ 'q=1&password=+Abc123+', password: " Abc123 " ],
94
+ [ 'q=1&password=0123456789abcdeF', password: "0123456789abcdeF" ],
95
+ [ 'q=1', password: nil ],
96
+ [ 'q=1', password: "" ],
97
+ [ 'q=1&password=+aA1+%09', password: " aA1 \t\r\n" ],
98
+ [ 'q=1&password=+aA1+%09', password: " aA1 \t\n" ],
99
+ [ 'q=1&password=+aA1+%09', password: " aA1 \t\r" ],
100
+ [ 'q=1&password=+aA1+%09%0D%0A', password: " aA1 \t\r\n\r\n" ],
101
+ [ 'q=1&password=+aA1+%09%0A', password: " aA1 \t\n\n" ],
102
+ [ 'q=1&password=+aA1+%09%0D', password: " aA1 \t\r\r" ],
103
+ [ 'q=1&opts[]=0&opts[]=1', opts: [ 0, 1 ] ],
104
+ [ 'q=1&opts[]=0&opts[]=1&opts[]=0', opts: [ 0, 1, 0 ] ],
105
+ [ 'q=1&on[0]=1&on[2]=8', on: { 0 => 1, 2 => 8 } ],
106
+ ]
107
+
108
+ INVALID_PARAMS = [
109
+ [ 'q is required', q: nil ],
110
+ [ 'q is required', q: "" ],
111
+ [ 'q is required', q: " \t\r\n " ],
112
+ [ 'q is not a string', q: [ 10 ] ],
113
+ [ 'q is not a string', q: { "x" => "y" } ],
114
+ [ 'q must use valid encoding', q: 255.chr.force_encoding( 'UTF-8' ) ],
115
+ [ 'q is required', q: "\u{0000}" ], # Because strip strips \0 as well.
116
+ [ 'q may not contain invalid characters', q: "a\u{0000}b" ],
117
+ [ 'q may not contain invalid characters', q: "\u{0001}" ],
118
+ [ 'q may not contain invalid characters', q: "\u{2029}" ],
119
+ [ 'email address like this is not valid', email: 'abc' ],
120
+ [ 'email address may have at most 255 characters', email: 'a@' + 'a' * 254 ],
121
+ [ 'email address may have at most 255 bytes', email: 'á@' + 'a' * 253 ],
122
+ [ 'age like this is not valid', age: 0.9 ],
123
+ [ 'age must be at least 1', age: 0 ],
124
+ [ 'age may be at most 200', age: 201 ],
125
+ [ 'rate like this is not allowed', rate: '0.9e10' ],
126
+ [ 'rate like this is not allowed', rate: '-10' ],
127
+ [ 'rate must be greater than 0', rate: 0 ],
128
+ [ 'rate must be less than 1', rate: 1 ],
129
+ [ 'text must have at least 2 bytes', text: " " ],
130
+ [ 'text must have at least 2 bytes', text: "a" ],
131
+ [ 'text may have at most 1000 characters', text: "a" * 1001 ],
132
+ [ 'text may have at most 1999 bytes', text: "á" * 1000 ],
133
+ [ 'Password must contain one lowercase and one uppercase letter and one digit', password: "abc123" ],
134
+ [ 'Password must contain one lowercase and one uppercase letter and one digit', password: "ABC123" ],
135
+ [ 'Password must contain one lowercase and one uppercase letter and one digit', password: "abcABC" ],
136
+ [ 'Password must have at least 6 characters', password: " \t\r\n" ],
137
+ [ 'Password must have at least 6 characters', password: "Abc12" ],
138
+ [ 'Password may have at most 16 characters', password: "0123456789abcdefG" ],
139
+ [ 'opts are not an array', opts: "" ],
140
+ [ 'opts are not an array', opts: " " ],
141
+ [ 'opts are not an array', opts: "x" ],
142
+ [ 'opts are not an array', opts: { 1 => 2 } ],
143
+ [ 'opts contain invalid value', opts: [ 1, { 0 => 1 } ] ],
144
+ [ 'opts must have at least 2 elements', opts: [ 0 ] ],
145
+ [ 'opts may have at most 3 elements', opts: [ 0, 1, 0, 0 ] ],
146
+ [ 'Only one option may be set', opts: [ 0, 1, 1 ] ],
147
+ [ 'opts like this is not valid', opts: [ 1, 2, 3 ] ],
148
+ [ 'on are not a hash', on: "" ],
149
+ [ 'on are not a hash', on: " " ],
150
+ [ 'on are not a hash', on: "x" ],
151
+ [ 'on are not a hash', on: [ 2 ] ],
152
+ [ 'on contain invalid key', on: { "k" => 1, 2 => 3 } ],
153
+ [ 'on contain invalid key', on: { "0b0" => 1, 2 => 3 } ],
154
+ [ 'on contain invalid key', on: { "0x0" => 1, 2 => 3 } ],
155
+ [ 'on contain invalid key', on: { "foo" => 1, "bar" => 3 } ],
156
+ [ 'on contain too small key', on: { -1 => 1, 2 => 3 } ],
157
+ [ 'on contain too large key', on: { ( 1 << 64 ) => 1, 2 => 3 } ],
158
+ [ 'on contain invalid value', on: { 0 => 1, 2 => { 3 => 4 } } ],
159
+ [ 'on contain invalid value', on: { 0 => 1, 2 => [ 3 ] } ],
160
+ [ 'on must have at least 2 elements', on: { 1 => 1 } ],
161
+ [ 'on may have at most 4 elements', on: { 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5 } ],
162
+ [ 'on like this is not valid', on: { 0 => 1, 2 => 2000 } ],
163
+ [ 'on like this is not valid', on: { 0 => 1, 2 => "z" } ],
164
+ [ 'on value is too large', on: { 0 => 9, 2 => 9 } ],
165
+ ]
166
+
167
+ extend Rack::Test::Methods
168
+
169
+ def app
170
+ TestFormInputApp.new
171
+ end
172
+
173
+ def request( query )
174
+ Rack::Request.new( Rack::MockRequest.env_for( query ) )
175
+ end
176
+
177
+ def names( params )
178
+ params.map{ |x| x && x.name }
179
+ end
180
+
181
+ should 'provide request parameters' do
182
+ for result, params in VALID_PARAMS
183
+ post( '/form?q=1', params ).body.should == result
184
+ end
185
+ end
186
+
187
+ should 'detect invalid parameters' do
188
+ for result, params in INVALID_PARAMS
189
+ post( '/form?q=1', params ).body.should == result
190
+ end
191
+ end
192
+
193
+ should 'complain about incorrect parameter definition' do
194
+ ->{ TestForm.param :x, "test", "test" }.should.raise( ArgumentError )
195
+ ->{ TestForm.param :x, { type: :email }, :extra }.should.raise( ArgumentError )
196
+ ->{ TestForm.param :x, { type: :email }, nil }.should.raise( ArgumentError )
197
+
198
+ ->{ TestForm.param :query }.should.raise( ArgumentError )
199
+ ->{ TestForm.array! :opts }.should.raise( ArgumentError )
200
+
201
+ ->{ TestForm.param :params }.should.raise( ArgumentError )
202
+ ->{ TestForm.param :errors }.should.raise( ArgumentError )
203
+
204
+ ->{ TestForm.copy nil }.should.raise( ArgumentError )
205
+ ->{ TestForm.copy :foo }.should.raise( ArgumentError )
206
+ ->{ TestForm.copy Object }.should.raise( ArgumentError )
207
+ ->{ TestForm.copy TestForm }.should.raise( ArgumentError )
208
+ ->{ TestForm.copy TestForm[ :query ] }.should.raise( ArgumentError )
209
+ end
210
+
211
+ should 'be cloned and copied properly' do
212
+ f = TestForm.new
213
+ f.report( :password, "error" )
214
+ f.errors_for( :password ).should == [ "error" ]
215
+ f.errors_for( :text ).should == []
216
+ c = f.clone
217
+ c.should.not.equal f
218
+ d = f.dup
219
+ d.should.not.equal f
220
+ c.errors_for( :password ).should == [ "error" ]
221
+ c.errors_for( :text ).should == []
222
+ d.errors_for( :password ).should == []
223
+ d.errors_for( :text ).should == []
224
+ f.report( :password, "orig" )
225
+ c.report( :password, "clone" )
226
+ d.report( :password, "copy" )
227
+ f.report( :text, "Orig" )
228
+ c.report( :text, "Clone" )
229
+ d.report( :text, "Copy" )
230
+ f.errors_for( :password ).should == [ "error", "orig" ]
231
+ c.errors_for( :password ).should == [ "error", "clone" ]
232
+ d.errors_for( :password ).should == [ "copy" ]
233
+ f.errors_for( :text ).should == [ "Orig" ]
234
+ c.errors_for( :text ).should == [ "Clone" ]
235
+ d.errors_for( :text ).should == [ "Copy" ]
236
+ end
237
+
238
+ should 'be frozen properly' do
239
+ f = TestForm.new
240
+ f.should.not.be.frozen
241
+ f.freeze
242
+ f.should.be.frozen
243
+ f.should.be.invalid
244
+ f.param( :query ).should.be.invalid
245
+ f.query.should.be.nil
246
+
247
+ ->{ f.query = "x" }.should.raise( RuntimeError )
248
+ ->{ f[ :query ] = "y" }.should.raise( RuntimeError )
249
+ ->{ f.set( query: "z" ) }.should.raise( RuntimeError )
250
+ ->{ f.clear }.should.raise( RuntimeError )
251
+ ->{ f.clear( :query ) }.should.raise( RuntimeError )
252
+ ->{ f.report( :query, "error" ) }.should.raise( RuntimeError )
253
+ ->{ f.validate! }.should.raise( RuntimeError )
254
+ ->{ f.validate }.should.not.raise
255
+ ->{ f.validate? }.should.not.raise
256
+
257
+ f.clone.should.be.frozen
258
+ f.clone.should.be.invalid
259
+
260
+ ->{ f.clone.query = "x" }.should.raise( RuntimeError )
261
+ ->{ f.clone[ :query ] = "y" }.should.raise( RuntimeError )
262
+ ->{ f.clone.set( query: "z" ) }.should.raise( RuntimeError )
263
+ ->{ f.clone.clear }.should.raise( RuntimeError )
264
+ ->{ f.clone.clear( :query ) }.should.raise( RuntimeError )
265
+ ->{ f.clone.report( :query, "error" ) }.should.raise( RuntimeError )
266
+ ->{ f.clone.validate! }.should.raise( RuntimeError )
267
+ ->{ f.clone.validate }.should.not.raise
268
+ ->{ f.clone.validate? }.should.not.raise
269
+
270
+ f.dup.should.not.be.frozen
271
+ f.dup.should.be.invalid
272
+
273
+ ->{ f.dup.query = "x" }.should.not.raise
274
+ ->{ f.dup[ :query ] = "y" }.should.not.raise
275
+ ->{ f.dup.set( query: "z" ) }.should.not.raise
276
+ ->{ f.dup.clear }.should.not.raise
277
+ ->{ f.dup.clear( :query ) }.should.not.raise
278
+ ->{ f.dup.report( :query, "error" ) }.should.not.raise
279
+ ->{ f.dup.validate! }.should.not.raise
280
+ ->{ f.dup.validate }.should.not.raise
281
+ ->{ f.dup.validate? }.should.not.raise
282
+
283
+ f.query.should.be.nil
284
+ end
285
+
286
+ should 'support form inheritance' do
287
+ c = Class.new( TestForm ).param :extra
288
+ names( c.new.params ).should == [ :query, :email, :age, :rate, :text, :password, :opts, :on, :extra ]
289
+ end
290
+
291
+ should 'support parameter copying' do
292
+ c = Class.new( FormInput )
293
+ c.param :first
294
+ c.copy TestForm
295
+ c.param :last
296
+ names( c.new.params ).should == [ :first, :query, :email, :age, :rate, :text, :password, :opts, :on, :last ]
297
+
298
+ c = Class.new( FormInput )
299
+ c.param :first
300
+ c.copy TestForm[ :email ]
301
+ c.param :last
302
+ names( c.new.params ).should == [ :first, :email, :last ]
303
+
304
+ c = Class.new( FormInput )
305
+ c.param :first
306
+ c.copy TestForm[ :password, :age ]
307
+ c.param :last
308
+ names( c.new.params ).should == [ :first, :password, :age, :last ]
309
+ end
310
+
311
+ should 'allow changing options when copying parameters' do
312
+ c = Class.new( FormInput )
313
+ c.copy TestForm[ :query ]
314
+ c[ :query ].should.be.required
315
+
316
+ c = Class.new( FormInput )
317
+ c.copy TestForm[ :query ], required: false
318
+ c[ :query ].should.not.be.required
319
+
320
+ c = Class.new( FormInput )
321
+ c.copy TestForm, required: false
322
+ c[ :query ].should.not.be.required
323
+
324
+ c = Class.new( FormInput )
325
+ c.copy TestForm[ :query ], name: nil, code: nil, title: nil
326
+ p = c[ :query ]
327
+ p.name.should == :query
328
+ p.code.should == :q
329
+ p.title.should == nil
330
+
331
+ c = Class.new( FormInput )
332
+ c.copy TestForm[ :password ], name: nil, code: nil, title: nil
333
+ p = c[ :password ]
334
+ p.name.should == :password
335
+ p.code.should == :password
336
+ p.title.should == nil
337
+
338
+ c = Class.new( FormInput )
339
+ c.copy TestForm[ :email ], name: :foo, code: :bar, title: "FooBar"
340
+ p = c[ :foo ]
341
+ p.name.should == :foo
342
+ p.code.should == :bar
343
+ p.title.should == "FooBar"
344
+ end
345
+
346
+ should 'support dynamic options' do
347
+ c = Class.new( FormInput )
348
+ c.class_eval "def limit ; 5 ; end"
349
+ c.param :s, max_size: ->{ form.limit }
350
+ c.array :a, max_count: ->{ form.limit }
351
+
352
+ ->{ c[ :s ][ :max_size ] }.should.raise NoMethodError
353
+ ->{ c[ :a ][ :max_count ] }.should.raise NoMethodError
354
+
355
+ f = c.new( s: "123456" )
356
+ f.param( :s )[ :max_size ].should == 5
357
+ f.param( :a )[ :max_count ].should == 5
358
+ f.error_messages.should == [ "s may have at most 5 characters" ]
359
+ end
360
+
361
+ should 'convert to/from internal value format' do
362
+ c = Class.new( FormInput )
363
+ c.param :str
364
+ c.param :int, filter: ->{ to_i }, class: Integer
365
+ c.param :int2, filter: ->{ Integer( self, 10 ) rescue self }, class: Integer,
366
+ check: ->{ report( "%p must be odd" ) unless value.odd? }
367
+ c.param :float, filter: ->{ to_f }, class: Float
368
+ c.param :float2, filter: ->{ Float( self ) rescue self }, class: Float
369
+ c.param :date, filter: ->{ Date.parse( self ) rescue self }, format: ->{ strftime( '%m/%d/%Y' ) }, class: Date
370
+ c.param :time, filter: ->{ Time.parse( self ) rescue self }, format: ->{ strftime( '%Y-%m-%d %H:%M:%S' ) }, class: Time
371
+ c.param :bool, filter: ->{ self == 'true' unless empty? }, class: [ TrueClass, FalseClass ]
372
+ c.param :str2, filter: ->{ downcase.reverse }, format: ->{ reverse.upcase rescue self }
373
+ c.param :arr, filter: ->{ split( ',' ) }, format: ->{ join( ',' ) rescue self }, class: Array
374
+ c.param :hsh, filter: ->{ Hash[ scan( /(\d+):(\d+)/ ) ] }, format: ->{ map{ |k,v| "#{k}:#{v}" }.join( ',' ) rescue self }, class: Hash
375
+
376
+ f = c.new( request( "?str=1.5&int=1.5&float=1.5&date=2011-12-31&time=31.12.2000+10:24:05&bool=true&str2=Abc&arr=a,b&hsh=1:2,3:4" ) )
377
+ f.should.be.valid
378
+ f.to_h.should == f.to_hash
379
+ f.to_hash.should == {
380
+ str: "1.5",
381
+ int: 1,
382
+ float: 1.5,
383
+ date: Date.new( 2011, 12, 31 ),
384
+ time: Time.new( 2000, 12, 31, 10, 24, 05 ),
385
+ bool: true,
386
+ str2: "cba",
387
+ arr: [ "a", "b" ],
388
+ hsh: { "1" => "2", "3" => "4" },
389
+ }
390
+ f.url_params.should == {
391
+ str: "1.5",
392
+ int: "1",
393
+ float: "1.5",
394
+ date: "12/31/2011",
395
+ time: "2000-12-31 10:24:05",
396
+ bool: "true",
397
+ str2: "ABC",
398
+ arr: "a,b",
399
+ hsh: "1:2,3:4",
400
+ }
401
+ f.url_query.should == "str=1.5&int=1&float=1.5&date=12%2F31%2F2011&time=2000-12-31+10%3A24%3A05&bool=true&str2=ABC&arr=a%2Cb&hsh=1%3A2%2C3%3A4"
402
+
403
+ f = c.new( request( "?str=a&int=b&float=c&date=d&time=e&bool=f" ) )
404
+ f.should.be.invalid
405
+ names( f.invalid_params ).should == [ :date, :time ]
406
+ f.error_messages.should == [ "date like this is not valid", "time like this is not valid" ]
407
+ f.to_h.should == f.to_hash
408
+ f.to_hash.should == {
409
+ str: "a",
410
+ int: 0,
411
+ float: 0,
412
+ date: "d",
413
+ time: "e",
414
+ bool: false,
415
+ }
416
+ f.url_params.should == {
417
+ str: "a",
418
+ int: "0",
419
+ float: "0.0",
420
+ date: "d",
421
+ time: "e",
422
+ bool: "false",
423
+ }
424
+ f.url_query.should == "str=a&int=0&float=0.0&date=d&time=e&bool=false"
425
+
426
+ f = c.new( request( "?arr=&hsh=" ) )
427
+ f.arr.should == []
428
+ f.hsh.should == {}
429
+ names( f.incorrect_params ).should == []
430
+ names( f.invalid_params ).should == []
431
+ f.to_hash.should == {}
432
+ f.url_params.should == {}
433
+ f.url_query.should == ""
434
+
435
+ f = c.new( request( "?int=1&int2=1&float=1.5&float2=1.5" ) )
436
+ f.should.be.valid
437
+ f.to_hash.should == { int: 1, int2: 1, float: 1.5, float2: 1.5 }
438
+ f.url_params.should == { int: "1", int2: "1", float: "1.5", float2: "1.5" }
439
+ f.url_query.should == "int=1&int2=1&float=1.5&float2=1.5"
440
+
441
+ f = c.new( request( "?int=1&int2=1.5&float=foo&float2=foo" ) )
442
+ f.should.be.invalid
443
+ names( f.invalid_params ).should == [ :int2, :float2 ]
444
+ f.error_messages.should == [ "int2 like this is not valid", "float2 like this is not valid" ]
445
+ f.to_hash.should == { int: 1, int2: "1.5", float: 0, float2: "foo" }
446
+ f.url_params.should == { int: "1", int2: "1.5", float: "0.0", float2: "foo" }
447
+ f.url_query.should == "int=1&int2=1.5&float=0.0&float2=foo"
448
+
449
+ f = c.new( request( "?int=&int2=&float=&float2=" ) )
450
+ f.should.be.valid
451
+ names( f.invalid_params ).should == []
452
+ f.error_messages.should == []
453
+ f.to_hash.should == { int: 0, float: 0 }
454
+ f.url_params.should == { int: "0", float: "0.0" }
455
+ f.url_query.should == "int=0&float=0.0"
456
+
457
+ f = c.new( request( "?int=0x0a&int2=0x0a" ) )
458
+ f.should.be.invalid
459
+ names( f.invalid_params ).should == [ :int2 ]
460
+ f.error_messages.should == [ "int2 like this is not valid" ]
461
+ f.to_hash.should == { int: 0, int2: "0x0a" }
462
+ f.url_params.should == { int: "0", int2: "0x0a" }
463
+ f.url_query.should == "int=0&int2=0x0a"
464
+
465
+ f = c.new( request( "?int2=2" ) )
466
+ f.should.be.invalid
467
+ names( f.invalid_params ).should == [ :int2 ]
468
+ f.error_messages.should == [ "int2 must be odd" ]
469
+ f.to_hash.should == { int2: 2 }
470
+ f.url_params.should == { int2: "2" }
471
+ f.url_query.should == "int2=2"
472
+
473
+ p = c[ :int ]
474
+ p.format_value( nil ).should == ""
475
+ p.format_value( 10 ).should == "10"
476
+ p.format_value( "foo" ).should == "foo"
477
+
478
+ p = c[ :float ]
479
+ p.format_value( nil ).should == ""
480
+ p.format_value( 10 ).should == "10"
481
+ p.format_value( 10.0 ).should == "10.0"
482
+ p.format_value( "foo" ).should == "foo"
483
+
484
+ p = c[ :date ]
485
+ p.format_value( nil ).should == ""
486
+ p.format_value( Time.at( 123456789 ).utc ).should == "11/29/1973"
487
+ p.format_value( "foo" ).should == "foo"
488
+
489
+ p = c[ :time ]
490
+ p.format_value( nil ).should == ""
491
+ p.format_value( Time.at( 123456789 ).utc ).should == "1973-11-29 21:33:09"
492
+ p.format_value( "foo" ).should == "foo"
493
+
494
+ p = c[ :bool ]
495
+ p.format_value( nil ).should == ""
496
+ p.format_value( true ).should == "true"
497
+ p.format_value( false ).should == "false"
498
+ p.format_value( "foo" ).should == "foo"
499
+
500
+ p = c[ :str ]
501
+ p.format_value( nil ).should == ""
502
+ p.format_value( true ).should == "true"
503
+ p.format_value( false ).should == "false"
504
+ p.format_value( 10 ).should == "10"
505
+ p.format_value( 10.0 ).should == "10.0"
506
+ p.format_value( "abc" ).should == "abc"
507
+
508
+ p = c[ :str2 ]
509
+ p.format_value( nil ).should == ""
510
+ p.format_value( true ).should == "true"
511
+ p.format_value( false ).should == "false"
512
+ p.format_value( 10 ).should == "10"
513
+ p.format_value( 10.0 ).should == "10.0"
514
+ p.format_value( "abc" ).should == "CBA"
515
+
516
+ p = c[ :arr ]
517
+ p.format_value( nil ).should == ""
518
+ p.format_value( [] ).should == ""
519
+ p.format_value( true ).should == "true"
520
+ p.format_value( false ).should == "false"
521
+ p.format_value( 10 ).should == "10"
522
+ p.format_value( 10.0 ).should == "10.0"
523
+ p.format_value( "abc" ).should == "abc"
524
+
525
+ p = c[ :hsh ]
526
+ p.format_value( nil ).should == ""
527
+ p.format_value( {} ).should == ""
528
+ p.format_value( true ).should == "true"
529
+ p.format_value( false ).should == "false"
530
+ p.format_value( 10 ).should == "10"
531
+ p.format_value( 10.0 ).should == "10.0"
532
+ p.format_value( "abc" ).should == "abc"
533
+ end
534
+
535
+ should 'support input transformation' do
536
+ c = Class.new( FormInput )
537
+ c.array :a
538
+
539
+ f = c.new( request( "?a[]=abc&a[]=&a[]=123&a[]=" ) )
540
+ f.a.should == [ "abc", "", "123", "" ]
541
+
542
+ c = Class.new( FormInput )
543
+ c.array :a, filter: ->{ reverse }, transform: ->{ reject{ |x| x.empty? } }
544
+
545
+ f = c.new( request( "?a[]=abc&a[]=&a[]=123&a[]=" ) )
546
+ f.a.should == [ "cba", "321" ]
547
+
548
+ f = c.new( a: [ "abc", "", "123" ] )
549
+ f.a.should == [ "abc", "", "123" ]
550
+ end
551
+
552
+ should 'support string hash keys when allowed' do
553
+ c = Class.new( FormInput )
554
+ c.hash :h
555
+
556
+ f = c.new( request( "?h[0]=a&h[1]=b&h[2]=c" ) )
557
+ f.should.be.valid
558
+ f.h.should == { 0 => 'a', 1 => 'b', 2 => 'c' }
559
+ f.url_query.should == "h[0]=a&h[1]=b&h[2]=c"
560
+
561
+ f = c.new( request( "?h[a]=a&h[b]=b&h[c]=c" ) )
562
+ f.should.be.invalid
563
+ f.error_messages.should == [ "h contain invalid key" ]
564
+ f.h.should == { 'a' => 'a', 'b' => 'b', 'c' => 'c' }
565
+ f.url_query.should == "h[a]=a&h[b]=b&h[c]=c"
566
+
567
+ c = Class.new( FormInput )
568
+ c.hash :h, match_key: /\A\d+\z/
569
+
570
+ f = c.new( request( "?h[0]=a&h[1]=b&h[2]=c" ) )
571
+ f.should.be.valid
572
+ f.h.should == { 0 => 'a', 1 => 'b', 2 => 'c' }
573
+ f.url_query.should == "h[0]=a&h[1]=b&h[2]=c"
574
+
575
+ f = c.new( request( "?h[a]=a&h[b]=b&h[c]=c" ) )
576
+ f.should.be.invalid
577
+ f.error_messages.should == [ "h contain invalid key" ]
578
+ f.h.should == { 'a' => 'a', 'b' => 'b', 'c' => 'c' }
579
+ f.url_query.should == "h[a]=a&h[b]=b&h[c]=c"
580
+
581
+ c = Class.new( FormInput )
582
+ c.hash :h, match_key: /\A[a-z]\z/
583
+
584
+ f = c.new( request( "?h[0]=a&h[1]=b&h[2]=c" ) )
585
+ f.should.be.invalid
586
+ f.error_messages.should == [ "h contain invalid key" ]
587
+ f.h.should == { 0 => 'a', 1 => 'b', 2 => 'c' }
588
+ f.url_query.should == "h[0]=a&h[1]=b&h[2]=c"
589
+
590
+ f = c.new( request( "?h[a]=a&h[b]=b&h[c]=c" ) )
591
+ f.should.be.valid
592
+ f.h.should == { 'a' => 'a', 'b' => 'b', 'c' => 'c' }
593
+ f.url_query.should == "h[a]=a&h[b]=b&h[c]=c"
594
+
595
+ c = Class.new( FormInput )
596
+ c.hash :h, match_key: ->{ [ /\A[a-z]+\z/i, /^[A-Z]/, /[A-Z]$/ ] }
597
+
598
+ f = c.new( request( "?h[A]=a&h[Bar]=b&h[baZ]=c" ) )
599
+ f.should.be.invalid
600
+ f.error_messages.should == [ "h contain invalid key" ]
601
+ f.h.should == { 'A' => 'a', 'Bar' => 'b', 'baZ' => 'c' }
602
+ f.url_query.should == "h[A]=a&h[Bar]=b&h[baZ]=c"
603
+
604
+ f = c.new( request( "?h[A]=a&h[BAR]=b&h[BaZ]=c" ) )
605
+ f.should.be.valid
606
+ f.h.should == { 'A' => 'a', 'BAR' => 'b', 'BaZ' => 'c' }
607
+ f.url_query.should == "h[A]=a&h[BAR]=b&h[BaZ]=c"
608
+ end
609
+
610
+ should 'support select parameters' do
611
+ c = Class.new( FormInput )
612
+ c.param :single, data: ->{ 2.times.map{ |i| [ i, ( 65 + i ).chr ] } }, class: Integer do to_i end
613
+ c.array :multi, data: ->{ 4.times.map{ |i| [ i, ( 65 + i ).chr ] } }, class: Integer do to_i end
614
+ c.param :x, filter: ->{ to_i }, class: Integer
615
+
616
+ f = c.new( single: 1, multi: [ 1, 3 ], x: 5 )
617
+ f.should.be.valid
618
+ f.to_hash.should == { single: 1, multi: [ 1, 3 ], x: 5 }
619
+ f.url_params.should == { single: "1", multi: [ "1", "3" ], x: "5" }
620
+ f.url_query.should == "single=1&multi[]=1&multi[]=3&x=5"
621
+
622
+ p = f.param( :single )
623
+ p.data.should == [ [ 0, "A" ], [ 1, "B" ] ]
624
+ p.code.should == :single
625
+ p.form_name.should == "single"
626
+ p.form_value.should == "1"
627
+ p.selected?( nil ).should.be.false
628
+ p.selected?( 0 ).should.be.false
629
+ p.selected?( 1 ).should.be.true
630
+ p.selected?( 2 ).should.be.false
631
+
632
+ p = f.param( :multi )
633
+ p.data.should == [ [ 0, "A" ], [ 1, "B" ], [ 2, "C" ], [ 3, "D" ] ]
634
+ p.code.should == :multi
635
+ p.form_name.should == "multi[]"
636
+ p.form_value.should == [ "1", "3" ]
637
+ p.selected?( nil ).should.be.false
638
+ p.selected?( 0 ).should.be.false
639
+ p.selected?( 1 ).should.be.true
640
+ p.selected?( 2 ).should.be.false
641
+ p.selected?( 3 ).should.be.true
642
+
643
+ p = f.param( :x )
644
+ p.data.should == []
645
+ p.code.should == :x
646
+ p.form_name.should == "x"
647
+ p.form_value.should == "5"
648
+
649
+ f = c.new( request( "?single=0&multi[]=0&multi[]=2&x=3" ) ) ;
650
+ f.should.be.valid
651
+ f.to_hash.should == { single: 0, multi: [ 0, 2 ], x: 3 }
652
+ f.url_params.should == { single: "0", multi: [ "0", "2" ], x: "3" }
653
+ f.url_query.should == "single=0&multi[]=0&multi[]=2&x=3"
654
+
655
+ f = c.new( request( "?single=5&multi[]=5" ) ) ;
656
+ f.should.be.valid
657
+ f.to_hash.should == { single: 5, multi: [ 5 ] }
658
+ f.url_params.should == { single: "5", multi: [ "5" ] }
659
+ f.url_query.should == "single=5&multi[]=5"
660
+
661
+ f = c.new( request( "" ) ) ;
662
+ f.should.be.valid
663
+ f.to_hash.should == {}
664
+ f.url_params.should == {}
665
+ f.url_query.should == ""
666
+ end
667
+
668
+ should 'classify parameters' do
669
+ f = TestForm.new( query: "x", text: "abc", password: nil, email: " " )
670
+ names( f.params ).should == [ :query, :email, :age, :rate, :text, :password, :opts, :on ]
671
+ names( f.params ).should == f.params_names
672
+
673
+ names( f.named_params ).should == []
674
+ names( f.named_params( :query ) ).should == [ :query ]
675
+ names( f.named_params( :text, :email ) ).should == [ :text, :email ]
676
+ names( f.named_params( :age, :age ) ).should == [ :age, :age ]
677
+
678
+ names( f.correct_params ).should == [ :query, :email, :age, :rate, :text, :password, :opts, :on ]
679
+ names( f.incorrect_params ).should == []
680
+
681
+ names( f.filled_params ).should == [ :query, :email, :text ]
682
+ names( f.empty_params ).should == [ :age, :rate, :password, :opts, :on ]
683
+ names( f.blank_params ).should == [ :email, :age, :rate, :password, :opts, :on ]
684
+
685
+ names( f.tagged_params ).should == [ :age, :rate ]
686
+ names( f.untagged_params ).should == [ :query, :email, :text, :password, :opts, :on ]
687
+
688
+ names( f.tagged_params( :filter ) ).should == [ :age, :rate ]
689
+ names( f.untagged_params( :filter ) ).should == [ :query, :email, :text, :password, :opts, :on ]
690
+
691
+ names( f.tagged_params( :float ) ).should == [ :rate ]
692
+ names( f.untagged_params( :float ) ).should == [ :query, :email, :age, :text, :password, :opts, :on ]
693
+
694
+ names( f.tagged_params( :filter, :float ) ).should == [ :age, :rate ]
695
+ names( f.untagged_params( :filter, :float ) ).should == [ :query, :email, :text, :password, :opts, :on ]
696
+
697
+ names( f.tagged_params( :foo ) ).should == []
698
+ names( f.untagged_params( :foo ) ).should == [ :query, :email, :age, :rate, :text, :password, :opts, :on ]
699
+
700
+ names( f.tagged_params( :float, :foo ) ).should == [ :rate ]
701
+ names( f.untagged_params( :float, :foo ) ).should == [ :query, :email, :age, :text, :password, :opts, :on ]
702
+
703
+ names( f.tagged_params( [] ) ).should == []
704
+ names( f.untagged_params( [] ) ).should == [ :query, :email, :age, :rate, :text, :password, :opts, :on ]
705
+
706
+ names( f.tagged_params( [ :filter ] ) ).should == [ :age, :rate ]
707
+ names( f.untagged_params( [ :filter ] ) ).should == [ :query, :email, :text, :password, :opts, :on ]
708
+
709
+ names( f.tagged_params( [ :float ] ) ).should == [ :rate ]
710
+ names( f.untagged_params( [ :float ] ) ).should == [ :query, :email, :age, :text, :password, :opts, :on ]
711
+
712
+ names( f.tagged_params( [ :filter, :float ] ) ).should == [ :age, :rate ]
713
+ names( f.untagged_params( [ :filter, :float ] ) ).should == [ :query, :email, :text, :password, :opts, :on ]
714
+
715
+ names( f.tagged_params( [ :foo ] ) ).should == []
716
+ names( f.untagged_params( [ :foo ] ) ).should == [ :query, :email, :age, :rate, :text, :password, :opts, :on ]
717
+
718
+ names( f.tagged_params( [ :float, :foo ] ) ).should == [ :rate ]
719
+ names( f.untagged_params( [ :float, :foo ] ) ).should == [ :query, :email, :age, :text, :password, :opts, :on ]
720
+
721
+ names( f.required_params ).should == [ :query ]
722
+ names( f.optional_params ).should == [ :email, :age, :rate, :text, :password, :opts, :on ]
723
+
724
+ names( f.disabled_params ).should == [ :rate ]
725
+ names( f.enabled_params ).should == [ :query, :email, :age, :text, :password, :opts, :on ]
726
+
727
+ names( f.hidden_params ).should == [ :on ]
728
+ names( f.ignored_params ).should == []
729
+ names( f.visible_params ).should == [ :query, :email, :age, :rate, :text, :password, :opts ]
730
+
731
+ names( f.array_params ).should == [ :opts ]
732
+ names( f.hash_params ).should == [ :on ]
733
+ names( f.scalar_params ).should == [ :query, :email, :age, :rate, :text, :password ]
734
+
735
+ names( f.invalid_params ).should == [ :email ]
736
+ names( f.valid_params ).should == [ :query, :age, :rate, :text, :password, :opts, :on ]
737
+ end
738
+
739
+ should 'expose details via parameters' do
740
+ f = TestForm.new( query: "x", text: "abc", :email => " " )
741
+
742
+ p = f.param( :query )
743
+ p.form.should == f
744
+ p.name.should == :query
745
+ p.code.should == :q
746
+ p.type.should == :text
747
+ p.title.should == nil
748
+ p.form_title.should == "q"
749
+ p.error_title.should == "q"
750
+ p.opts.should == { required: true, filter: FormInput::DEFAULT_FILTER, max_size: 255, max_bytesize: 255 }
751
+ p[ :form_title ].should == nil
752
+ p[ :max_size ].should == 255
753
+ p.value.should == "x"
754
+ p.form_value.should == "x"
755
+ p.should.be.correct
756
+ p.should.not.be.incorrect
757
+ p.should.not.be.blank
758
+ p.should.not.be.empty
759
+ p.should.be.filled
760
+ p.should.be.valid
761
+ p.should.not.be.invalid
762
+ p.should.be.required
763
+ p.should.not.be.optional
764
+ p.should.not.be.disabled
765
+ p.should.be.enabled
766
+ p.should.not.be.hidden
767
+ p.should.not.be.ignored
768
+ p.should.be.visible
769
+ p.should.not.be.array
770
+ p.should.not.be.hash
771
+ p.should.be.scalar
772
+ p.should.not.be.tagged
773
+ p.should.be.untagged
774
+ p.errors.should == []
775
+ p.error.should == nil
776
+ p.tags.should == []
777
+
778
+ p = f.param( :email )
779
+ p.form.should == f
780
+ p.name.should == :email
781
+ p.code.should == :email
782
+ p.type.should == :email
783
+ p.title.should == "Email"
784
+ p.form_title.should == "Your email"
785
+ p.error_title.should == "email address"
786
+ p.value.should == " "
787
+ p.form_value.should == " "
788
+ p.should.be.correct
789
+ p.should.not.be.incorrect
790
+ p.should.be.blank
791
+ p.should.not.be.empty
792
+ p.should.be.filled
793
+ p.should.not.be.valid
794
+ p.should.be.invalid
795
+ p.should.not.be.required
796
+ p.should.be.optional
797
+ p.should.not.be.disabled
798
+ p.should.be.enabled
799
+ p.should.not.be.hidden
800
+ p.should.not.be.ignored
801
+ p.should.be.visible
802
+ p.should.not.be.array
803
+ p.should.not.be.hash
804
+ p.should.be.scalar
805
+ p.should.not.be.tagged
806
+ p.should.be.untagged
807
+ p.errors.should == [ "email address like this is not valid" ]
808
+ p.error.should == "email address like this is not valid"
809
+ p.tags.should == []
810
+
811
+ p = f.param( :rate )
812
+ p.value.should == nil
813
+ p.form_value.should == ""
814
+ p.should.be.blank
815
+ p.should.be.empty
816
+ p.should.not.be.filled
817
+ p.should.be.disabled
818
+ p.should.not.be.enabled
819
+ p[ :tag ].should == :mix
820
+ p[ :tags ].should == [ :filter, :float ]
821
+ p.tags.should == [ :mix, :filter, :float ]
822
+ p.should.be.tagged
823
+ p.should.not.be.untagged
824
+ p.should.be.tagged( :filter )
825
+ p.should.not.be.untagged( :filter )
826
+ p.should.be.tagged( :foo, :float )
827
+ p.should.not.be.untagged( :foo, :float )
828
+ p.should.not.be.tagged( :foo )
829
+ p.should.be.untagged( :foo )
830
+ p.should.not.be.tagged( [] )
831
+ p.should.be.untagged( [] )
832
+ p.should.be.tagged( [ :filter ] )
833
+ p.should.not.be.untagged( [ :filter ] )
834
+ p.should.be.tagged( [ :foo, :float ] )
835
+ p.should.not.be.untagged( [ :foo, :float ] )
836
+ p.should.not.be.tagged( [ :foo ] )
837
+ p.should.be.untagged( [ :foo ] )
838
+
839
+ p = f.param( :opts )
840
+ p.value.should == nil
841
+ p.form_value.should == []
842
+ p.should.be.blank
843
+ p.should.be.empty
844
+ p.should.not.be.filled
845
+ p.should.be.array
846
+ p.should.not.be.hash
847
+ p.should.not.be.scalar
848
+
849
+ p = f.param( :on )
850
+ p.opts.values_at( :min_key, :max_key ).should == [ 0, 18446744073709551615 ]
851
+ p.value.should == nil
852
+ p.form_value.should == {}
853
+ p.should.be.blank
854
+ p.should.be.empty
855
+ p.should.not.be.filled
856
+ p.should.not.be.array
857
+ p.should.be.hash
858
+ p.should.not.be.scalar
859
+ p.type.should == :hidden
860
+ p.should.be.hidden
861
+ p.should.not.be.ignored
862
+ p.should.not.be.visible
863
+ end
864
+
865
+ should 'support both new and derived forms' do
866
+ f = TestForm.new
867
+ f.should.be.empty
868
+ f.url_query.should == ""
869
+
870
+ f = TestForm.new( request( "" ) )
871
+ f.should.be.empty
872
+ f.url_query.should == ""
873
+
874
+ f = TestForm.new( request( "?q=10" ) )
875
+ f.should.not.be.empty
876
+ f.url_query.should == "q=10"
877
+
878
+ f = TestForm.new( query: "x" )
879
+ f.should.not.be.empty
880
+ f.url_query.should == "q=x"
881
+
882
+ f.set( email: "a@b", text: "foo" )
883
+ f.should.not.be.empty
884
+ f.url_query.should == "q=x&email=a%40b&text=foo"
885
+
886
+ f.except( :email ).url_query.should == "q=x&text=foo"
887
+ f.except( :email, :query ).url_query.should == "text=foo"
888
+ f.except().url_query.should == "q=x&email=a%40b&text=foo"
889
+ f.except( [ :email ] ).url_query.should == "q=x&text=foo"
890
+ f.except( [ :email, :query ] ).url_query.should == "text=foo"
891
+ f.except( [] ).url_query.should == "q=x&email=a%40b&text=foo"
892
+
893
+ f.only( :email ).url_query.should == "email=a%40b"
894
+ f.only( :email, :query ).url_query.should == "q=x&email=a%40b"
895
+ f.only().url_query.should == ""
896
+ f.only( [ :email ] ).url_query.should == "email=a%40b"
897
+ f.only( [ :email, :query ] ).url_query.should == "q=x&email=a%40b"
898
+ f.only( [] ).url_query.should == ""
899
+
900
+ f.clear( :text )
901
+ f.should.not.be.empty
902
+ f.url_query.should == "q=x&email=a%40b"
903
+ f.clear( f.required_params )
904
+ f.should.not.be.empty
905
+ f.url_query.should == "email=a%40b"
906
+ f.clear
907
+ f.should.be.empty
908
+ f.url_query.should == ""
909
+
910
+ f = TestForm.new( { age: 2, query: "x" }, { rate: 1, query: "y" } )
911
+ f.url_query.should == "q=y&age=2&rate=1"
912
+
913
+ f = TestForm.new( request( "?q=10&age=3" ), query: "y", rate: 0 )
914
+ f.url_query.should == "q=y&age=3&rate=0"
915
+
916
+ f = TestForm.new( { query: "x", age: 5 }, request( "?rate=1&q=10" ) )
917
+ f.url_query.should == "q=10&age=5&rate=1"
918
+
919
+ f = TestForm.from_request( request( "?q=+%30&age=0" ) )
920
+ f.to_hash.should == { query: "0", age: 0 }
921
+ f.url_query.should == "q=0&age=0"
922
+
923
+ f = TestForm.from_params( q: "+%30", age: "0" )
924
+ f.to_hash.should == { query: "+%30", age: 0 }
925
+ f.url_query.should == "q=%2B%2530&age=0"
926
+
927
+ f = TestForm.from_hash( query: "+%30", age: "0" )
928
+ f.to_hash.should == { query: "+%30", age: "0" }
929
+ f.url_query.should == "q=%2B%2530&age=0"
930
+ end
931
+
932
+ should 'provide direct access to values' do
933
+ f = TestForm.new( email: "a@b", text: "foo" )
934
+
935
+ f.email.should == "a@b"
936
+ f.email = "x@y"
937
+ f.email.should == "x@y"
938
+
939
+ f[ :text ].should == "foo"
940
+ f[ :text ] = "bar"
941
+ f[ :text ].should == "bar"
942
+
943
+ f[ :query, :text ].should == [ nil, "bar" ]
944
+ f[ :text, :email ].should == [ "bar", "x@y" ]
945
+ end
946
+
947
+ should 'guard against typos in parameter names' do
948
+ f = TestForm.new
949
+
950
+ f.param( :typo ).should == nil
951
+ f.named_params( :typo ).should == [ nil ]
952
+ f.named_params( :typo, :missing ).should == [ nil, nil ]
953
+
954
+ ->{ f.set( typo: 10 ) }.should.raise( NoMethodError )
955
+ ->{ f.clear( :typo ) }.should.raise( ArgumentError )
956
+ ->{ f.except( :typo ) }.should.raise( ArgumentError )
957
+ ->{ f.except( [ :typo ] ) }.should.raise( ArgumentError )
958
+ ->{ f.only( :typo ) }.should.raise( ArgumentError )
959
+ ->{ f.only( [ :typo ] ) }.should.raise( ArgumentError )
960
+
961
+ ->{ f.typo }.should.raise( NoMethodError )
962
+ ->{ f.typo = 10 }.should.raise( NoMethodError )
963
+ ->{ f[ :typo ] }.should.raise( NoMethodError )
964
+ ->{ f[ :typo ] = 10 }.should.raise( NoMethodError )
965
+ ->{ f[ :query, :typo ] }.should.raise( NoMethodError )
966
+
967
+ ->{ f.valid?( :typo ) }.should.raise( ArgumentError )
968
+ ->{ f.valid?( :query, :typo ) }.should.raise( ArgumentError )
969
+ ->{ f.valid?( [ :typo ] ) }.should.raise( ArgumentError )
970
+ ->{ f.valid?( [ :query, :typo ] ) }.should.raise( ArgumentError )
971
+ ->{ f.invalid?( :typo ) }.should.raise( ArgumentError )
972
+ ->{ f.invalid?( :query, :typo ) }.should.raise( ArgumentError )
973
+ ->{ f.invalid?( [ :typo ] ) }.should.raise( ArgumentError )
974
+ ->{ f.invalid?( [ :query, :typo ] ) }.should.raise( ArgumentError )
975
+ ->{ f.valid }.should.raise( ArgumentError )
976
+ ->{ f.valid( :typo ) }.should.raise( ArgumentError )
977
+ ->{ f.valid( :query, :typo ) }.should.raise( ArgumentError )
978
+ ->{ f.valid( [ :typo ] ) }.should.raise( ArgumentError )
979
+ ->{ f.valid( [ :query, :typo ] ) }.should.raise( ArgumentError )
980
+ end
981
+
982
+ should 'not overwrite original values via derived forms' do
983
+ f = TestForm.new( query: "x" )
984
+ f.query.should == "x"
985
+ f[ :query ].should == "x"
986
+
987
+ f.dup.query = "y"
988
+ f.query.should == "x"
989
+ f[ :query ].should == "x"
990
+
991
+ f.only( :query ).query = "y"
992
+ f.query.should == "x"
993
+ f[ :query ].should == "x"
994
+
995
+ f.only( :query )[ :query ] = "y"
996
+ f.query.should == "x"
997
+ f[ :query ].should == "x"
998
+
999
+ f.except( :query ).query = "y"
1000
+ f.query.should == "x"
1001
+ f[ :query ].should == "x"
1002
+
1003
+ f.except( :query )[ :query ] = "y"
1004
+ f.query.should == "x"
1005
+ f[ :query ].should == "x"
1006
+ end
1007
+
1008
+ should 'handle non string values gracefully' do
1009
+ f = TestForm.new( query: true, age: 3, rate: 0.35, text: false, opts: [], on: {} )
1010
+ ->{ f.validate }.should.not.raise
1011
+ f.to_hash.should == { query: true, age: 3, rate: 0.35, text: false }
1012
+ f.url_params.should == { q: "true", age: "3", rate: "0.35", text: "false" }
1013
+ f.url_query.should == "q=true&age=3&rate=0.35&text=false"
1014
+ names( f.incorrect_params ).should == [ :query, :rate, :text ]
1015
+
1016
+ f = TestForm.new( opts: 1 )
1017
+ ->{ f.validate }.should.not.raise
1018
+ f.to_hash.should == { opts: 1 }
1019
+ f.url_params.should == { opts: [ "1" ] }
1020
+ f.url_query.should == "opts[]=1"
1021
+ names( f.incorrect_params ).should == [ :opts ]
1022
+
1023
+ f = TestForm.new( opts: [ 2.5, true ] )
1024
+ ->{ f.validate }.should.not.raise
1025
+ f.to_hash.should == { opts: [ 2.5, true ] }
1026
+ f.url_params.should == { opts: [ "2.5", "true" ] }
1027
+ f.url_query.should == "opts[]=2.5&opts[]=true"
1028
+ names( f.incorrect_params ).should == []
1029
+
1030
+ f = TestForm.new( opts: { "foo" => 10, true => false } )
1031
+ ->{ f.validate }.should.not.raise
1032
+ f.to_hash.should == { opts: { "foo" => 10, true => false } }
1033
+ f.url_params.should == { opts: [ '["foo", 10]', '[true, false]' ] }
1034
+ f.url_query.should == "opts[]=%5B%22foo%22%2C+10%5D&opts[]=%5Btrue%2C+false%5D"
1035
+ names( f.incorrect_params ).should == [ :opts ]
1036
+
1037
+ f = TestForm.new( on: 1 )
1038
+ ->{ f.validate }.should.not.raise
1039
+ f.to_hash.should == { on: 1 }
1040
+ f.url_params.should == { on: { "1" => "" } }
1041
+ f.url_query.should == "on[1]="
1042
+ names( f.incorrect_params ).should == [ :on ]
1043
+
1044
+ f = TestForm.new( on: { 0 => 1, 2 => 3.4 } )
1045
+ ->{ f.validate }.should.not.raise
1046
+ f.to_hash.should == { on: { 0 => 1, 2 => 3.4 } }
1047
+ f.url_params.should == { on: { "0" => "1", "2" => "3.4" } }
1048
+ f.url_query.should == "on[0]=1&on[2]=3.4"
1049
+ names( f.incorrect_params ).should == []
1050
+
1051
+ f = TestForm.new( on: [ [ 10, 20 ], [ true, false ] ] )
1052
+ ->{ f.validate }.should.not.raise
1053
+ f.to_hash.should == { on: [ [ 10, 20 ], [ true, false ] ] }
1054
+ f.url_params.should == { on: { "10" => "20", "true" => "false" } }
1055
+ f.url_query.should == "on[10]=20&on[true]=false"
1056
+ names( f.incorrect_params ).should == [ :on ]
1057
+
1058
+ f = TestForm.new( on: [ 1, true, false ] )
1059
+ ->{ f.validate }.should.not.raise
1060
+ f.to_hash.should == { on: [ 1, true, false ] }
1061
+ f.url_params.should == { on: { "1" => "", "true" => "", "false" => "" } }
1062
+ f.url_query.should == "on[1]=&on[true]=&on[false]="
1063
+ names( f.incorrect_params ).should == [ :on ]
1064
+ end
1065
+
1066
+ should 'handle invalid encoding gracefully' do
1067
+ s = 255.chr.force_encoding( 'UTF-8' )
1068
+
1069
+ f = TestForm.new( query: s )
1070
+ ->{ f.validate }.should.not.raise
1071
+ f.should.not.be.valid
1072
+ f.error_messages.should == [ "q must use valid encoding" ]
1073
+ f.param( :query ).should.not.be.blank
1074
+ f.to_hash.should == { query: s }
1075
+ f.url_params.should == { q: s }
1076
+ f.url_query.should == "q=%FF"
1077
+
1078
+ f = TestForm.new( Rack::Request.new( Rack::MockRequest.env_for( "?q=%ff" ) ) )
1079
+ ->{ f.validate }.should.not.raise
1080
+ f.should.not.be.valid
1081
+ f.error_messages.should == [ "q must use valid encoding" ]
1082
+ f.param( :query ).should.not.be.blank
1083
+ f.to_hash.should == { query: s.dup.force_encoding( 'BINARY' ) }
1084
+ f.url_params.should == { q: s.dup.force_encoding( 'BINARY' ) }
1085
+ f.url_query.should == "q=%FF"
1086
+ end
1087
+
1088
+ should 'make it easy to create URLs' do
1089
+ f = TestForm.new( query: "x", opts: [ 0, 0, 1 ], on: { 0 => 1 }, age: 10, email: nil, password: "", text: " " )
1090
+ f.url_params.should == { q: "x", age: "10", text: " ", opts: [ "0", "0", "1" ], on: { "0" => "1" } }
1091
+ f.url_query.should == "q=x&age=10&text=+&opts[]=0&opts[]=0&opts[]=1&on[0]=1"
1092
+ f.extend_url( "" ).should == "?q=x&age=10&text=+&opts[]=0&opts[]=0&opts[]=1&on[0]=1"
1093
+ f.extend_url( "/" ).should == "/?q=x&age=10&text=+&opts[]=0&opts[]=0&opts[]=1&on[0]=1"
1094
+ f.extend_url( "/foo" ).should == "/foo?q=x&age=10&text=+&opts[]=0&opts[]=0&opts[]=1&on[0]=1"
1095
+ f.extend_url( "/foo?x" ).should == "/foo?x&q=x&age=10&text=+&opts[]=0&opts[]=0&opts[]=1&on[0]=1"
1096
+ f.extend_url( URI.parse( "/foo" ) ).should == "/foo?q=x&age=10&text=+&opts[]=0&opts[]=0&opts[]=1&on[0]=1"
1097
+ f.extend_url( URI.parse( "/foo?x" ) ).should == "/foo?x&q=x&age=10&text=+&opts[]=0&opts[]=0&opts[]=1&on[0]=1"
1098
+
1099
+ f = TestForm.new
1100
+ f.url_params.should == {}
1101
+ f.url_query.should == ""
1102
+ f.extend_url( "" ).should == ""
1103
+ f.extend_url( "/" ).should == "/"
1104
+ f.extend_url( "/foo" ).should == "/foo"
1105
+ f.extend_url( "/foo?x" ).should == "/foo?x"
1106
+ f.extend_url( URI.parse( "/foo" ) ).should == "/foo"
1107
+ f.extend_url( URI.parse( "/foo?x" ) ).should == "/foo?x"
1108
+
1109
+ f.build_url( "/" ).should == "/"
1110
+ f.build_url( "/foo", query: "x" ).should == "/foo?q=x"
1111
+ f.build_url( "/foo?x", query: "x", age: 42 ).should == "/foo?x&q=x&age=42"
1112
+ end
1113
+
1114
+ should 'provide useful error detecting and reporting methods' do
1115
+ # Create new form every time to test that the validation triggers automatically.
1116
+ f = ->{ TestForm.new( email: "x", text: "yy" ) }
1117
+ f[].should.not.be.valid
1118
+ f[].should.be.invalid
1119
+ f[].should.be.valid?( :text )
1120
+ f[].should.not.be.valid?( :email )
1121
+ f[].should.not.be.valid?( :text, :email )
1122
+ f[].should.be.valid?( [] )
1123
+ f[].should.be.valid?( [ :text ] )
1124
+ f[].should.not.be.valid?( [ :email ] )
1125
+ f[].should.not.be.valid?( [ :text, :email ] )
1126
+ f[].should.not.be.invalid?( :text )
1127
+ f[].should.be.invalid?( :email )
1128
+ f[].should.be.invalid?( :text, :email )
1129
+ f[].should.not.be.invalid?( [] )
1130
+ f[].should.not.be.invalid?( [ :text ] )
1131
+ f[].should.be.invalid?( [ :email ] )
1132
+ f[].should.be.invalid?( [ :text, :email ] )
1133
+ f[].valid( :text ).should == "yy"
1134
+ f[].valid( :email ).should == nil
1135
+ f[].valid( :text, :email ).should == nil
1136
+ f[].valid( :text, :password ).should == [ "yy", nil ]
1137
+ f[].valid( :text, :text ).should == [ "yy", "yy" ]
1138
+ f[].errors.should == { query: [ "q is required" ], email: [ "email address like this is not valid" ] }
1139
+ f[].error_messages.should == [ "q is required", "email address like this is not valid" ]
1140
+ f[].errors_for( :query ).should == [ "q is required" ]
1141
+ f[].error_for( :query ).should == "q is required"
1142
+ f[].errors_for( :password ).should == []
1143
+ f[].error_for( :password ).should == nil
1144
+
1145
+ f = ->{ TestForm.new.report( :query, "msg" ).report( :password, "bad" ) }
1146
+ f[].should.not.be.valid
1147
+ f[].should.be.invalid
1148
+ f[].should.be.valid?( :email )
1149
+ f[].should.not.be.valid?( :query )
1150
+ f[].should.not.be.valid?( :email, :query )
1151
+ f[].should.be.valid?( [] )
1152
+ f[].should.be.valid?( [ :email ] )
1153
+ f[].should.not.be.valid?( [ :query ] )
1154
+ f[].should.not.be.valid?( [ :email, :query ] )
1155
+ f[].should.not.be.invalid?( :email )
1156
+ f[].should.be.invalid?( :query )
1157
+ f[].should.be.invalid?( :email, :query )
1158
+ f[].should.not.be.invalid?( [] )
1159
+ f[].should.not.be.invalid?( [ :email ] )
1160
+ f[].should.be.invalid?( [ :query ] )
1161
+ f[].should.be.invalid?( [ :email, :query ] )
1162
+ f[].errors.should == { query: [ "q is required", "msg" ], password: [ "bad" ] }
1163
+ f[].error_messages.should == [ "q is required", "bad" ]
1164
+ f[].errors_for( :query ).should == [ "q is required", "msg" ]
1165
+ f[].error_for( :query ).should == "q is required"
1166
+ f[].errors_for( :password ).should == [ "bad" ]
1167
+ f[].error_for( :password ).should == "bad"
1168
+
1169
+ f = TestForm.new
1170
+ f.should.be.invalid
1171
+ f.set( query: "x" ).should.be.valid
1172
+ f.should.be.valid
1173
+ f.except( :query ).should.be.invalid
1174
+ f.should.be.valid
1175
+ f.only( :password ).should.be.invalid
1176
+ f.should.be.valid
1177
+ f.dup.set( query: "" ).should.be.invalid
1178
+ f.should.be.valid
1179
+
1180
+ f.query = nil
1181
+ f.should.be.valid
1182
+ f.validate?.should.be.valid
1183
+ f.dup.validate?.should.be.invalid
1184
+ f.validate.should.be.invalid
1185
+ f.validate!.should.be.invalid
1186
+
1187
+ f.query = "x"
1188
+ f.should.be.invalid
1189
+ f.validate?.should.be.invalid
1190
+ f.dup.validate?.should.be.valid
1191
+ f.validate.should.be.invalid
1192
+ f.validate!.should.be.valid
1193
+
1194
+ f[ :query ] = nil
1195
+ f.should.be.invalid
1196
+ f.validate?.should.be.invalid
1197
+ f.dup.validate?.should.be.invalid
1198
+ f.validate.should.be.invalid
1199
+ f.validate!.should.be.invalid
1200
+
1201
+ f[ :query ] = "x"
1202
+ f.should.be.valid
1203
+ f.validate?.should.be.valid
1204
+ f.dup.validate?.should.be.valid
1205
+ f.validate.should.be.valid
1206
+ f.validate!.should.be.valid
1207
+
1208
+ f.clear.should.equal f
1209
+ f.should.be.invalid
1210
+ f.validate?.should.be.invalid
1211
+ f.dup.validate?.should.be.invalid
1212
+ f.validate.should.be.invalid
1213
+ f.validate!.should.be.invalid
1214
+
1215
+ f.set( query: "x" ).should.equal f
1216
+ f.should.be.valid
1217
+ f.validate?.should.be.valid
1218
+ f.dup.validate?.should.be.valid
1219
+ f.validate.should.be.valid
1220
+ f.validate!.should.be.valid
1221
+ end
1222
+
1223
+ should 'support some custom error messages' do
1224
+ c = Class.new( FormInput )
1225
+ c.param! :q, match: /A/, reject: /B/
1226
+ c.copy c[ :q ], name: :c,
1227
+ required_msg: '%p must be filled in',
1228
+ match_msg: '%p must contain A',
1229
+ reject_msg: '%p may not contain B'
1230
+ f = c.new
1231
+ f.error_messages.should == [ "q is required", "c must be filled in" ]
1232
+ f = c.new( q: 'X', c: 'X' )
1233
+ f.error_messages.should == [ "q like this is not valid", "c must contain A" ]
1234
+ f = c.new( q: 'BA', c: 'BA' )
1235
+ f.error_messages.should == [ "q like this is not allowed", "c may not contain B" ]
1236
+ f = c.new( q: 'A', c: 'A' )
1237
+ f.error_messages.should.be.empty
1238
+ end
1239
+
1240
+ should 'split parameters into rows as desired' do
1241
+ c = Class.new( FormInput )
1242
+ c.param :a
1243
+ c.param :b, row: 1
1244
+ c.param :c, row: 1
1245
+ c.param :d
1246
+ c.param :e, row: 1
1247
+ c.param :f, row: 2
1248
+ c.param :g, row: 2
1249
+ c.param :h, row: 3
1250
+ c.param :i, row: 3
1251
+ c.param :j, row: 3
1252
+ c.param :k
1253
+
1254
+ f = c.new
1255
+ f.chunked_params.map{ |x| x.is_a?( Array ) ? x.map{ |y| y.name } : x.name }.should == [
1256
+ :a,
1257
+ [ :b, :c ],
1258
+ :d,
1259
+ :e,
1260
+ [ :f, :g ],
1261
+ [ :h, :i, :j ],
1262
+ :k
1263
+ ]
1264
+
1265
+ f = TestForm.new
1266
+ f.chunked_params.should == f.params
1267
+ f.chunked_params( f.scalar_params ).should == f.scalar_params
1268
+ end
1269
+
1270
+ end
1271
+
1272
+ # EOF #