form_input 0.9.0.pre1

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