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