uformats 1.2.1

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.
@@ -0,0 +1,23 @@
1
+ require 'uformats/basic'
2
+
3
+ module Microformats
4
+ module HCalendar
5
+ class Event < BasicNestedFormat
6
+
7
+ identifier :vevent
8
+
9
+ structure(
10
+ :version => :string,
11
+ :url => :url,
12
+ :summary => :string,
13
+ :dtstart => :datetime,
14
+ :dtend => :datetime,
15
+ :dtstamp => :datetime,
16
+ :location => :string,
17
+ :description => :string,
18
+ :uid => :string
19
+ )
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,100 @@
1
+ require 'uformats/basic'
2
+
3
+ module Microformats
4
+ class HCard < BasicNestedFormat
5
+
6
+ identifier :vcard
7
+
8
+ structure(
9
+ :fn => :string,
10
+ :n => {
11
+ :family_name => :string,
12
+ :given_name => :string,
13
+ :additional_names => :string,
14
+ :honorific_prefix => [:string],
15
+ :honorific_suffix => [:string],
16
+ },
17
+ :nickname => [:string],
18
+ :sort_string => :string,
19
+ :url => [:url],
20
+ :email => [{
21
+ :type => [:string],
22
+ :value => :email,
23
+ }],
24
+ :tel => [{
25
+ :type => [:string],
26
+ :value => :value,
27
+ }],
28
+ :adr => [{
29
+ :post_office_box => :string,
30
+ :extended_address => :string,
31
+ :street_address => :string,
32
+ :locality => :string,
33
+ :region => :string,
34
+ :postal_code => :string,
35
+ :country_name => :string,
36
+ :type => [:string],
37
+ :value => :string,
38
+ }],
39
+ :label => :string,
40
+ :geo => {
41
+ :latitude => :float,
42
+ :longitude => :float,
43
+ },
44
+ :tz => :string,
45
+ :photo => :url,
46
+ :logo => :url,
47
+ :sound => :string,
48
+ :bday => :datetime,
49
+ :title => :string,
50
+ :role => :string,
51
+ :org => {
52
+ :organization_name => :string,
53
+ :organization_unit => :string,
54
+ },
55
+ :category => [:string],
56
+ :note => [:string],
57
+ :class => :string,
58
+ :key => :string,
59
+ :mailer => :string,
60
+ :uid => :string,
61
+ :rev => :string
62
+ )
63
+
64
+ def post_process!
65
+ n_optimize!
66
+ org_optimize!
67
+ end
68
+
69
+ #
70
+ # Perform implied 'N' optimization
71
+ # http://microformats.org/wiki/hcard#Implied_.22N.22_Optimization
72
+ #
73
+ def n_optimize!
74
+ @tree[:n] ||= MethodHash.new
75
+ if (!@tree[:n][:family_name] && !@tree[:n][:given_name] && @tree[:fn])
76
+ @tree[:n][:family_name], @tree[:n][:given_name] = case @tree[:fn]
77
+ when /^(\w+), (\w+)$/u
78
+ [$1, $2]
79
+ when /^(\w+) (\w)\.?$/u
80
+ [$1, $2]
81
+ when /^(\w+) (\w+)$/u
82
+ [$2, $1]
83
+ end
84
+ end
85
+ end
86
+
87
+ #
88
+ # Perform org optimization (for when organization-name is implicit)
89
+ #
90
+ def org_optimize!
91
+ return if lookup(:org, :organization_name)
92
+ Microformats.each_element_by_class(source, :org) do |element|
93
+ @tree[:org] ||= MethodHash.new
94
+ @tree[:org][:organization_name] = process_as_string(element)
95
+ return
96
+ end
97
+ end
98
+
99
+ end
100
+ end
@@ -0,0 +1,182 @@
1
+ require 'uformats/basic'
2
+ require 'uformats/hcard'
3
+
4
+ module Microformats
5
+ class HReview < BasicNestedFormat
6
+
7
+ structure(
8
+ :version => :string,
9
+ :summary => :string,
10
+ :type => :string,
11
+ :item => {
12
+ :fn => :string,
13
+ :url => [:url],
14
+ :photo => [:url],
15
+ :vcard => :hcard,
16
+ :vevent => :event,
17
+ },
18
+ :reviewer => {
19
+ :fn => :string,
20
+ :url => :url,
21
+ :email => :email,
22
+ :vcard => :hcard,
23
+ },
24
+ :dtreviewed => :datetime,
25
+ :rating => [:rating],
26
+ :worst => [:worst],
27
+ :best => [:best],
28
+ :description => :xhtml,
29
+ :permalink => :url
30
+ )
31
+
32
+ VALID_TYPES = %w[product business event person place website url]
33
+
34
+ def post_process!
35
+ fix_anonymous_reviewer!
36
+ fix_ratings!
37
+ end
38
+
39
+ #
40
+ # Returns true if this is a valid hReview
41
+ #
42
+ def valid?
43
+ unless (
44
+ lookup(:item, :fn) || lookup(:item, :url) ||
45
+ lookup(:item, :photo) || lookup(:item, :vcard)
46
+ )
47
+ @validation_message = 'missing item details'
48
+ return false
49
+ end
50
+ if (
51
+ lookup(:type) &&
52
+ !VALID_TYPES.include?(lookup(:type))
53
+ )
54
+ @validation_message = 'invalid review type'
55
+ return false
56
+ end
57
+ if (
58
+ (lookup(:version) || 999).to_f > 0.2 &&
59
+ %w[person business].include?(lookup(:type)) &&
60
+ !lookup(:item, :vcard)
61
+ )
62
+ @validation_message = 'item must be vcard for person or business'
63
+ return false
64
+ end
65
+ return true
66
+ end
67
+ attr_accessor :validation_message
68
+
69
+ def license
70
+ return (licenses || []).first
71
+ end
72
+
73
+ def licenses
74
+ return @licenses ||= Microformats.rel_licenses(@source)
75
+ end
76
+
77
+ def process_as_hcard(element)
78
+ return HCard.new(element)
79
+ end
80
+
81
+ def process_as_event(element)
82
+ return HCalendar::Event.new(element)
83
+ end
84
+
85
+ def process_as_rating(element)
86
+ value = process_as_float(element)
87
+ tags = Microformats.rel_tags_above_element(element)
88
+ if !value && value_element = Microformats.first_element_by_class(element, :value)
89
+ value = process_as_float(value_element)
90
+ tags = Microformats.rel_tags_above_element(value_element)
91
+ end
92
+ return set_score(:score=, value, tags)
93
+ end
94
+
95
+ def process_as_best(element)
96
+ value = process_as_float(element)
97
+ tags = Microformats.rel_tags_above_element(element)
98
+ return set_score(:best=, value, tags)
99
+ end
100
+
101
+ def process_as_worst(element)
102
+ value = process_as_float(element)
103
+ tags = Microformats.rel_tags_above_element(element)
104
+ return set_score(:worst=, value, tags)
105
+ end
106
+
107
+ def set_score(sym, value, tags)
108
+ @ratings ||= {}
109
+ if tags
110
+ tags.each do |tag|
111
+ @ratings[tag] ||= Rating.new
112
+ @ratings[tag].__send__(sym, value)
113
+ end
114
+ else
115
+ @rating ||= Rating.new
116
+ @rating.__send__(sym, value)
117
+ end
118
+ return nil
119
+ end
120
+
121
+ def fix_anonymous_reviewer!
122
+ return if lookup(:reviewer, :fn)
123
+ Microformats.each_element_by_class(@source, :reviewer) do |e|
124
+ if 'anonymous' == process_as_string(e).downcase
125
+ @tree[:reviewer][:fn] = 'anonymous'
126
+ return
127
+ end
128
+ end
129
+ end
130
+
131
+ #
132
+ # Assign default values for ratings, and insert them into
133
+ # the tree
134
+ #
135
+ def fix_ratings!
136
+ if (@rating)
137
+ @rating.best ||= Rating::BEST
138
+ @rating.worst ||= Rating::WORST
139
+ default_best = @rating.best
140
+ default_worst = @rating.worst
141
+ end
142
+ default_best ||= Rating::BEST
143
+ default_worst ||= Rating::WORST
144
+ @ratings ||= {}
145
+ @ratings.each do |tag, rating|
146
+ rating.best ||= default_best
147
+ rating.worst ||= default_worst
148
+ end
149
+ @tree[:rating] = @rating
150
+ @tree[:ratings] = @ratings
151
+ end
152
+
153
+ class Rating
154
+ WORST = 1
155
+ BEST = 5
156
+
157
+ def initialize(score=nil)
158
+ @score = score
159
+ end
160
+
161
+ attr_reader :best, :worst, :score
162
+
163
+ def best=(b)
164
+ @best = b.to_i
165
+ end
166
+
167
+ def worst=(w)
168
+ @worst = w.to_i
169
+ end
170
+
171
+ def score=(s)
172
+ @score = s.to_f
173
+ end
174
+
175
+ def to_f
176
+ return (@score - (@worst || WORST)).to_f / ((@best || BEST) - (@worst || WORST))
177
+ end
178
+ end # Rating
179
+
180
+ end # HReview
181
+
182
+ end
@@ -0,0 +1,32 @@
1
+ module Microformats
2
+ class Pluralizer
3
+ @@plurals = {}
4
+
5
+ PLURALS = [
6
+ [/([^aeo])y$/, '\1ies'],
7
+ [/(s+|ch|x)$/, '\1es'],
8
+ ]
9
+
10
+ def self.pluralize(sym)
11
+ return @@plurals[sym] if @@plurals[sym]
12
+ string = sym.to_s
13
+ PLURALS.each do |match, replacement|
14
+ if string.match(match)
15
+ plural = string.sub(match, replacement).to_sym
16
+ @@plurals[sym] = plural
17
+ return plural
18
+ end
19
+ end
20
+ plural = (string + 's').to_sym
21
+ @@plurals[sym] = plural
22
+ return plural
23
+ end
24
+
25
+ def self.singularized_lookup(hash, sym)
26
+ return hash[sym] if hash[sym]
27
+ plural = hash[Pluralizer.pluralize(sym)]
28
+ return plural[0] if plural.respond_to?(:[])
29
+ return nil
30
+ end
31
+ end # Pluralizer
32
+ end
@@ -0,0 +1,4 @@
1
+ $: << File.dirname(__FILE__)
2
+ Dir[File.dirname(__FILE__)+'/*_test.rb'].each do |test|
3
+ require test
4
+ end
@@ -0,0 +1,441 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+ require 'uformats/hcard'
3
+
4
+ class TestFormat < Microformats::BasicNestedFormat
5
+ structure(
6
+ :tv => {
7
+ :type => [:string],
8
+ :value => :value,
9
+ },
10
+ :te => {
11
+ :type => [:string],
12
+ :value => :email,
13
+ },
14
+ :string => :string,
15
+ :nested => {
16
+ :inner => :string,
17
+ },
18
+ :integer => :integer,
19
+ :float => :float,
20
+ :datetime => :datetime,
21
+ :url => :url,
22
+ :email => :email,
23
+ :xhtml => :xhtml
24
+ )
25
+ end
26
+
27
+ class TestFormatWithPostProcess < Microformats::BasicNestedFormat
28
+
29
+ structure(:string => :string)
30
+
31
+ def post_process!
32
+ @post = true
33
+ end
34
+ attr_reader :post
35
+ end
36
+
37
+ class BasicNestedFormatTest < Test::Unit::TestCase
38
+
39
+ def test_should_accept_rexml_document_as_source
40
+ assert_nothing_raised{
41
+ t = TestFormat.new(REXML::Document.new(%{
42
+ <x class="testformat">
43
+ </x>
44
+ }))
45
+ }
46
+ end
47
+
48
+ def test_should_accept_text_as_source
49
+ assert_nothing_raised{
50
+ t = TestFormat.new(%{
51
+ <x class="testformat">
52
+ </x>
53
+ })
54
+ }
55
+ end
56
+
57
+ def test_should_call_post_process
58
+ t = TestFormatWithPostProcess.new('<x></x>')
59
+ assert(t.post)
60
+ end
61
+
62
+ def test_should_not_cause_error_if_post_process_not_defined
63
+ assert_nothing_raised{ TestFormat.new('<x></x>') }
64
+ end
65
+
66
+ def test_should_expand_include_patterns_before_processing
67
+ before = %{
68
+ <div>
69
+ <span class="testformat">
70
+ <span class="string" id="master">Foo</span>
71
+ </span>
72
+ <span class="testformat">
73
+ <object data="#master" class="include" type="text/html"></object>
74
+ </span>
75
+ </div>
76
+ }
77
+ tfs = []
78
+ TestFormat.each(before) do |tf|
79
+ tfs << tf
80
+ end
81
+ assert_equal('Foo', tfs[0].string)
82
+ assert_equal('Foo', tfs[1].string)
83
+ end
84
+
85
+ def test_should_expand_relative_urls
86
+ t = TestFormat.new(%{
87
+ <x class="testformat">
88
+ <a class="url" href="/link">link</a>
89
+ </x>
90
+ }, 'http://www.example.com/')
91
+ assert_equal('http://www.example.com/link', t.url)
92
+ end
93
+
94
+ def test_should_return_nested_item_or_nil_via_lookup_method
95
+ t = TestFormat.new(%{
96
+ <x class="testformat">
97
+ <x class="tv">
98
+ <x class="type">a</x>
99
+ <x class="type">b</x>
100
+ <x class="value">c</x>
101
+ </x>
102
+ </x>
103
+ }, 'http://www.example.com/')
104
+ assert_equal('a', t.lookup(:tv, :type))
105
+ assert_equal('a', t.lookup(:tv, :types, 0))
106
+ assert_equal('b', t.lookup(:tv, :types, 1))
107
+ assert_equal('c', t.lookup(:tv, :value))
108
+ assert_nil(t.lookup(:foo, :bar))
109
+ end
110
+
111
+ def test_should_return_email_when_href
112
+ t = TestFormat.new(%{
113
+ <x class="testformat">
114
+ <a class="email" href="mailto:bob@example.com">email</a>
115
+ </x>
116
+ })
117
+ assert_equal('bob@example.com', t.email)
118
+ end
119
+
120
+ def test_should_return_email_when_text
121
+ t = TestFormat.new(%{
122
+ <x class="testformat">
123
+ <a class="email">bob@example.com</a>
124
+ </x>
125
+ })
126
+ assert_equal('bob@example.com', t.email)
127
+ end
128
+
129
+
130
+ def test_should_interpret_nested_values_when_xml_is_nested
131
+ t = TestFormat.new(%{
132
+ <x class="testformat">
133
+ <a><b class="nested"><c class="inner">foo</c>bar</b></a>
134
+ </x>
135
+ })
136
+ assert_equal('foo', t.nested.inner)
137
+ end
138
+
139
+ def test_should_interpret_nested_values_when_xml_is_not_nested
140
+ t = TestFormat.new(%{
141
+ <x class="testformat">
142
+ <a><b class="nested inner">foo</b>bar</a>
143
+ </x>
144
+ })
145
+ assert_equal('foo', t.nested.inner)
146
+ end
147
+
148
+ def test_should_return_nil_for_missing_href_in_url
149
+ t = TestFormat.new(%{
150
+ <x class="testformat">
151
+ <a class="url">
152
+ quux
153
+ </a>
154
+ </x>
155
+ })
156
+ assert_nil(t.url)
157
+ end
158
+
159
+ def test_should_process_type_and_value_when_value_is_explicit
160
+ t = TestFormat.new(%{
161
+ <x class="testformat">
162
+ <a class="tv">
163
+ <b class="type">foo</b>
164
+ <b class="type">bar</b>
165
+ <b class="value">baz</b>
166
+ quux
167
+ </a>
168
+ </x>
169
+ })
170
+ assert_equal('foo', t.tv.type)
171
+ assert_equal(['foo', 'bar'], t.tv.types)
172
+ assert_equal('baz', t.tv.value)
173
+ end
174
+
175
+ def test_should_process_type_and_value_when_value_is_implicit
176
+ t = TestFormat.new(%{
177
+ <x class="testformat">
178
+ <a class="tv">
179
+ <b class="type">foo</b>
180
+ <b class="type">bar</b>
181
+ baz
182
+ </a>
183
+ </x>
184
+ })
185
+ assert_equal('foo', t.tv.type)
186
+ assert_equal(['foo', 'bar'], t.tv.types)
187
+ assert_equal('baz', t.tv.value)
188
+ end
189
+
190
+ def test_should_process_type_and_email_when_value_is_explicit
191
+ t = TestFormat.new(%{
192
+ <x class="testformat">
193
+ <a class="te">
194
+ <b class="type">foo</b>
195
+ <b class="type">bar</b>
196
+ <b class="value" href="mailto:user@example.com">user@example.com</b>
197
+ quux
198
+ </a>
199
+ </x>
200
+ })
201
+ assert_equal('foo', t.te.type)
202
+ assert_equal(['foo', 'bar'], t.te.types)
203
+ assert_equal('user@example.com', t.te.value)
204
+ end
205
+
206
+ def test_should_process_type_and_email_when_value_is_implicit_href
207
+ t = TestFormat.new(%{
208
+ <x class="testformat">
209
+ <a class="te" href="mailto:user@example.com">
210
+ user@example.com
211
+ <b class="type">foo</b>
212
+ <b class="type">bar</b>
213
+ </a>
214
+ </x>
215
+ })
216
+ assert_equal('foo', t.te.type)
217
+ assert_equal(['foo', 'bar'], t.te.types)
218
+ assert_equal('user@example.com', t.te.value)
219
+ end
220
+
221
+ def test_should_process_type_and_email_when_value_is_implicit_text
222
+ t = TestFormat.new(%{
223
+ <x class="testformat">
224
+ <a class="te">
225
+ user@example.com
226
+ </a>
227
+ </x>
228
+ })
229
+ assert_equal('user@example.com', t.te.value)
230
+ end
231
+
232
+ def test_should_reassemble_marked_up_strings
233
+ t = TestFormat.new(%{
234
+ <x class="testformat">
235
+ <a class="string">
236
+ <h1>About</h1>
237
+ <p>This is <a href="/">some text</a>.</p>
238
+ <h2>Final</h2>
239
+ <p>This is the last paragraph.</p>
240
+ </a>
241
+ </x>
242
+ })
243
+ assert_equal('About This is some text. Final This is the last paragraph.', t.string)
244
+ end
245
+
246
+ def test_should_regurgitate_xhtml_verbatim
247
+ xhtml = %{
248
+ <h1>About</h1>
249
+ <p>This is <a href='/'>some text</a>.</p>
250
+ <h2>Final</h2>
251
+ <p>This is the last paragraph.</p>
252
+ }
253
+ t = TestFormat.new(%{
254
+ <x class="testformat">
255
+ <a class="xhtml">
256
+ #{xhtml}
257
+ </a>
258
+ </x>
259
+ })
260
+ assert_equal(xhtml.strip.gsub(/\s+/, ' '), t.xhtml.strip.gsub(/\s+/, ' '))
261
+ end
262
+
263
+ def test_should_use_title_attribute_when_string_is_abbr
264
+ t = TestFormat.new(%{
265
+ <x class="testformat">
266
+ <abbr class="string" title="foo">bar</abbr>
267
+ </x>
268
+ })
269
+ assert_equal('foo', t.string)
270
+ end
271
+
272
+ def test_should_use_alt_attribute_when_string_is_img
273
+ t = TestFormat.new(%{
274
+ <x class="testformat">
275
+ <img class="string" alt="foo" />
276
+ </x>
277
+ })
278
+ assert_equal('foo', t.string)
279
+ end
280
+
281
+ def test_should_use_href_for_url_when_available
282
+ t = TestFormat.new(%{
283
+ <x class="testformat">
284
+ <a class="url" href="http://example.com/foo">bar</a>
285
+ </x>
286
+ })
287
+ assert_equal('http://example.com/foo', t.url)
288
+ end
289
+
290
+ def test_should_use_src_for_url_when_available
291
+ t = TestFormat.new(%{
292
+ <x class="testformat">
293
+ <img class="url" src="http://example.com/foo" />
294
+ </x>
295
+ })
296
+ assert_equal('http://example.com/foo', t.url)
297
+ end
298
+
299
+ def test_should_find_all_matching_microformats_via_each_with_text
300
+ src = %{
301
+ <z>
302
+ <x class="testformat">
303
+ <a class="integer">1</a>
304
+ </x>
305
+ <x class="testformat">
306
+ <a class="integer">2</a>
307
+ </x>
308
+ </z>
309
+ }
310
+ ary = []
311
+ TestFormat.each(src) do |tf|
312
+ ary << tf
313
+ end
314
+ assert_equal(1, ary[0].integer)
315
+ assert_equal(2, ary[1].integer)
316
+ end
317
+
318
+ def test_should_find_all_matching_microformats_via_each_with_rexml
319
+ src = REXML::Document.new(%{
320
+ <z>
321
+ <x class="testformat">
322
+ <a class="integer">1</a>
323
+ </x>
324
+ <x class="testformat">
325
+ <a class="integer">2</a>
326
+ </x>
327
+ </z>
328
+ })
329
+ ary = []
330
+ TestFormat.each(src) do |tf|
331
+ ary << tf
332
+ end
333
+ assert_equal(1, ary[0].integer)
334
+ assert_equal(2, ary[1].integer)
335
+ end
336
+
337
+
338
+ def test_should_find_first_matching_microformats_via_first
339
+ src = %{
340
+ <z>
341
+ <x class="testformat">
342
+ <a class="integer">1</a>
343
+ </x>
344
+ <x class="testformat">
345
+ <a class="integer">2</a>
346
+ </x>
347
+ </z>
348
+ }
349
+ tf = TestFormat.first(src)
350
+ assert_equal(1, tf.integer)
351
+ end
352
+
353
+
354
+ def test_should_parse_integers
355
+ t = TestFormat.new(%{
356
+ <x class="testformat">
357
+ <a class="integer">09</a>
358
+ </x>
359
+ })
360
+ assert_equal(9, t.integer)
361
+ end
362
+
363
+ def test_should_parse_floats
364
+ t = TestFormat.new(%{
365
+ <x class="testformat">
366
+ <a class="float">3.14159</a>
367
+ </x>
368
+ })
369
+ assert_in_delta(3.14159, t.float, 0.01)
370
+ end
371
+
372
+ def test_should_parse_zulu_dates
373
+ t = TestFormat.new(%{
374
+ <x class="testformat">
375
+ <a class="datetime">20050102T030405Z</a>
376
+ </x>
377
+ })
378
+ assert_equal(2005, t.datetime.utc.year)
379
+ assert_equal(1, t.datetime.utc.month)
380
+ assert_equal(2, t.datetime.utc.day)
381
+ assert_equal(3, t.datetime.utc.hour)
382
+ assert_equal(4, t.datetime.utc.min)
383
+ assert_equal(5, t.datetime.utc.sec)
384
+ end
385
+
386
+ def test_should_parse_partial_dates
387
+ t = TestFormat.new(%{
388
+ <x class="testformat">
389
+ <a class="datetime">200501</a>
390
+ </x>
391
+ })
392
+ assert_equal(2005, t.datetime.utc.year)
393
+ assert_equal(1, t.datetime.utc.month)
394
+ end
395
+
396
+ def test_should_return_nil_for_invalid_date
397
+ t = TestFormat.new(%{
398
+ <x class="testformat">
399
+ <a class="datetime">Hello!</a>
400
+ </x>
401
+ })
402
+ assert_nil(t.datetime)
403
+ t = TestFormat.new(%{
404
+ <x class="testformat">
405
+ <a class="datetime">9999-88-88</a>
406
+ </x>
407
+ })
408
+ assert_nil(t.datetime)
409
+ end
410
+
411
+ def test_should_parse_time_zones_with_multiple_pluses
412
+ t = TestFormat.new(%{
413
+ <x class="testformat">
414
+ <a class="datetime">20010101T120000++0200</a>
415
+ </x>
416
+ })
417
+ assert_equal(10, t.datetime.utc.hour)
418
+ end
419
+
420
+ def test_should_parse_time_zones_with_multiple_minuses
421
+ t = TestFormat.new(%{
422
+ <x class="testformat">
423
+ <a class="datetime">20010101T120000--0200</a>
424
+ </x>
425
+ })
426
+ assert_equal(14, t.datetime.utc.hour)
427
+ end
428
+
429
+
430
+ def test_should_handle_email_when_encapsulated_by_unknown_element
431
+ t = TestFormat.new(%{
432
+ <x class="testformat">
433
+ <div class="te">
434
+ <a class="internet" href="mailto:info@example.com">info@example.com</a>
435
+ </div>
436
+ </x>
437
+ })
438
+ assert_equal('info@example.com', t.te.value)
439
+ end
440
+
441
+ end