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