simple_mapper 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,194 @@
1
+ require 'test_helper'
2
+ require 'delegate'
3
+ class CollectionArrayTest < Test::Unit::TestCase
4
+ context 'A SimpleMapper::Collection::Array' do
5
+ setup do
6
+ @class = SimpleMapper::Collection::Array
7
+ end
8
+
9
+ should 'be equivalent to a standard Array' do
10
+ array = [:a, :b, :c]
11
+ instance = @class.new(array)
12
+ assert_equal array, instance
13
+ assert_equal @class, instance.class
14
+ end
15
+
16
+ should 'allow array-style assignments, lookups, size checks, etc' do
17
+ instance = @class.new
18
+ assert_equal 0, instance.size
19
+ instance << :a
20
+ assert_equal 1, instance.size
21
+ assert_equal :a, instance[0]
22
+ instance[4] = :b
23
+ assert_equal 5, instance.size
24
+ assert_equal [:a, nil, nil, nil, :b], instance
25
+ end
26
+
27
+ should 'disallow non-integer indexes' do
28
+ instance = @class.new
29
+ assert_raise(TypeError) { instance[:a] = 'a' }
30
+ end
31
+
32
+ should 'provide list of indexes via :keys' do
33
+ assert_equal (0..3).to_a, @class.new([:a, :b, :c, :d]).keys
34
+ end
35
+
36
+ should 'yield key/value pairs instead of just value for :inject' do
37
+ source = [:a, :b, :c, :d, :e]
38
+ expected = (0..(source.size - 1)).to_a.collect {|key| [key, source[key]]}
39
+ result = @class.new(source).inject([]) {|a, pair| a << pair; a}
40
+ assert_equal expected, result
41
+ end
42
+
43
+ should 'have an :attribute attribute' do
44
+ instance = @class.new
45
+ assert_equal nil, instance.attribute
46
+ attrib = stub('attribute')
47
+ assert_equal attrib, instance.attribute = attrib
48
+ assert_equal attrib, instance.attribute
49
+ end
50
+
51
+ should 'allow creation of new mapper instances through :build' do
52
+ mapper = stub('mapper')
53
+ attrib = stub('attrib', :mapper => mapper)
54
+ seq = sequence('build invocations')
55
+ mapper.expects(:new).with.in_sequence(seq).returns(:one)
56
+ mapper.expects(:new).with(:a => 'A').in_sequence(seq).returns(:two)
57
+ mapper.expects(:new).with(:a, :b, :c).in_sequence(seq).returns(:three)
58
+ instance = @class.new
59
+ instance.attribute = attrib
60
+ result = []
61
+ result << instance.build
62
+ result << instance.build(:a => 'A')
63
+ result << instance.build(:a, :b, :c)
64
+ assert_equal [:one, :two, :three], result
65
+ end
66
+
67
+ context 'with change_tracking true' do
68
+ context 'when appending to the array' do
69
+ setup do
70
+ @instance = @class.new
71
+ @instance.attribute = stub('attrib', :mapper => nil)
72
+ @instance.change_tracking = false
73
+ @values = [:a, :b, :c]
74
+ @instance.push(*@values)
75
+ @instance.change_tracking = true
76
+ end
77
+
78
+ should 'mark new item for :<< as changed' do
79
+ @instance << :zzz
80
+ assert_equal [@values.size], @instance.changed_members
81
+ end
82
+
83
+ should 'mark new items for :push as changed' do
84
+ @instance.push(:xxx, :yyy, :zzz)
85
+ assert_equal [@values.size, @values.size + 1, @values.size + 2], @instance.changed_members
86
+ end
87
+ end
88
+ end
89
+
90
+ context 'when deleting from the array' do
91
+ setup do
92
+ @instance = @class.new
93
+ @instance.attribute = stub('attrib', :mapper => nil)
94
+ @instance.change_tracking = false
95
+ @values = ('a'..'z').to_a
96
+ @instance.push(*@values)
97
+ @instance.change_tracking = true
98
+ end
99
+
100
+ context 'with slice! given a range' do
101
+ should 'identify all indexes from the range minimum to the original size as changed' do
102
+ range = 5..10
103
+ expected_list = @values.clone
104
+ expected_slice = expected_list.slice!(range)
105
+ expected_changes = (5..(@values.size - 1)).to_a
106
+ assert_equal expected_slice, @instance.slice!(range)
107
+ assert_equal expected_list, @instance
108
+ assert_equal expected_changes, @instance.changed_members
109
+ end
110
+
111
+ should 'not mark changes for indexes outside the original size' do
112
+ range = (@values.size - 2)..(@values.size + 1)
113
+ expected_list = @values.clone
114
+ expected_slice = expected_list.slice!(range)
115
+ assert_equal expected_slice, @instance.slice!(range)
116
+ assert_equal expected_list, @instance
117
+ assert_equal [@values.size - 2, @values.size - 1], @instance.changed_members
118
+ end
119
+ end
120
+
121
+ context 'with slice! given a start index and length' do
122
+ should 'identify all indexes from start up to original size as changed' do
123
+ start = @values.size - 5
124
+ expected_list = @values.clone
125
+ expected_slice = expected_list.slice!(start, 3)
126
+ assert_equal expected_slice, @instance.slice!(start, 3)
127
+ assert_equal expected_list, @instance
128
+ assert_equal ((@values.size - 5)..(@values.size - 1)).to_a, @instance.changed_members
129
+ end
130
+
131
+ should 'not mark changes for indexes outside the original size' do
132
+ start = @values.size - 2
133
+ expected_list = @values.clone
134
+ expected_slice = expected_list.slice!(start, 1000)
135
+ assert_equal expected_slice, @instance.slice!(start, 1000)
136
+ assert_equal expected_list, @instance
137
+ assert_equal [@values.size - 2, @values.size - 1], @instance.changed_members
138
+ end
139
+
140
+ should 'handle negative start index properly' do
141
+ expected_list = @values.clone
142
+ expected_slice = expected_list.slice!(-5, 1)
143
+ assert_equal expected_slice, @instance.slice!(-5, 1)
144
+ assert_equal expected_list, @instance
145
+ assert_equal ((@values.size - 5)..(@values.size - 1)).to_a, @instance.changed_members
146
+ end
147
+ end
148
+
149
+ context 'with delete_at' do
150
+ should 'return deleted item and marked index to end as changed if index is in range' do
151
+ index = @values.size - 5
152
+ assert_equal @values[index], @instance.delete_at(index)
153
+ assert_equal @values.slice(0, index) + @values.slice(index + 1, 4), @instance
154
+ assert_equal (index..@values.size - 1).to_a, @instance.changed_members
155
+ end
156
+
157
+ should 'do nothing but return nil if index out of range' do
158
+ assert_equal nil, @instance.delete_at(@values.size)
159
+ assert_equal @values, @instance
160
+ assert_equal [], @instance.changed_members
161
+ end
162
+ end
163
+
164
+ context 'with reject!' do
165
+ should 'return the altered list and mark all items as changed from first removed to end' do
166
+ start_index = @values.size - 10
167
+ expected_values = @values.clone.reject! {|x| x >= @values[start_index]}
168
+ assert_equal(expected_values, @instance.reject! {|val| val >= @values[start_index]})
169
+ assert_equal (start_index..@values.size - 1).to_a, @instance.changed_members
170
+ end
171
+
172
+ should 'do nothing but return nil if nothing is deleted' do
173
+ assert_equal(nil, @instance.reject! {|v| false})
174
+ assert_equal [], @instance.changed_members
175
+ end
176
+ end
177
+
178
+ context 'with delete_if' do
179
+ should 'return the altered list and mark all items as changed from first removed to end' do
180
+ start = @values.size - 10
181
+ key = @values[start]
182
+ expected_values = @values.find_all {|x| x < key}
183
+ assert_equal(expected_values, @instance.delete_if {|val| val >= key})
184
+ assert_equal (start..@values.size-1).to_a, @instance.changed_members
185
+ end
186
+
187
+ should 'do nothing but return unchanged list if nothing is deleted' do
188
+ assert_equal(@values, @instance.delete_if {|val| false})
189
+ assert_equal [], @instance.changed_members
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,139 @@
1
+ require 'test_helper'
2
+
3
+ class CollectionHashTest < Test::Unit::TestCase
4
+ context 'A SimpleMapper::Collection::Hash' do
5
+ setup do
6
+ @class = SimpleMapper::Collection::Hash
7
+ end
8
+
9
+ should 'be equivalent to a hash passed to the constructor' do
10
+ hash = {:a => 'A', :b => 'B'}
11
+ instance = @class.new(hash)
12
+ assert_equal hash, instance
13
+ assert_equal @class, instance.class
14
+ end
15
+
16
+ should 'allow hash-style lookups, assignments, etc.' do
17
+ instance = @class.new
18
+ assert_equal nil, instance[:foo]
19
+ assert_equal :bar, instance[:foo] = :bar
20
+ assert_equal :bar, instance[:foo]
21
+ assert_equal true, instance.has_key?(:foo)
22
+ assert_equal false, instance.has_key?(:blah)
23
+ assert_equal :blee, instance[:boo] = :blee
24
+ assert_equal :blee, instance[:boo]
25
+ assert_equal([:boo, :foo], instance.keys.sort_by {|k| k.to_s})
26
+ end
27
+
28
+ should 'have an :attribute attribute' do
29
+ instance = @class.new
30
+ assert_equal nil, instance.attribute
31
+ attrib = stub('attribute_object')
32
+ assert_equal attrib, instance.attribute = attrib
33
+ assert_equal attrib, instance.attribute
34
+ end
35
+
36
+ should 'allow construction of new mapper instances through :build' do
37
+ mapper = stub('mapper')
38
+ attrib = stub('attribute_object', :mapper => mapper)
39
+ seq = sequence('build invocations')
40
+ mapper.expects(:new).with.in_sequence(seq).returns(:one)
41
+ mapper.expects(:new).with(:a => 'A').in_sequence(seq).returns(:two)
42
+ mapper.expects(:new).with(:foo, :bar, :a => 'A', :b => 'B').in_sequence(seq).returns(:three)
43
+ instance = @class.new
44
+ instance.attribute = attrib
45
+ result = []
46
+ result << instance.build
47
+ result << instance.build(:a => 'A')
48
+ result << instance.build(:foo, :bar, :a => 'A', :b => 'B')
49
+ assert_equal [:one, :two, :three], result
50
+ end
51
+
52
+ context 'tracking changes' do
53
+ should 'be off by default' do
54
+ instance = @class.new
55
+ assert_equal false, (instance.change_tracking || false)
56
+ end
57
+
58
+ context 'for assignment' do
59
+ should 'mark newly-assigned key as changed and mark entre value as changed if new value supports :all_changed' do
60
+ instance = @class.new
61
+ instance.change_tracking = true
62
+ assert_equal([], instance.changed_members)
63
+ instance[:a] = 'A'
64
+ assert_equal([:a], instance.changed_members)
65
+
66
+ change_me = stub('change_me')
67
+ change_me.expects(:all_changed!).with
68
+ instance[:b] = change_me
69
+ assert_equal([:a, :b], instance.changed_members)
70
+ end
71
+
72
+ should 'mark replaced key as changed' do
73
+ instance = @class.new({:a => 'A'})
74
+ instance.change_tracking = true
75
+ assert_equal([], instance.changed_members)
76
+ instance[:a] = 'Aprime'
77
+ assert_equal([:a], instance.changed_members)
78
+ end
79
+
80
+ should 'mark replaced key as changed and mark entire value as changed if new value supports :all_changed!' do
81
+ instance = @class.new({:a => 'A'})
82
+ instance.change_tracking = true
83
+ assert_equal([], instance.changed_members)
84
+ obj = stub('new value')
85
+ obj.expects(:all_changed!).with.returns(true)
86
+ instance[:a] = obj
87
+ assert_equal([:a], instance.changed_members)
88
+ end
89
+
90
+ should 'not track any changes on assignment if track_changes is false' do
91
+ instance = @class.new({:a => 'A', :b => 'B'})
92
+ instance.change_tracking = false
93
+ assert_equal([], instance.changed_members)
94
+ instance[:a] = 'A prime'
95
+ instance[:b] = nil
96
+ instance[:c] = 'C'
97
+ assert_equal([], instance.changed_members)
98
+ end
99
+ end
100
+
101
+ context 'for deletion' do
102
+ setup do
103
+ @key = :delete_me
104
+ @value = 'delete me!'
105
+ @keep_key = :keep_me
106
+ @keep_value = 'keep me!'
107
+ @instance = @class.new({@key => @value, @keep_key => @keep_value})
108
+ @instance.change_tracking = true
109
+ end
110
+
111
+ should 'mark key as changed when key is specified in a :delete' do
112
+ assert_equal @value, @instance.delete(@key)
113
+ assert_equal false, @instance.key?(@key)
114
+ assert_equal [@key], @instance.changed_members
115
+ end
116
+
117
+ should 'mark key as changed when key is removed via :delete_if' do
118
+ assert_equal({@keep_key => @keep_value}, @instance.delete_if {|k,v| k == @key})
119
+ assert_equal false, @instance.key?(@key)
120
+ assert_equal [@key], @instance.changed_members
121
+ end
122
+
123
+ should 'mark key as changed when key is removed via :reject!' do
124
+ assert_equal({@keep_key => @keep_value}, @instance.reject! {|k,v| k == @key})
125
+ assert_equal false, @instance.key?(@key)
126
+ assert_equal [@key], @instance.changed_members
127
+ end
128
+
129
+ should 'mark nothing as changed if change_tracking is false at delete time' do
130
+ @instance.change_tracking = false
131
+ @instance.delete(:a)
132
+ @instance.delete_if {|k, v| true}
133
+ assert_equal [], @instance.changed_members
134
+ end
135
+ end
136
+ end
137
+
138
+ end
139
+ end
@@ -0,0 +1,314 @@
1
+ require 'test_helper'
2
+ require 'date'
3
+
4
+ class AttributesTypesTest < Test::Unit::TestCase
5
+ context 'The Float type' do
6
+ setup do
7
+ @type = SimpleMapper::Attributes::Types::Float
8
+ end
9
+
10
+ should 'decode the empty string to nil' do
11
+ assert_equal nil, @type.decode('')
12
+ end
13
+
14
+ should 'decode simple integer strings' do
15
+ [1, 2, 3, 5, 7, 11, 13, 17, 21129].each do |num|
16
+ result = @type.decode(num.to_s)
17
+ assert_equal Float, result.class
18
+ assert_equal num.to_f, result
19
+ end
20
+ end
21
+
22
+ should 'decode actual integers' do
23
+ [1, 13, 21000, 4600].each do |num|
24
+ result = @type.decode(num)
25
+ assert_equal Float, result.class
26
+ assert_equal num.to_f, result
27
+ end
28
+ end
29
+
30
+ should 'decode valid floats' do
31
+ [1.31, 46.723, 0.00290004, 16.161616].each do |num|
32
+ result = @type.decode(num.to_s)
33
+ assert_equal Float, result.class
34
+ assert_equal num, result
35
+ end
36
+ end
37
+
38
+ should 'handle float with decimal point but not fractional portion' do
39
+ assert_equal 21.0, @type.decode('21.')
40
+ end
41
+
42
+ should 'throw a TypeConversionException given invalid decode input' do
43
+ ['total junk', :more_garbage, '12a32b23c', ' '].each do |value|
44
+ assert_raise(SimpleMapper::TypeConversionException) { @type.decode(value) }
45
+ end
46
+ end
47
+
48
+ should 'encode numeric inputs to a string' do
49
+ [1, 1.0, 0, 0.0, 21129, 46.723, 15.1515].each do |num|
50
+ assert_equal num.to_f.to_s, @type.encode(num)
51
+ end
52
+ end
53
+
54
+ should 'encode nil as nil' do
55
+ assert_equal nil, @type.encode(nil)
56
+ end
57
+
58
+ should 'throw a TypeConversionException given non-numeric inputs for encode' do
59
+ ['', :xyz, 'abc', ' 0 . 77asd '].each do |input|
60
+ assert_raise(SimpleMapper::TypeConversionException) { @type.encode(input) }
61
+ end
62
+ end
63
+
64
+ should 'provide a default of nil' do
65
+ assert_equal nil, @type.default
66
+ end
67
+
68
+ should 'be registered as :float' do
69
+ assert_equal({:name => :float,
70
+ :expected_type => Float,
71
+ :converter => @type,}, SimpleMapper::Attributes.type_for(:float))
72
+ end
73
+ end
74
+
75
+ context 'the String type' do
76
+ setup do
77
+ @type = SimpleMapper::Attributes::Types::String
78
+ end
79
+
80
+ should 'encode nil as nil' do
81
+ assert_equal nil, @type.encode(nil)
82
+ end
83
+
84
+ should 'decode nil as nil' do
85
+ assert_equal nil, @type.decode(nil)
86
+ end
87
+
88
+ context 'given input with to_s' do
89
+ setup do
90
+ @value = 'to_s result'
91
+ @input = mock('input')
92
+ @input.expects(:to_s).with.returns(@value)
93
+ end
94
+
95
+ should 'return result of :to_s on input for :encode' do
96
+ assert_equal @value, @type.encode(@input)
97
+ end
98
+
99
+ should 'return result of :to_s on input for :decode' do
100
+ assert_equal @value, @type.decode(@input)
101
+ end
102
+ end
103
+
104
+ should 'provide empty string for the default' do
105
+ assert_equal '', @type.default
106
+ end
107
+
108
+ should 'be registered as :string' do
109
+ assert_equal({:name => :string,
110
+ :expected_type => String,
111
+ :converter => @type,}, SimpleMapper::Attributes.type_for(:string))
112
+ end
113
+ end
114
+
115
+ context 'the SimpleUUID type' do
116
+ setup do
117
+ @type = SimpleMapper::Attributes::Types::SimpleUUID
118
+ @class = SimpleUUID::UUID
119
+ end
120
+
121
+ should 'decode and encode nil as nil' do
122
+ [:decode, :encode].each {|op| assert_equal nil, @type.send(op, nil)}
123
+ end
124
+
125
+ should 'return GUID string representation for :encode of actual UUID object' do
126
+ @uuid = @class.new
127
+ result = @type.encode(@uuid)
128
+ assert_equal String, result.class
129
+ assert_equal @uuid.to_guid, result
130
+ end
131
+
132
+ should 'return GUID string representation for :encode of a GUID-conforming string' do
133
+ @uuid = @class.new
134
+ result = @type.encode(@uuid.to_guid)
135
+ assert_equal String, result.class
136
+ assert_equal @uuid.to_guid, result
137
+ end
138
+
139
+ should 'parse string and return GUID-conforming string for :decode' do
140
+ @uuid = @class.new
141
+ assert_equal @uuid.to_guid, @type.decode(@uuid.to_guid)
142
+ end
143
+
144
+ should ':decode a SimpleUUID::UUID instance to a GUID string' do
145
+ @uuid = @class.new
146
+ assert_equal @uuid.to_guid, @type.decode(@uuid)
147
+ end
148
+
149
+ should 'a new SimpleUUID::UUID-based GUID string for the default' do
150
+ assert_equal String, (first = @type.default).class
151
+ assert_equal String, (second = @type.default).class
152
+ assert_not_equal first, second
153
+ assert_not_equal @class.new(first).to_guid, @class.new(second).to_guid
154
+ assert_equal first, @class.new(first).to_guid
155
+ assert_equal second, @class.new(second).to_guid
156
+ end
157
+
158
+ should 'be registered as :simple_uuid' do
159
+ assert_equal({:name => :simple_uuid,
160
+ :expected_type => nil,
161
+ :converter => @type}, SimpleMapper::Attributes.type_for(:simple_uuid))
162
+ end
163
+ end
164
+
165
+ context 'the Timestamp type' do
166
+ setup do
167
+ @type = SimpleMapper::Attributes::Types::Timestamp
168
+ @class = DateTime
169
+ @format = '%Y-%m-%d %H:%M:%S%z'
170
+ # get time with appropriate resolution
171
+ @now = @class.strptime(@class.now.strftime(@format), @format)
172
+ end
173
+
174
+ should ':decode a timestamp string with %Y-%m-%d %H:%M:%S%z' do
175
+ result = @type.decode(@now.strftime(@format))
176
+ assert_equal @now, result
177
+ end
178
+
179
+ should ':encode a timestamp as string with %Y-%m-%d %H:%M:%S%z structure' do
180
+ assert_equal @now.strftime(@format), @type.encode(@now)
181
+ end
182
+
183
+ should 'pass nil through untouched for :encode and :decode' do
184
+ assert_equal nil, @type.encode(nil)
185
+ assert_equal nil, @type.decode(nil)
186
+ end
187
+
188
+ should 'provide current date/time as default' do
189
+ # yes, if another process/thread preempts this one on a heavily-loaded server,
190
+ # there's a possibility that these won't match. I'm willing to live with that.
191
+ assert_equal @now.strftime(@format), (default = @type.default) && default.strftime(@format)
192
+ end
193
+
194
+ should ':decode a DateTime instance as identity (pass through)' do
195
+ assert_equal @now, @type.decode(@now)
196
+ end
197
+
198
+ should 'be registered as :timestamp type' do
199
+ assert_equal({:name => :timestamp,
200
+ :expected_type => @class,
201
+ :converter => @type}, SimpleMapper::Attributes.type_for(:timestamp))
202
+ end
203
+ end
204
+
205
+ context 'the Integer type' do
206
+ setup do
207
+ @type = SimpleMapper::Attributes::Types::Integer
208
+ @class = Integer
209
+ @ints = [0, 54, 32, 65535, -12, -65535]
210
+ end
211
+
212
+ should 'convert a numeric string into something string-like' do
213
+ @ints.each do |int|
214
+ assert_equal int, @type.decode(int.to_s)
215
+ assert_equal int.to_s, @type.encode(int.to_s)
216
+ end
217
+ end
218
+
219
+ should 'convert integer values as strings' do
220
+ @ints.each do |int|
221
+ assert_equal int, @type.decode(int)
222
+ assert_equal int.to_s, @type.encode(int)
223
+ end
224
+ end
225
+
226
+ should 'handle non-integer objects gracefully if they have integer values' do
227
+ [1.0, '1.0', -12.0, '-12.0', 1245.0, '1245.0', Rational(1,1), Rational(4500,4500)].each do |val|
228
+ assert_equal val.to_i, @type.decode(val)
229
+ assert_equal val.to_i, @type.encode(val).to_i
230
+ end
231
+ end
232
+
233
+ should 'throw type conversion exception if integer value does not appear to match input' do
234
+ ['abc', nil, 14.5, Rational(5,16)].each do |val|
235
+ assert_raise(SimpleMapper::TypeConversionException) { @type.encode(val) }
236
+ assert_raise(SimpleMapper::TypeConversionException) { @type.decode(val) }
237
+ end
238
+ end
239
+
240
+ should 'be registered as :integer type' do
241
+ assert_equal({:expected_type => @class,
242
+ :name => :integer,
243
+ :converter => @type}, SimpleMapper::Attributes.type_for(:integer))
244
+ end
245
+ end
246
+
247
+ context 'the TimestampHighRes type' do
248
+ require 'bigdecimal'
249
+ setup do
250
+ @type = SimpleMapper::Attributes::Types::TimestampHighRes
251
+ @class = DateTime
252
+ @name = :timestamp_high_res
253
+ # put the time in UTC, since this loses time zone info
254
+ @time = DateTime.now
255
+ @format = '%Y-%m-%d %H:%M:%S.%N%z'
256
+ @time_string = @time.strftime(@format)
257
+ @offsets = [0, 1, 2, 3, -1, -2, -3]
258
+ end
259
+
260
+ should 'pass along DateTimes when given DateTime to decode' do
261
+ @offsets.each do |offset|
262
+ assert_equal @time.new_offset(offset), @type.decode(@time.new_offset(offset))
263
+ end
264
+ end
265
+
266
+ should 'decode conformant strings into DateTimes' do
267
+ @offsets.each do |offset|
268
+ assert_equal @time.new_offset(offset).strftime(@format),
269
+ @type.decode(@time.new_offset(offset).strftime(@format)).strftime(@format)
270
+ end
271
+ end
272
+
273
+ # addresses bug in decoding from strings when fractional portion is smaller than .1 sec.
274
+ should 'decode conformat strings with small fractional portions into DateTimes' do
275
+ # get timestamp with no fractional seconds
276
+ time = DateTime.parse(DateTime.now.to_s)
277
+ # cycle through fractions: 0.111111111, 0.011111111, 0.001111111, etc.
278
+ checks = (9..18).collect do |factor|
279
+ sec_fraction = Rational(111111111, 10 ** factor)
280
+ fractional_time = time + (sec_fraction * Rational(1, 24 * 60 * 60))
281
+ fractional_time.strftime(@format)
282
+ end
283
+ results = checks.collect {|input| @type.decode(input).strftime(@format)}
284
+ assert_equal checks, results
285
+ end
286
+
287
+ should 'encode nil as nil' do
288
+ assert_equal true, @type.encode(nil).nil?
289
+ end
290
+
291
+ should 'decode nil as nil' do
292
+ assert_equal true, @type.decode(nil).nil?
293
+ end
294
+
295
+ should 'throw type conversion exceptions if strings do not conform' do
296
+ ['', 'abc', '7145.abc.234'].each do |string|
297
+ assert_raise(SimpleMapper::TypeConversionException) { @type.decode(string) }
298
+ end
299
+ end
300
+
301
+ should 'encode DateTime values into conformant strings' do
302
+ @offsets.each do |offset|
303
+ assert_equal @time.new_offset(offset).strftime(@format),
304
+ @type.encode(@time.new_offset(offset))
305
+ end
306
+ end
307
+
308
+ should 'be registered as the :timestamp_hi_res type' do
309
+ assert_equal({:expected_type => DateTime,
310
+ :name => @name,
311
+ :converter => @type}, SimpleMapper::Attributes.type_for(@name))
312
+ end
313
+ end
314
+ end