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
@@ -0,0 +1,27 @@
1
+ # encoding: UTF-8
2
+
3
+ require_relative 'helper'
4
+
5
+ require 'form_input/localize'
6
+
7
+ class TestLocalizeForm < FormInput
8
+ param :simple, "String"
9
+ param :utf, "ěščřžýáíéúůďťň"
10
+ param :yaml, '%', msg: '{}'
11
+ end
12
+
13
+ describe FormInput do
14
+
15
+ should 'provide helper to create default translation file' do
16
+ text = FormInput.default_translation
17
+ name = File.expand_path( "#{__FILE__}/../localize/en.yml" )
18
+ if File.exists?( name )
19
+ text.should == File.read( name )
20
+ else
21
+ File.write( name, text )
22
+ end
23
+ end
24
+
25
+ end
26
+
27
+ # EOF #
data/test/test_r18n.rb ADDED
@@ -0,0 +1,373 @@
1
+ # encoding: UTF-8
2
+
3
+ require_relative 'helper'
4
+
5
+ require 'form_input/r18n'
6
+
7
+ class TestR18nForm < FormInput
8
+ param! :q, min_size: 2, max_size: 8,
9
+ min_bytesize: 3, max_bytesize: 6,
10
+ match: /\A[A-Z]+\z/, reject: /[a-z]/
11
+ array! :a, INTEGER_ARGS, min_count: 2, max_count: 3
12
+ hash :h, min_key: 1, max_key: 7
13
+ hash :hh, match_key: /\A[a-z]+\z/
14
+ param :int, INTEGER_ARGS, min: 5, max: 10
15
+ param :float, FLOAT_ARGS, inf: 0, sup: 1
16
+ param :msg, "Message"
17
+ param :msg2, "Second Message", form_title: ->{ ft.msg.title | 'Another Message' }, error_title: ->{ pt.error_title }
18
+ param :msg3, plural: true
19
+ param :bool, BOOL_ARGS
20
+ end
21
+
22
+ class TestInflectionForm < FormInput
23
+ param! :name, "Name"
24
+ param! :address, "Address"
25
+ param! :state, "State"
26
+ param! :author, "Author"
27
+ param! :friends, "Friends", plural: true
28
+ param! :chars, "Characters", plural: true
29
+ param! :keywords, "Keywords", plural: true
30
+ param! :notes, "Notes", plural: true
31
+ param :test
32
+ end
33
+
34
+ class TestLocalizedStepsForm < FormInput
35
+ define_steps(
36
+ one: "First",
37
+ two: "Second",
38
+ three: "Third",
39
+ four: nil,
40
+ )
41
+ end
42
+
43
+ describe FormInput do
44
+
45
+ TESTS = [
46
+ [ '', '', q: 'ABC', a: [ 1, 2 ] ],
47
+ [ 'q is required', 'q je povinný', q: nil ],
48
+ [ 'a are required', 'a jsou povinné', a: nil ],
49
+ [ 'a are not an array', 'a nejsou pole', a: 2 ],
50
+ [ 'h are not a hash', 'h nejsou hash', h: 3 ],
51
+ [ 'hh contain invalid key', 'hh obsahují neplatný klíč', hh: { 1 => 2 } ],
52
+ [ 'h contain invalid key', 'h obsahují neplatný klíč', h: { 'foo' => 'bar' } ],
53
+ [ 'h contain too small key', 'h obsahují příliš malý klíč', h: { 0 => 0 } ],
54
+ [ 'h contain too large key', 'h obsahují příliš velký klíč', h: { 10 => 0 } ],
55
+ [ 'a must have at least 2 elements', 'a musí mít nejméně 2 prvky', a: [ 1 ] ],
56
+ [ 'a may have at most 3 elements', 'a smí mít nejvíce 3 prvky', a: [ 1, 2, 3, 4 ] ],
57
+ [ 'int like this is not valid', 'int musí mít správný formát', int: 'foo' ],
58
+ [ 'a contain invalid value', 'a obsahují neplatnou hodnotu', a: [ 10, 'bar' ] ],
59
+ [ 'int must be at least 5', 'int musí být nejméně 5', int: 0 ],
60
+ [ 'int may be at most 10', 'int smí být nejvíce 10', int: 20 ],
61
+ [ 'float must be greater than 0', 'float musí být větší než 0', float: 0.0 ],
62
+ [ 'float must be less than 1', 'float musí být menší než 1', float: 1.0 ],
63
+ [ 'q is not a string', 'q není řetězec', q: [ 1 ] ],
64
+ [ 'h contain invalid value', 'h obsahují neplatnou hodnotu', h: { 5 => 5 } ],
65
+ [ 'h must use valid encoding', 'h musí mít platný encoding', h: { 5 => 255.chr.force_encoding( Encoding::UTF_8 ) } ],
66
+ [ 'h may not contain invalid characters', 'h nesmí obsahovat zakázané znaky', h: { 5 => "\f" } ],
67
+ [ 'q must have at least 2 characters', 'q musí mít nejméně 2 znaky', q: '0' ],
68
+ [ 'q may have at most 8 characters', 'q smí mít nejvíce 8 znaků', q: '123456789' ],
69
+ [ 'q must have at least 3 bytes', 'q musí mít nejméně 3 byty', q: '01' ],
70
+ [ 'q may have at most 6 bytes', 'q smí mít nejvíce 6 bytů', q: '1234567' ],
71
+ [ 'q like this is not allowed', 'q v tomto tvaru není povolen', q: 'abcd' ],
72
+ [ 'q like this is not valid', 'q není ve správném tvaru', q: '12345' ],
73
+ ]
74
+
75
+ after do
76
+ R18n.reset!
77
+ end
78
+
79
+ def set_locale( code, found = code )
80
+ result = R18n.set( code, [ "#{__FILE__}/../r18n", FormInput.translations_path ] )
81
+ result.locale.code.should == found
82
+ result
83
+ end
84
+
85
+ def test_translations
86
+ for reference, translation, hash in TESTS
87
+ defaults ||= hash
88
+ hash = defaults.merge( hash )
89
+ f = TestR18nForm.new( hash )
90
+ yield( f.error_messages.join( ':' ), reference, translation )
91
+ end
92
+ end
93
+
94
+ should 'use builtin error messages by default' do
95
+ R18n.get.should.be.nil
96
+ test_translations do |msg, reference|
97
+ msg.should == reference
98
+ end
99
+ end
100
+
101
+ should 'localize builtin error messages for supported locales' do
102
+ set_locale( 'en' )
103
+ test_translations do |msg, reference|
104
+ msg.should == reference
105
+ end
106
+
107
+ set_locale( 'cs' )
108
+ test_translations do |msg, reference, translation|
109
+ msg.should == translation
110
+ end
111
+
112
+ set_locale( 'xx' )
113
+ test_translations do |msg, reference|
114
+ msg.should =~ /^{{.*}}$/ unless msg.empty?
115
+ msg.should =~ /^{{.*{{.*}}}}$/ if msg =~ /\d \w/
116
+ msg.gsub( /{{|}}/, '' ).should == reference
117
+ end
118
+ end
119
+
120
+ should 'fallback to English error messages for unsupported locales' do
121
+ set_locale( 'zz', 'en' )
122
+ test_translations do |msg, reference|
123
+ msg.should == reference
124
+ end
125
+ end
126
+
127
+ should 'automatically support inflection of localized strings' do
128
+ set_locale( 'en' )
129
+ f = TestInflectionForm.new
130
+ f.error_messages.should == [
131
+ "Name is required",
132
+ "Address is required",
133
+ "State is required",
134
+ "Author is required",
135
+ "Friends are required",
136
+ "Characters are required",
137
+ "Keywords are required",
138
+ "Notes are required",
139
+ ]
140
+ set_locale( 'cs' )
141
+ f.validate!
142
+ f.error_messages.should == [
143
+ "Jméno je povinné",
144
+ "Adresa je povinná",
145
+ "Stát je povinný",
146
+ "Autor je povinný",
147
+ "Přátelé jsou povinní",
148
+ "Znaky jsou povinné",
149
+ "Klíčová slova jsou povinná",
150
+ "Poznámky jsou povinné",
151
+ ]
152
+ end
153
+
154
+ should 'report invalid inflection strings' do
155
+ set_locale( 'cs' )
156
+ f = TestInflectionForm.new
157
+ p = f.param( :test )
158
+ p.report( :required_scalar ).error.should == 'form_input.errors.required_scalar.[invalid]'
159
+ end
160
+
161
+ def each_error_message( default_limits )
162
+ for code, text in FormInput::DEFAULT_ERROR_MESSAGES
163
+ limits = case code.to_s
164
+ when /(min|inf)_limit/
165
+ [ 0, 10 ]
166
+ when /(max|sup)_limit/
167
+ [ 1, 100 ]
168
+ when /bytesize|size|count/
169
+ unit = { 'bytesize' => 'byte', 'size' => 'character', 'count' => 'element' }[ $& ]
170
+ values = default_limits.dup
171
+ values.shift if code.to_s =~ /max/ and values.first == 0
172
+ values.map{ |x| [ x, unit ] }
173
+ end
174
+ yield( code, limits )
175
+ end
176
+ end
177
+
178
+ should 'properly inflect all builtin messages in all locales' do
179
+ # Setup parameter which will fake the inflection and title we need.
180
+ inflection = name = nil
181
+ p = TestInflectionForm.new.param( :name )
182
+ p.define_singleton_method( :inflection ) do
183
+ inflection
184
+ end
185
+ p.define_singleton_method( :error_title ) do
186
+ name
187
+ end
188
+
189
+ dir = File.expand_path( "#{__FILE__}/../reference" )
190
+ R18n.available_locales( FormInput.translations_path ).each do |locale|
191
+ R18n.set( locale.code, FormInput.translations_path )
192
+
193
+ base = "#{dir}/#{locale.code}"
194
+ inflections = YAML.load_file( "#{base}.yml" )
195
+ limits = inflections.delete( 'limits' )
196
+
197
+ file = "#{base}.txt"
198
+ read = File.exists?( file )
199
+ File.open( file, read ? "r" : "w" ) do |file|
200
+ each_error_message( limits ) do |code, limits|
201
+ for inflection, name in inflections
202
+ for limit in limits || [ nil ]
203
+ text = p.format_error_message( code, *limit )
204
+ if read
205
+ text.should == file.gets.chomp
206
+ else
207
+ file.puts text
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end
213
+ end.map{ |x| x.code }.sort.should == %w[ cs en pl sk ]
214
+ end
215
+
216
+ should 'provide scope name for automatic translations' do
217
+ TestR18nForm.translation_name.should == 'test_r18n_form'
218
+ TestInflectionForm.translation_name.should == 'test_inflection_form'
219
+ end
220
+
221
+ should 'automatically translate string options when possible' do
222
+ set_locale( 'cs' )
223
+ f = TestR18nForm.new
224
+ p = f.param( :msg )
225
+ p.title.should == 'Zpráva'
226
+ p.form_title.should == 'Vaše zpráva'
227
+ p.error_title.should == 'Parametr Zpráva'
228
+ p[ :msg ].should == '%p není správně vyplněn'
229
+ p[ :match_msg ].should == '%p není ve správném tvaru'
230
+ p[ :reject_msg ].should == '%p obsahuje nepovolené znaky'
231
+ p[ :required_msg ].should == '%p musí být vyplněn'
232
+ end
233
+
234
+ should 'use default string options for missing translations' do
235
+ set_locale( 'en' )
236
+ f = TestR18nForm.new
237
+ p = f.param( :msg )
238
+ p.title.should == 'Message'
239
+ p.form_title.should == 'Message'
240
+ p.error_title.should == 'Message'
241
+ p[ :msg ].should.be.nil
242
+ p[ :match_msg ].should.be.nil
243
+ p[ :reject_msg ].should.be.nil
244
+ p[ :required_msg ].should.be.nil
245
+ end
246
+
247
+ should 'provide R18n translation helpers' do
248
+ set_locale( 'en' )
249
+ f = TestR18nForm.new
250
+ p = f.param( :msg3 )
251
+ p.title.should.be.nil
252
+ p.form_title.should == 'msg3'
253
+ p.error_title.should == 'msg3'
254
+
255
+ p = f.param( :msg2 )
256
+ p.title.should == 'Second Message'
257
+ p.form_title.should == 'Another Message'
258
+ p.error_title.to_s.should == '[forms.test_r18n_form.msg2.error_title]'
259
+
260
+ set_locale( 'cs' )
261
+ p.title.should == 'Druhá zpráva'
262
+ p.form_title.should == 'Zpráva'
263
+ p.error_title.should == 'Parametr druhá zpráva'
264
+
265
+ f.t.foo.to_s.should == '[foo]'
266
+ p.t.foo.to_s.should == '[foo]'
267
+
268
+ f.ft.foo.to_s.should == 'forms.test_r18n_form.[foo]'
269
+ p.ft.foo.to_s.should == 'forms.test_r18n_form.[foo]'
270
+ p.pt.foo.to_s.should == 'forms.test_r18n_form.msg2.[foo]'
271
+
272
+ f.ft( :foo ).to_s.should == 'forms.test_r18n_form.[foo]'
273
+ p.ft( :foo ).to_s.should == 'forms.test_r18n_form.[foo]'
274
+ p.pt( :foo ).to_s.should == 'forms.test_r18n_form.msg2.[foo]'
275
+
276
+ p = f.param( :msg3 )
277
+ p.title.should.be == 'Třetí zpráva'
278
+ p.form_title.should.be == 'Třetí zpráva'
279
+ p.error_title.should.be == 'Třetí zpráva'
280
+
281
+ f.ft.texts.test.should == 'Test'
282
+ f.ft[ :texts ].test.should == 'Test'
283
+ f.ft( :texts ).test.should == 'Test'
284
+ p.ft.texts.test.should == 'Test'
285
+ p.ft[ :texts ].test.should == 'Test'
286
+ p.ft( :texts ).test.should == 'Test'
287
+ p.pt.test_msg( 10 ).should == 'Argument 10'
288
+ p.pt[ :test_msg, 10 ].should == 'Argument 10'
289
+ p.pt( :test_msg, 10 ).should == 'Argument 10'
290
+ # Indeed, because without parameter context, the default is singular neuter.
291
+ p.pt.inflected_msg.should == 'Singular'
292
+ p.pt[ :inflected_msg ].should == 'Singular'
293
+ p.pt.inflected_msg( p ).should == 'Plural'
294
+ p.pt[ :inflected_msg, p ].should == 'Plural'
295
+ p.pt[ :inflected_msg, p.inflection ].should == 'Plural'
296
+ # This form adds the parameter context automatically.
297
+ p.pt( :inflected_msg ).should == 'Plural'
298
+ end
299
+
300
+ should 'fail properly when the locale is not set when needed' do
301
+ R18n.get.should.be.nil
302
+ f = TestR18nForm.new
303
+ p = f.param( :msg3 )
304
+ p.title.should.be.nil
305
+ p.form_title.should == 'msg3'
306
+ p.error_title.should == 'msg3'
307
+
308
+ p = f.param( :msg2 )
309
+ p.title.should == 'Second Message'
310
+ ->{ p.form_title }.should.raise( RuntimeError )
311
+ ->{ p.error_title }.should.raise( RuntimeError )
312
+
313
+ ->{ f.t.foo }.should.raise( NoMethodError )
314
+ ->{ p.t.foo }.should.raise( NoMethodError )
315
+
316
+ ->{ f.ft.foo }.should.raise( RuntimeError )
317
+ ->{ p.ft.foo }.should.raise( RuntimeError )
318
+ ->{ p.pt.foo }.should.raise( RuntimeError )
319
+
320
+ ->{ f.ft( :foo ) }.should.raise( RuntimeError )
321
+ ->{ p.ft( :foo ) }.should.raise( RuntimeError )
322
+ ->{ p.pt( :foo ) }.should.raise( RuntimeError )
323
+ end
324
+
325
+ should 'automatically localize the boolean helper' do
326
+ R18n.get.should.be.nil
327
+ f = TestR18nForm.new
328
+ p = f.param( :bool )
329
+ p.data.should == [ [ true, 'Yes' ], [ false, 'No' ] ]
330
+ set_locale( 'en' )
331
+ p.data.should == [ [ true, 'Yes' ], [ false, 'No' ] ]
332
+ set_locale( 'cs' )
333
+ p.data.should == [ [ true, 'Ano' ], [ false, 'Ne' ] ]
334
+ p.data.first.last.class.should.equal String
335
+ p.data.last.last.class.should.equal String
336
+ end
337
+
338
+ should 'localize the step names' do
339
+ R18n.get.should.be.nil
340
+ f = TestLocalizedStepsForm.new
341
+ f.form_steps.should == { one: "First", two: "Second", three: "Third", four: nil }
342
+ f.step_names.should == { one: "First", two: "Second", three: "Third" }
343
+ f.step_name.should == "First"
344
+ f.step_name( :two ).should == "Second"
345
+ f.step_name( :four ).should.be.nil
346
+ f.step_name( :none ).should.be.nil
347
+ f.step_name.class.should.equal String
348
+ f.step_names[ :one ].class.should.equal String
349
+
350
+ set_locale( 'en' )
351
+ f.form_steps.should == { one: "First", two: "Second", three: "Third", four: nil }
352
+ f.step_names.should == { one: "First", two: "Second", three: "Third" }
353
+ f.step_name.should == "First"
354
+ f.step_name( :two ).should == "Second"
355
+ f.step_name( :four ).should.be.nil
356
+ f.step_name( :none ).should.be.nil
357
+ f.step_name.class.should.equal String
358
+ f.step_names[ :one ].class.should.equal String
359
+
360
+ set_locale( 'cs' )
361
+ f.form_steps.should == { one: "First", two: "Second", three: "Third", four: nil }
362
+ f.step_names.should == { one: "První", two: "Druhý", three: "Třetí" }
363
+ f.step_name.should == "První"
364
+ f.step_name( :two ).should == "Druhý"
365
+ f.step_name( :four ).should.be.nil
366
+ f.step_name( :none ).should.be.nil
367
+ f.step_name.class.should.equal String
368
+ f.step_names[ :one ].class.should.equal String
369
+ end
370
+
371
+ end
372
+
373
+ # EOF #