form_input 0.9.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/LICENSE +19 -0
- data/README.md +3160 -0
- data/Rakefile +19 -0
- data/example/controllers/ramaze/press_release.rb +104 -0
- data/example/controllers/ramaze/profile.rb +38 -0
- data/example/controllers/sinatra/press_release.rb +114 -0
- data/example/controllers/sinatra/profile.rb +39 -0
- data/example/forms/change_password_form.rb +17 -0
- data/example/forms/login_form.rb +14 -0
- data/example/forms/lost_password_form.rb +14 -0
- data/example/forms/new_password_form.rb +15 -0
- data/example/forms/password_form.rb +18 -0
- data/example/forms/press_release_form.rb +153 -0
- data/example/forms/profile_form.rb +21 -0
- data/example/forms/signup_form.rb +25 -0
- data/example/views/press_release.slim +65 -0
- data/example/views/profile.slim +28 -0
- data/example/views/snippets/form_block.slim +27 -0
- data/example/views/snippets/form_chunked.slim +25 -0
- data/example/views/snippets/form_hidden.slim +21 -0
- data/example/views/snippets/form_panel.slim +89 -0
- data/form_input.gemspec +32 -0
- data/lib/form_input/core.rb +1165 -0
- data/lib/form_input/localize.rb +49 -0
- data/lib/form_input/r18n/cs.yml +97 -0
- data/lib/form_input/r18n/en.yml +70 -0
- data/lib/form_input/r18n/pl.yml +122 -0
- data/lib/form_input/r18n/sk.yml +120 -0
- data/lib/form_input/r18n.rb +163 -0
- data/lib/form_input/steps.rb +365 -0
- data/lib/form_input/types.rb +176 -0
- data/lib/form_input/version.rb +12 -0
- data/lib/form_input.rb +5 -0
- data/test/helper.rb +21 -0
- data/test/localize/en.yml +63 -0
- data/test/r18n/cs.yml +60 -0
- data/test/r18n/xx.yml +51 -0
- data/test/reference/cs.txt +352 -0
- data/test/reference/cs.yml +14 -0
- data/test/reference/en.txt +76 -0
- data/test/reference/en.yml +8 -0
- data/test/reference/pl.txt +440 -0
- data/test/reference/pl.yml +16 -0
- data/test/reference/sk.txt +352 -0
- data/test/reference/sk.yml +14 -0
- data/test/test_core.rb +1272 -0
- data/test/test_localize.rb +27 -0
- data/test/test_r18n.rb +373 -0
- data/test/test_steps.rb +482 -0
- data/test/test_types.rb +307 -0
- 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 #
|