gorillib 0.0.8 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/CHANGELOG.textile +6 -0
  2. data/README.textile +34 -11
  3. data/VERSION +1 -1
  4. data/gorillib.gemspec +63 -5
  5. data/lib/gorillib/enumerable/sum.rb +2 -2
  6. data/lib/gorillib/hash/compact.rb +2 -29
  7. data/lib/gorillib/hash/deep_compact.rb +2 -12
  8. data/lib/gorillib/hash/deep_dup.rb +4 -0
  9. data/lib/gorillib/hash/deep_merge.rb +2 -14
  10. data/lib/gorillib/hash/indifferent_access.rb +207 -0
  11. data/lib/gorillib/hash/keys.rb +2 -40
  12. data/lib/gorillib/hash/reverse_merge.rb +2 -24
  13. data/lib/gorillib/hash/slice.rb +2 -51
  14. data/lib/gorillib/hash/tree_merge.rb +4 -0
  15. data/lib/gorillib/hashlike.rb +824 -0
  16. data/lib/gorillib/hashlike/compact.rb +60 -0
  17. data/lib/gorillib/hashlike/deep_compact.rb +18 -0
  18. data/lib/gorillib/hashlike/deep_dup.rb +15 -0
  19. data/lib/gorillib/hashlike/deep_merge.rb +20 -0
  20. data/lib/gorillib/hashlike/hashlike_via_accessors.rb +169 -0
  21. data/lib/gorillib/hashlike/keys.rb +59 -0
  22. data/lib/gorillib/hashlike/reverse_merge.rb +31 -0
  23. data/lib/gorillib/hashlike/slice.rb +67 -0
  24. data/lib/gorillib/hashlike/tree_merge.rb +76 -0
  25. data/lib/gorillib/metaprogramming/mattr_accessor.rb +1 -1
  26. data/lib/gorillib/receiver.rb +315 -0
  27. data/lib/gorillib/receiver/active_model_shim.rb +19 -0
  28. data/lib/gorillib/receiver/acts_as_hash.rb +191 -0
  29. data/lib/gorillib/receiver/acts_as_loadable.rb +42 -0
  30. data/lib/gorillib/receiver/tree_diff.rb +74 -0
  31. data/lib/gorillib/receiver/validations.rb +30 -0
  32. data/lib/gorillib/struct/acts_as_hash.rb +108 -0
  33. data/lib/gorillib/struct/hashlike_iteration.rb +0 -0
  34. data/notes/fancy_hashes_and_receivers.textile +120 -0
  35. data/notes/hash_rdocs.textile +97 -0
  36. data/spec/hash/deep_merge_spec.rb +0 -2
  37. data/spec/hash/indifferent_access_spec.rb +391 -0
  38. data/spec/hash/slice_spec.rb +35 -12
  39. data/spec/hashlike/behave_same_as_hash_spec.rb +105 -0
  40. data/spec/hashlike/hashlike_behavior_spec.rb +824 -0
  41. data/spec/hashlike/hashlike_via_accessors_fuzzing_spec.rb +37 -0
  42. data/spec/hashlike/hashlike_via_accessors_spec.rb +262 -0
  43. data/spec/hashlike_spec.rb +302 -0
  44. data/spec/metaprogramming/aliasing_spec.rb +3 -0
  45. data/spec/metaprogramming/cattr_accessor_spec.rb +2 -0
  46. data/spec/metaprogramming/class_attribute_spec.rb +2 -0
  47. data/spec/metaprogramming/delegation_spec.rb +2 -0
  48. data/spec/metaprogramming/mattr_accessor_spec.rb +2 -0
  49. data/spec/metaprogramming/singleton_class_spec.rb +3 -0
  50. data/spec/receiver/acts_as_hash_spec.rb +286 -0
  51. data/spec/receiver_spec.rb +478 -0
  52. data/spec/spec_helper.rb +11 -6
  53. data/spec/string/truncate_spec.rb +1 -0
  54. data/spec/struct/acts_as_hash_fuzz_spec.rb +67 -0
  55. data/spec/struct/acts_as_hash_spec.rb +426 -0
  56. data/spec/support/hashlike_fuzzing_helper.rb +127 -0
  57. data/spec/support/hashlike_helper.rb +75 -0
  58. data/spec/support/hashlike_struct_helper.rb +37 -0
  59. data/spec/support/hashlike_via_delegation.rb +30 -0
  60. data/spec/support/matchers/be_array_eql.rb +12 -0
  61. data/spec/support/matchers/be_hash_eql.rb +14 -0
  62. data/spec/support/matchers/enumerate_method.rb +10 -0
  63. data/spec/support/matchers/evaluate_to_true.rb +5 -0
  64. metadata +62 -4
@@ -0,0 +1,105 @@
1
+ require File.dirname(__FILE__)+'/../spec_helper'
2
+ require 'enumerator'
3
+ require 'gorillib/hashlike'
4
+
5
+ require GORILLIB_ROOT_DIR('spec/support/hashlike_helper')
6
+ require GORILLIB_ROOT_DIR('spec/support/hashlike_fuzzing_helper')
7
+ require GORILLIB_ROOT_DIR('spec/support/hashlike_via_delegation')
8
+
9
+ class InternalHashWithEquality < InternalHash
10
+ # Override these so we can compare exceptions.
11
+ def to_s() @myhsh.to_s ; end
12
+ def ==(other_hash) @myhsh == other_hash ; end
13
+ end
14
+
15
+ describe Gorillib::Hashlike do
16
+
17
+ if ENV['FULL_SPECS']
18
+
19
+ include HashlikeFuzzingHelper
20
+
21
+ before do
22
+ @total = 0
23
+ @hsh = HashlikeFuzzingHelper::HASH_TO_TEST_FULLY_HASHLIKE.dup
24
+ @hshlike = InternalHashWithEquality.new.merge(HashlikeFuzzingHelper::HASH_TO_TEST_FULLY_HASHLIKE)
25
+ end
26
+
27
+ it 'does everything a hash can do' do
28
+ hsh_methods = ({}.methods.map(&:to_sym) - HashlikeHelper::OMITTED_METHODS_FROM_HASH - HashlikeHelper::HASH_METHODS_MISSING_FROM_VERSION)
29
+ hshlike_methods = (@hshlike.methods.map(&:to_sym) -
30
+ ([:hash_eql?, :myhsh] + HashlikeHelper::HASH_METHODS_MISSING_FROM_VERSION))
31
+ hsh_methods.sort_by(&:to_s).should == hshlike_methods.sort_by(&:to_s)
32
+ end
33
+
34
+ it 'has specs for every Hash method' do
35
+ (@hshlike.methods.map(&:to_sym) -
36
+ (Object.new.methods.map(&:to_sym) +
37
+ HashlikeHelper::METHODS_TO_TEST +
38
+ HashlikeHelper::HASH_METHODS_MISSING_FROM_VERSION +
39
+ [:hash_eql?, :myhsh])
40
+ ).should == []
41
+ end
42
+
43
+ ( HashlikeHelper::METHODS_TO_TEST -
44
+ HashlikeHelper::HASH_METHODS_MISSING_FROM_VERSION
45
+ ).each do |method_to_test|
46
+ describe "##{method_to_test} same as for Hash" do
47
+ HashlikeFuzzingHelper::INPUTS_WHEN_FULLY_HASHLIKE.each do |input|
48
+
49
+ it "on #{input.inspect}" do
50
+ behaves_the_same(@hsh, @hshlike, method_to_test, input)
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+
57
+ #
58
+ # With a few exceptions (see HASHLIKE_DEPENDENT_METHODS), all hashlike methods go through only the following core methods:
59
+ #
60
+ HASHLIKE_CONTRACT_METHODS = [:[], :[]=, :delete, :keys, :each_pair, :has_key?] + Object.public_instance_methods.map(&:to_sym)
61
+ #
62
+ # With a few exceptions, all hashlike methods go through only the core methods
63
+ # in HASHLIKE_CONTRACT_METHODS. The Enumerable methods go though :each, and
64
+ # these exceptions call a tightly-bound peer:
65
+ #
66
+ HASHLIKE_DEPENDENT_METHODS = Hash.new([]).merge({
67
+ :merge => [:update], :rassoc => [:key], :flatten => [:to_hash], :invert => [:to_hash],
68
+ :keep_if => [:select!], :delete_if => [:reject!], :select => [:select!, :keep_if], :reject => [:reject!, :delete_if],
69
+ })
70
+ Enumerable.public_instance_methods.map(&:to_sym).each{|meth| HASHLIKE_DEPENDENT_METHODS[meth] << :each }
71
+
72
+ include HashlikeFuzzingHelper
73
+ before do
74
+ @total = 0
75
+ @hsh = HashlikeFuzzingHelper::HASH_TO_TEST_FULLY_HASHLIKE.dup
76
+ end
77
+
78
+ def nuke_most_methods_except(klass, method_to_test)
79
+ (klass.public_instance_methods.map(&:to_sym) -
80
+ (HASHLIKE_CONTRACT_METHODS + HASHLIKE_DEPENDENT_METHODS[method_to_test] + [method_to_test])).each do |method|
81
+ @hshlike_klass.send(:undef_method, method)
82
+ end
83
+ end
84
+
85
+ ( HashlikeHelper::METHODS_TO_TEST
86
+ ).each do |method_to_test|
87
+ describe "##{method_to_test} same as for Hash" do
88
+ before do
89
+ @hshlike_klass = Class.new(InternalHashWithEquality)
90
+ @hshlike = @hshlike_klass.new
91
+ @hshlike.merge!(HashlikeFuzzingHelper::HASH_TO_TEST_FULLY_HASHLIKE)
92
+ nuke_most_methods_except(@hshlike_klass, method_to_test)
93
+ end
94
+ HashlikeFuzzingHelper::INPUTS_WHEN_FULLY_HASHLIKE.each do |input|
95
+ it "on #{input.inspect}" do
96
+ behaves_the_same(@hsh, @hshlike, method_to_test, input)
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ else
103
+ it 'skipping full specs -- set environment variable FULL_SPECS=true to run all specs'
104
+ end
105
+ end
@@ -0,0 +1,824 @@
1
+ unless defined?(HASHLIKE_BEHAVIOR_SPEC)
2
+ describe '' do
3
+
4
+ # ===========================================================================
5
+ #
6
+ # Fundamental behavior
7
+
8
+ shared_examples_for :hashlike_store_and_retrieve do
9
+ it 'stores and retrieves values' do
10
+ @hshlike[:a].should == 100
11
+ @hshlike[:a] = 999
12
+ @hshlike[:a].should == 999
13
+ end
14
+
15
+ it 'returns nil on a missing (but valid) key' do
16
+ @hshlike[:new_key].should == nil
17
+ end
18
+ end
19
+
20
+ shared_examples_for :references_string_and_symbol_keys_equivalently do
21
+ it 'treats string and symbol keys as the same thing' do
22
+ @hshlike['c'].should == 300
23
+ @hshlike[:c].should == 300
24
+ @hshlike['c'] = 999
25
+ @hshlike['c'].should == 999
26
+ @hshlike[:c].should == 999
27
+ @hshlike.delete('c').should == 999
28
+ @hshlike['c'].should be_nil
29
+ @hshlike[:c].should be_nil
30
+ end
31
+ end
32
+
33
+ shared_examples_for :references_complex_keys do
34
+ it 'treats string and symbol keys as distinct' do
35
+ @hshlike['c'].should be_nil
36
+ @hshlike[:c].should == 300
37
+ @hshlike['c'] = 999
38
+ @hshlike['c'].should == 999
39
+ @hshlike[:c].should == 300
40
+ @hshlike.delete('c').should == 999
41
+ @hshlike['c'].should be_nil
42
+ @hshlike[:c].should == 300
43
+ end
44
+
45
+ it 'allows nil, Object, and other non-stringy keys' do
46
+ @hshlike[300] = :i_haz_num ; @hshlike[300].should == :i_haz_num; @hshlike.delete(300).should == :i_haz_num
47
+ @hshlike[nil] = :i_haz_nil ; @hshlike[nil].should == :i_haz_nil; @hshlike.delete(nil).should == :i_haz_nil
48
+ obj = Object.new
49
+ @hshlike[obj] = :i_haz_obj ; @hshlike[obj].should == :i_haz_obj; @hshlike.delete(obj).should == :i_haz_obj
50
+ @hshlike.should be_hash_eql(HashlikeHelper::BASE_HSH)
51
+ end
52
+ end
53
+
54
+ shared_examples_for :accepts_arbitrary_keys do
55
+ it 'never-seen keys' do
56
+ @hshlike[:fnord] = 69
57
+ @hshlike[:fnord].should == 69
58
+ @hshlike.delete(:fnord).should == 69
59
+ @hshlike[:boink].should be_nil
60
+ end
61
+ end
62
+
63
+ shared_examples_for :hashlike_delete do
64
+ it 'removes the key/value association and returns the value' do
65
+ @hshlike.delete(:a).should == 100
66
+ @hshlike.delete(:is_missing).should be_nil
67
+ @hshlike.delete(:false_val).should == false
68
+ @hshlike.should be_hash_eql({ :b => 200, :c => 300, :nil_val => nil })
69
+ end
70
+ describe 'with optional code block' do
71
+ it 'returns the value of executing the block (passing in the key)' do
72
+ set_in_block = nil
73
+ ret_val = @hshlike.delete(:is_missing){|k| set_in_block = "got: #{k}" ; "hello!" }
74
+ set_in_block.should == "got: is_missing"
75
+ ret_val.should == "hello!"
76
+ end
77
+ end
78
+ it 'does not include? key after deleting' do
79
+ @hshlike.should include(:a)
80
+ @hshlike.delete(:a)
81
+ @hshlike.should_not include(:a)
82
+ @hshlike[:a].should be_nil
83
+ end
84
+ end
85
+
86
+ shared_examples_for :hashlike_keys do
87
+ it 'lists keys, even where values are nil' do
88
+ @hshlike.keys.should be_array_eql([:a, :b, :c, :nil_val, :false_val])
89
+ @hshlike[:nil_val].should be_nil
90
+ end
91
+ it 'is an empty array when there are no keys' do
92
+ @empty_hshlike.keys.should == []
93
+ end
94
+ it 'setting an association creates a new key' do
95
+ @hshlike[:new_key] = 3
96
+ @hshlike.keys.should be_array_eql([:a, :b, :c, :nil_val, :false_val, :new_key])
97
+ end
98
+ end
99
+
100
+ # ===========================================================================
101
+ #
102
+ # Iteration
103
+
104
+ shared_examples_for :with_no_block_returns_enumerator do |method_to_test|
105
+ it('returns an enumerator'){ @hshlike.send(method_to_test).should enumerate_method(@hshlike, method_to_test) }
106
+ end
107
+
108
+ shared_examples_for :each_pair_on_stringlike_keys do |method_to_test|
109
+ it 'calls block once for each key/value pair in hsh' do
110
+ seen_arg1 = []
111
+ seen_arg2 = []
112
+ @hshlike.send(method_to_test){|arg1,arg2| seen_arg1 << arg1 ; seen_arg2 << arg2 }
113
+ seen_arg1.should be_array_eql([:a, :b, :c, :nil_val, :false_val ])
114
+ seen_arg2.should be_array_eql([100, 200, 300, nil, false ])
115
+ end
116
+ it 'with arity 1, returns arrays' do
117
+ seen_args = []
118
+ @hshlike.send(method_to_test){|arg| seen_args << arg }
119
+ seen_args.should be_array_eql([[:a, 100], [:b, 200], [:c, 300], [:nil_val, nil], [:false_val, false]])
120
+ end
121
+ it 'handles array vals' do
122
+ seen_args = []
123
+ @hshlike_with_array_vals.send(method_to_test){|arg1, arg2, arg3| seen_args << [arg1, arg2, arg3] }
124
+ seen_args.should be_array_eql([[:a, [100, 111], nil], [:b, 200, nil], [:c, [1, [2, 3, [4, 5, 6]]], nil]])
125
+ seen_args = []
126
+ @hshlike_with_array_vals.send(method_to_test){|(arg1, arg2), arg3| seen_args << [arg1, arg2, arg3] }
127
+ seen_args.should be_array_eql([[:a, nil, [100, 111]], [:b, nil, 200], [:c, nil, [1, [2, 3, [4, 5, 6]]]]])
128
+ end
129
+ it 'returns self' do
130
+ ret_val = @hshlike.send(method_to_test){|k,v| 3 }
131
+ ret_val.should equal(@hshlike)
132
+ end
133
+ end
134
+
135
+ #
136
+ # For a struct, the keys exist eternally, so even an unset key/value pair is iterated
137
+ #
138
+ shared_examples_for :each_pair_on_stringlike_fixed_keys do |method_to_test|
139
+ it 'calls block once for each key/value pair in hsh' do
140
+ seen_arg1 = []
141
+ seen_arg2 = []
142
+ @hshlike.send(method_to_test){|arg1,arg2| seen_arg1 << arg1 ; seen_arg2 << arg2 }
143
+ seen_arg1.should be_array_eql([:a, :b, :c, :nil_val, :false_val, :new_key ])
144
+ seen_arg2.should be_array_eql([100, 200, 300, nil, false, nil ])
145
+ end
146
+ it 'with arity 1, returns array pairs' do
147
+ seen_args = []
148
+ @hshlike.send(method_to_test){|arg| seen_args << arg }
149
+ # seen_args.should be_array_eql([[:a, 100], [:b, 200], [:c, 300], [:nil_val, nil], [:false_val, false], [:new_key, nil]])
150
+ seen_args.should be_array_eql([:a, :b, :c, :nil_val, :false_val, :new_key])
151
+ end
152
+ it 'handles array vals' do
153
+ seen_args = []
154
+ @hshlike_with_array_vals.send(method_to_test){|arg1, arg2, arg3| seen_args << [arg1, arg2, arg3] }
155
+ seen_args.should be_array_eql([[:a, [100, 111], nil], [:b, 200, nil], [:c, [1, [2, 3, [4, 5, 6]]], nil], [:nil_val, nil, nil], [:false_val, nil, nil], [:new_key, nil, nil]])
156
+ seen_args = []
157
+ @hshlike_with_array_vals.send(method_to_test){|(arg1, arg2), arg3| seen_args << [arg1, arg2, arg3] }
158
+ seen_args.should be_array_eql([[:a, nil, [100, 111]], [:b, nil, 200], [:c, nil, [1, [2, 3, [4, 5, 6]]]], [:nil_val, nil, nil], [:false_val, nil, nil], [:new_key, nil, nil]])
159
+ end
160
+ it 'returns self' do
161
+ ret_val = @hshlike.send(method_to_test){|k,v| 3 }
162
+ ret_val.should equal(@hshlike)
163
+ end
164
+ end
165
+
166
+ shared_examples_for :each_key_on_stringlike_keys do
167
+ it 'calls block once for each key in hsh' do
168
+ seen_keys = []
169
+ @hshlike.each_key{|k| seen_keys << k }
170
+ seen_keys.should be_array_eql([:a, :b, :c, :nil_val, :false_val ])
171
+ end
172
+ it 'handles array vals and extra arity' do
173
+ seen_args = []
174
+ @hshlike.each_key{|arg1, arg2, arg3| seen_args << [arg1, arg2, arg3] }
175
+ seen_args.should be_array_eql([[:a, nil, nil], [:b, nil, nil], [:c, nil, nil], [:nil_val, nil, nil], [:false_val, nil, nil] ])
176
+ seen_args = []
177
+ @hshlike_with_array_vals.each_key{|arg1, arg2, arg3| seen_args << [arg1, arg2, arg3] }
178
+ seen_args.should be_array_eql([[:a, nil, nil], [:b, nil, nil], [:c, nil, nil] ])
179
+ seen_args = []
180
+ @hshlike_with_array_vals.each_key{|(arg1, arg2), arg3| seen_args << [arg1, arg2, arg3] }
181
+ seen_args.should be_array_eql([[:a, nil, nil], [:b, nil, nil], [:c, nil, nil] ])
182
+ end
183
+ it 'returns self' do
184
+ ret_val = @hshlike.each_key{|k,v| 3 }
185
+ ret_val.should equal(@hshlike)
186
+ end
187
+ end
188
+
189
+ #
190
+ # For a struct, the keys exist eternally, so even an unset key/value pair is iterated
191
+ #
192
+ shared_examples_for :each_key_on_stringlike_fixed_keys do |method_to_test|
193
+ it 'calls block once for each key in hsh' do
194
+ seen_keys = []
195
+ @hshlike.each_key{|k| seen_keys << k }
196
+ seen_keys.should be_array_eql([:a, :b, :c, :nil_val, :false_val, :new_key ])
197
+ end
198
+ it 'handles array keys and extra arity' do
199
+ seen_args = []
200
+ @hshlike.each_key{|arg1, arg2, arg3| seen_args << [arg1, arg2, arg3] }
201
+ seen_args.should be_array_eql([[:a, nil, nil], [:b, nil, nil], [:c, nil, nil], [:nil_val, nil, nil], [:false_val, nil, nil], [:new_key, nil, nil] ])
202
+ seen_args = []
203
+ @hshlike_with_array_vals.each_key{|arg1, arg2, arg3| seen_args << [arg1, arg2, arg3] }
204
+ seen_args.should be_array_eql([[:a, nil, nil], [:b, nil, nil], [:c, nil, nil], [:nil_val, nil, nil], [:false_val, nil, nil], [:new_key, nil, nil]])
205
+ seen_args = []
206
+ @hshlike_with_array_vals.each_key{|(arg1, arg2), arg3| seen_args << [arg1, arg2, arg3] }
207
+ seen_args.should be_array_eql([[:a, nil, nil], [:b, nil, nil], [:c, nil, nil], [:nil_val, nil, nil], [:false_val, nil, nil], [:new_key, nil, nil]])
208
+ end
209
+ it 'returns self' do
210
+ ret_val = @hshlike.each_key{|k,v| 3 }
211
+ ret_val.should equal(@hshlike)
212
+ end
213
+ end
214
+
215
+ shared_examples_for :each_value_on_stringlike_keys do
216
+ it 'calls block once for each key in hsh, passing the value as parameter' do
217
+ seen_vals = []
218
+ @hshlike.each_value{|k| seen_vals << k }
219
+ seen_vals.should be_array_eql([100, 200, 300, nil, false])
220
+ end
221
+ it 'calls block on each value even when nil, false, empty or duplicate' do
222
+ @hshlike[:a] = 999
223
+ @hshlike[:new_key] = 999
224
+ seen_vals = []
225
+ @hshlike.each_value{|k| seen_vals << k }
226
+ seen_vals.should be_array_eql([999, 200, 300, nil, false, 999])
227
+ end
228
+ it 'handles array vals and extra arity' do
229
+ seen_args = []
230
+ @hshlike.each_value{|arg1, arg2, arg3| seen_args << [arg1, arg2, arg3] }
231
+ seen_args.should be_array_eql([[100, nil, nil], [200, nil, nil], [300, nil, nil], [nil, nil, nil], [false, nil, nil]])
232
+ seen_args = []
233
+ @hshlike_with_array_vals.each_value{|arg1, arg2, arg3| seen_args << [arg1, arg2, arg3] }
234
+ seen_args.should be_array_eql([[100, 111, nil], [200, nil, nil], [1, [2, 3, [4, 5, 6]], nil]])
235
+ seen_args = []
236
+ @hshlike_with_array_vals.each_value{|(arg1, arg2), arg3| seen_args << [arg1, arg2, arg3] }
237
+ seen_args.should be_array_eql([[100, nil, 111], [200, nil, nil], [1, nil, [2, 3, [4, 5, 6]]]])
238
+ end
239
+ it 'returns self' do
240
+ ret_val = @hshlike.each_value{|k,v| 3 }
241
+ ret_val.should equal(@hshlike)
242
+ end
243
+ end
244
+
245
+ #
246
+ # For a struct, the keys exist eternally, so even an unset key/value pair is iterated
247
+ #
248
+ shared_examples_for :each_value_on_stringlike_fixed_keys do
249
+ it 'calls block once for each key in hsh, passing the value as parameter' do
250
+ seen_vals = []
251
+ @hshlike.each_value{|k| seen_vals << k }
252
+ seen_vals.should be_array_eql([100, 200, 300, nil, false, nil])
253
+ end
254
+ it 'calls block on each value even when nil, false, empty or duplicate' do
255
+ @hshlike[:a] = 999
256
+ @hshlike[:new_key] = 999
257
+ seen_vals = []
258
+ @hshlike.each_value{|k| seen_vals << k }
259
+ seen_vals.should be_array_eql([999, 200, 300, nil, false, 999 ])
260
+ end
261
+ it 'handles array vals and extra arity' do
262
+ seen_args = []
263
+ @hshlike.each_value{|arg1, arg2, arg3| seen_args << [arg1, arg2, arg3] }
264
+ seen_args.should be_array_eql([[100, nil, nil], [200, nil, nil], [300, nil, nil], [nil, nil, nil], [false, nil, nil], [nil, nil, nil]])
265
+ seen_args = []
266
+ @hshlike_with_array_vals.each_value{|arg1, arg2, arg3| seen_args << [arg1, arg2, arg3] }
267
+ seen_args.should be_array_eql([[100, 111, nil], [200, nil, nil], [1, [2, 3, [4, 5, 6]], nil], [nil, nil, nil], [nil, nil, nil], [nil, nil, nil]])
268
+ seen_args = []
269
+ @hshlike_with_array_vals.each_value{|(arg1, arg2), arg3| seen_args << [arg1, arg2, arg3] }
270
+ seen_args.should be_array_eql([[100, nil, 111], [200, nil, nil], [1, nil, [2, 3, [4, 5, 6]]], [nil, nil, nil], [nil, nil, nil], [nil, nil, nil]])
271
+ end
272
+ it 'returns self' do
273
+ ret_val = @hshlike.each_value{|k,v| 3 }
274
+ ret_val.should equal(@hshlike)
275
+ end
276
+ end
277
+
278
+ shared_examples_for :each_pair_on_complex_keys do |method_to_test|
279
+ it 'handles array keys' do
280
+ seen_args = []
281
+ @hshlike_with_array_keys.each_pair{|arg1, arg2, arg3| seen_args << [arg1, arg2, arg3] }
282
+ seen_args.should be_array_eql([[[:a, :aa], 100, nil], [:b, 200, nil], [[:c, :cc], [300, 333], nil], [[1, 2, [3, 4]], [1, [2, 3, [4, 5, 6]]], nil]])
283
+ seen_args = []
284
+ @hshlike_with_array_keys.each_pair{|(arg1, arg2), arg3| seen_args << [arg1, arg2, arg3] }
285
+ seen_args.should be_array_eql([[:a, :aa, 100], [:b, nil, 200], [:c, :cc, [300, 333]], [1, 2, [1, [2, 3, [4, 5, 6]]]]])
286
+ end
287
+ end
288
+
289
+ shared_examples_for :each_key_on_complex_keys do
290
+ it 'handles array keys and extra arity' do
291
+ seen_args = []
292
+ @hshlike.each_key{|arg1, arg2, arg3| seen_args << [arg1, arg2, arg3] }
293
+ seen_args.should be_array_eql([[:a, nil, nil], [:b, nil, nil], [:c, nil, nil], [:nil_val, nil, nil], [:false_val, nil, nil]])
294
+ seen_args = []
295
+ @hshlike_with_array_keys.each_key{|arg1, arg2, arg3| seen_args << [arg1, arg2, arg3] }
296
+ seen_args.should be_array_eql([[:a, :aa, nil], [:b, nil, nil], [:c, :cc, nil], [1, 2, [3, 4]]])
297
+ seen_args = []
298
+ @hshlike_with_array_keys.each_key{|(arg1, arg2), arg3| seen_args << [arg1, arg2, arg3] }
299
+ seen_args.should be_array_eql([[:a, nil, :aa], [:b, nil, nil], [:c, nil, :cc], [1, nil, 2]])
300
+ end
301
+ end
302
+
303
+ # ===========================================================================
304
+ #
305
+ # Retrieval and Membership
306
+
307
+ shared_examples_for :hashlike_values do
308
+ it 'returns a new array populated with the values from hsh' do
309
+ @hshlike.values.should be_array_eql([100, 200, 300, nil, false])
310
+ end
311
+ end
312
+
313
+ shared_examples_for :hashlike_values_at_or_of do |method_to_test|
314
+ it 'returns an array containing the values associated with the given keys' do
315
+ @hshlike.send(method_to_test, :b, :a, :z, :nil_val).should == [200, 100, nil, nil]
316
+ end
317
+ it 'returns duplicate keys or missing keys in given slot' do
318
+ @hshlike.send(method_to_test, :b, :b, :i_am_missing, :nil_val, :c, '300').should == [200, 200, nil, nil, 300, nil]
319
+ end
320
+ end
321
+
322
+ shared_examples_for :hashlike_length do |method_to_test|
323
+ it 'returns the number of key/value pairs in the hashlike' do
324
+ @hshlike.send(method_to_test).should == 5
325
+ @hshlike[:new_key] = 69
326
+ @hshlike.send(method_to_test).should == 6
327
+ @hshlike.delete(:a)
328
+ @hshlike.delete(:b)
329
+ @hshlike.send(method_to_test).should == 4
330
+ end
331
+ it 'is zero for an empty hashlike' do
332
+ @empty_hshlike.send(method_to_test).should == 0
333
+ end
334
+ end
335
+
336
+ shared_examples_for :hashlike_has_key? do |method_to_test|
337
+ it 'returns true if the given key is present' do
338
+ @hshlike.send(method_to_test, :a).should == true
339
+ end
340
+ it 'returns false if the key is absent, whether pre-defined or not' do
341
+ @hshlike.send(method_to_test, :new_key).should == false
342
+ @hshlike[:new_key] = nil
343
+ @hshlike.send(method_to_test, :new_key).should == true
344
+ @hshlike.send(method_to_test, :fnord).should == false
345
+ end
346
+ it 'is true even if value is nil, empty or false' do
347
+ @hshlike.should evaluate_to_true(method_to_test, :nil_val)
348
+ @hshlike.should evaluate_to_true(method_to_test, :false_val)
349
+ end
350
+ it 'something something convert_key'
351
+ end
352
+
353
+ shared_examples_for :hashlike_has_key_predefined_always_present do |method_to_test|
354
+ it 'returns true if the given key is present' do
355
+ @hshlike.send(method_to_test, :a).should == true
356
+ end
357
+ it 'returns false if the key is absent, whether pre-defined or not' do
358
+ @hshlike.send(method_to_test, :new_key).should == true
359
+ @hshlike[:new_key] = nil
360
+ @hshlike.send(method_to_test, :new_key).should == true
361
+ @hshlike.send(method_to_test, :fnord).should == false
362
+ end
363
+ it 'is true even if value is nil, empty or false' do
364
+ @hshlike.should evaluate_to_true(method_to_test, :nil_val)
365
+ @hshlike.should evaluate_to_true(method_to_test, :false_val)
366
+ end
367
+ it 'something something convert_key'
368
+ end
369
+
370
+ shared_examples_for :hashlike_has_key_string_and_symbol_equivalent do |method_to_test|
371
+ it 'treats symbol and string keys as equivalent' do
372
+ @hshlike.send(method_to_test, :a).should == true
373
+ @hshlike.send(method_to_test, 'a').should == true
374
+ @hshlike.send(method_to_test, :c).should == true
375
+ @hshlike.send(method_to_test, 'c').should == true
376
+ end
377
+ end
378
+
379
+ shared_examples_for :hashlike_has_key_on_complex_keys do |method_to_test|
380
+ it 'treats symbol and string keys as equivalent' do
381
+ @hshlike.send(method_to_test, :a).should == true
382
+ @hshlike.send(method_to_test, 'a').should == false
383
+ @hshlike.send(method_to_test, :c).should == true
384
+ @hshlike.send(method_to_test, 'c').should == false
385
+ @hshlike.send(method_to_test, nil).should == false
386
+ @hshlike[nil] = 1
387
+ @hshlike.send(method_to_test, nil).should == true
388
+ obj = Object.new
389
+ @hshlike.send(method_to_test, obj).should == false
390
+ @hshlike[obj] = 1
391
+ @hshlike.send(method_to_test, obj).should == true
392
+ end
393
+ end
394
+
395
+ shared_examples_for :hashlike_has_value? do |method_to_test|
396
+ it 'returns true if the given value is present' do
397
+ @hshlike.send(method_to_test, 100).should == true
398
+ end
399
+ it 'returns false if the value is absent' do
400
+ @hshlike.send(method_to_test, :i_am_missing).should == false
401
+ end
402
+ it 'returns true if the given value is present-but-nil or present-but-false' do
403
+ @hshlike[:false_val].should == false
404
+ @hshlike[:nil_val].should == nil
405
+ @hshlike.send(method_to_test, false).should == true
406
+ @hshlike.send(method_to_test, nil).should == true
407
+ end
408
+ it 'something something convert_key'
409
+ end
410
+
411
+ shared_examples_for :hashlike_has_value_on_complex_keys do |method_to_test|
412
+ it 'is true even if key or value is nil, empty or false values' do
413
+ @hshlike[nil] = :key_is_nil
414
+ @hshlike[false] = :key_is_false
415
+ @hshlike.should be_hash_eql({:a=>100, :b=>200, :c=>300, :nil_val=>nil, :false_val=>false, nil=>:key_is_nil, false=>:key_is_false})
416
+ @hshlike.should evaluate_to_true(method_to_test, nil)
417
+ @hshlike.should evaluate_to_true(method_to_test, false)
418
+ @hshlike.should evaluate_to_true(method_to_test, :key_is_nil)
419
+ @hshlike.should evaluate_to_true(method_to_test, :key_is_false)
420
+ end
421
+ end
422
+
423
+ shared_examples_for :hashlike_fetch do
424
+ it 'returns a value from the hashlike for the given key' do
425
+ @hshlike.fetch(:a).should == 100
426
+ @hshlike.fetch(:c).should == 300
427
+ @hshlike.fetch(:nil_val).should == nil
428
+ end
429
+ describe 'on a missing key' do
430
+ it 'with no other arguments, raises a +KeyError+ exception' do
431
+ lambda{ @hshlike.fetch(:new_key) }.should raise_error(KeyError, 'key not found: :new_key')
432
+ end
433
+ it 'if block given, runs the block with the given key and returns its value' do
434
+ set_in_block = nil
435
+ ret_val = @hshlike.fetch(:new_key){|k| set_in_block = "got: #{k}" ; "hello!" }
436
+ ret_val.should == "hello!"
437
+ set_in_block.should == "got: new_key"
438
+ end
439
+ it 'if default given, returns the default arg' do
440
+ ret_val = @hshlike.fetch(:new_key, :returned_as_default)
441
+ ret_val.should == :returned_as_default
442
+ end
443
+ it 'if block and default are both given, issues a warning and runs the block' do
444
+ set_in_block = nil
445
+ ret_val = @hshlike.fetch(:new_key, :spurious_default){|k| set_in_block = "got: #{k}" ; "hello!" }
446
+ ret_val.should == "hello!"
447
+ set_in_block.should == "got: new_key"
448
+ end
449
+ end
450
+ it 'something something convert_key'
451
+ end
452
+
453
+ shared_examples_for :hashlike_key do
454
+ it 'searches for an entry with the given val, returning the corresponding key; if not found, returns nil' do
455
+ @hshlike.key(100).should == :a
456
+ @hshlike.key(300).should == :c
457
+ @hshlike.key(nil).should == :nil_val
458
+ @hshlike.key(:i_am_missing).should be_nil
459
+ end
460
+ it 'returns the first matching key/value pair' do
461
+ @hshlike[:a] = 999
462
+ @hshlike[:new_key] = 999
463
+ if (RUBY_VERSION >= '1.9')
464
+ @hshlike.key(999).should == :a
465
+ end
466
+ end
467
+ end
468
+
469
+ shared_examples_for :hashlike_assoc do
470
+ it 'searches for an entry with the given key, returning the corresponding key/value pair' do
471
+ @hshlike.assoc(:a).should == [:a, 100]
472
+ @hshlike.assoc(:nil_val).should == [:nil_val, nil]
473
+ end
474
+ it 'returns nil if missing' do
475
+ @hshlike.assoc(:i_am_missing).should be_nil
476
+ end
477
+ it 'something something convert_key'
478
+ end
479
+
480
+ shared_examples_for :hashlike_rassoc do
481
+ it 'searches for an entry with the given val, returning the corresponding key/value pair' do
482
+ @hshlike.rassoc(100).should == [:a, 100]
483
+ @hshlike.rassoc(300).should == [:c, 300]
484
+ @hshlike.rassoc(nil).should == [:nil_val, nil]
485
+ end
486
+ it 'returns nil if missing' do
487
+ @hshlike.rassoc(:i_am_missing).should be_nil
488
+ end
489
+ end
490
+
491
+ shared_examples_for :hashlike_empty? do
492
+ it 'returns true if the hashlike contains no key-value pairs, false otherwise' do
493
+ @hshlike.empty?.should == false
494
+ @empty_hshlike.empty?.should == true
495
+ @empty_hshlike[:a] = nil
496
+ @empty_hshlike.empty?.should == false
497
+ end
498
+ end
499
+
500
+ # ===========================================================================
501
+ #
502
+ # Update, merge!, merge
503
+
504
+ shared_examples_for :merging_method do |method_to_test|
505
+ it 'adds the contents of another hash' do
506
+ ret_val = @hshlike.send(method_to_test, {:a => [], :new_key => "zzz" })
507
+ ret_val.should be_hash_eql({:a=>[], :b=>200, :c=>300, :nil_val=>nil, :false_val=>false, :new_key => "zzz"})
508
+ end
509
+ it 'adds the contents of a Struct' do
510
+ bob_klass = Struct.new(:a, :b, :nil_val, :new_key)
511
+ bob = bob_klass.new("aaa", 200, "here", "zzz")
512
+ ret_val = @hshlike.send(method_to_test, bob)
513
+ ret_val.should be_hash_eql({ :a => "aaa", :b => 200, :c => 300, :nil_val => "here", :false_val => false, :new_key => "zzz" })
514
+ bob.values.should == ["aaa", 200, "here", "zzz"]
515
+ end
516
+ it 'adds the contents of another Hashlike' do
517
+ bob = InternalHash.new.merge({ :a => "aaa", :b => 200, :nil_val => "here", :new_key => "zzz" })
518
+ ret_val = @hshlike.send(method_to_test, bob)
519
+ ret_val.should be_hash_eql({ :a => "aaa", :b => 200, :c => 300, :nil_val => "here", :false_val => false, :new_key => "zzz" })
520
+ bob.should be_hash_eql({ :a => "aaa", :b => 200, :nil_val => "here", :new_key => "zzz" })
521
+ end
522
+ it 'adds the contents of anything that respond_to?(:each_pair)' do
523
+ obj = Object.new
524
+ def obj.each_pair
525
+ [[:a, "aaa"], [:b, 200], [:nil_val, "here"], [:new_key, "zzz"]].each{|k,v| yield(k,v) }
526
+ end
527
+ ret_val = @hshlike.send(method_to_test, obj)
528
+ ret_val.should be_hash_eql({ :a => "aaa", :b => 200, :c => 300, :nil_val => "here", :false_val => false, :new_key => "zzz" })
529
+ end
530
+ describe 'with no block' do
531
+ it 'overwrites entries in this hash with those from the other hash' do
532
+ ret_val = @hshlike.send(method_to_test, {:a => "aaa", :b => 200, :nil_val => "here", :new_key => "zzz" })
533
+ ret_val.should be_hash_eql({ :a => "aaa", :b => 200, :c => 300, :nil_val => "here", :false_val => false, :new_key => "zzz" })
534
+ end
535
+ end
536
+ it 'raises a type error unless given hash responds to each_pair' do
537
+ obj = Object.new
538
+ lambda{ @hshlike.send(method_to_test, obj) }.should raise_error(TypeError, "can't convert Object into Hash")
539
+ end
540
+ it 'something something convert_key'
541
+ end
542
+
543
+ shared_examples_for :merging_method_with_normal_keys do |method_to_test|
544
+ describe 'with a block' do
545
+ it 'sets the value for colliding keys by evaluating the block' do
546
+ ret_val = @hshlike.send(method_to_test, {:a => "aaa", :nil_val => "here", :new_key => "zzz" }) do |key, other_val, hsh_val|
547
+ "key: '#{key.inspect}', other_val: '#{other_val.inspect}', hsh_val: '#{hsh_val.inspect}'"
548
+ end
549
+ ret_val.should be_hash_eql({
550
+ :a => %Q{key: ':a', other_val: '"aaa"', hsh_val: '100'},
551
+ :b => 200,
552
+ :c => 300,
553
+ :nil_val => %Q{key: ':nil_val', other_val: '"here"', hsh_val: 'nil'},
554
+ :false_val => false,
555
+ :new_key => "zzz",
556
+ })
557
+ end
558
+ it 'passes params |key, current val, other hash val|' do
559
+ seen_args = []
560
+ ret_val = @hshlike.send(method_to_test, {:a => "aaa", :nil_val => "here", :new_key => "zzz" }) do |key, other_val, hsh_val|
561
+ seen_args << [key, other_val, hsh_val]
562
+ 3
563
+ end
564
+ ret_val.should be_hash_eql({ :a => 3, :b => 200, :c => 300, :nil_val => 3, :false_val => false, :new_key => "zzz" })
565
+ seen_args.should be_array_eql([ [:a, "aaa", 100], [:nil_val, "here", nil] ])
566
+ end
567
+ it 'calls the block even if colliding keys have same value' do
568
+ seen_args = []
569
+ ret_val = @hshlike.send(method_to_test, {:a => "aaa", :b => 200, :new_key => "zzz" }) do |key, other_val, hsh_val|
570
+ seen_args << [key, other_val, hsh_val]
571
+ 3
572
+ end
573
+ ret_val.should be_hash_eql({ :a => 3, :b => 3, :c => 300, :nil_val => nil, :false_val => false, :new_key => "zzz" })
574
+ seen_args.should be_array_eql([ [:a, "aaa", 100], [:b, 200, 200] ])
575
+ end
576
+ end
577
+ end
578
+
579
+ shared_examples_for :merging_method_with_struct_keys do |method_to_test|
580
+ describe 'with a block' do
581
+ it 'sets the value for colliding keys by evaluating the block' do
582
+ ret_val = @hshlike.send(method_to_test, {:a => "aaa", :nil_val => "here", :new_key => "zzz" }) do |key, other_val, hsh_val|
583
+ "key: '#{key.inspect}', other_val: '#{other_val.inspect}', hsh_val: '#{hsh_val.inspect}'"
584
+ end
585
+ ret_val.should be_hash_eql({
586
+ :a => %Q{key: ':a', other_val: '"aaa"', hsh_val: '100'},
587
+ :b => 200,
588
+ :c => 300,
589
+ :nil_val => %Q{key: ':nil_val', other_val: '"here"', hsh_val: 'nil'},
590
+ :false_val => false,
591
+ :new_key => %Q{key: ':new_key', other_val: '"zzz"', hsh_val: 'nil'},
592
+ })
593
+ end
594
+ it 'passes params |key, current val, other hash val|' do
595
+ seen_args = []
596
+ ret_val = @hshlike.send(method_to_test, {:a => "aaa", :nil_val => "here", :new_key => "zzz" }) do |key, other_val, hsh_val|
597
+ seen_args << [key, other_val, hsh_val]
598
+ 3
599
+ end
600
+ ret_val.should be_hash_eql({ :a => 3, :b => 200, :c => 300, :nil_val => 3, :false_val => false, :new_key => 3 })
601
+ seen_args.should be_array_eql([ [:a, "aaa", 100], [:nil_val, "here", nil], [:new_key, "zzz", nil] ])
602
+ end
603
+ it 'calls the block even if colliding keys have same value' do
604
+ seen_args = []
605
+ ret_val = @hshlike.send(method_to_test, {:a => "aaa", :b => 200, :new_key => "zzz" }) do |key, other_val, hsh_val|
606
+ seen_args << [key, other_val, hsh_val]
607
+ 3
608
+ end
609
+ ret_val.should be_hash_eql({ :a => 3, :b => 3, :c => 300, :nil_val => nil, :false_val => false, :new_key => 3 })
610
+ seen_args.should be_array_eql([ [:a, "aaa", 100], [:b, 200, 200], [:new_key, "zzz", nil] ])
611
+ end
612
+ end
613
+ end
614
+
615
+ shared_examples_for :merging_method_inplace do |method_to_test|
616
+ it 'updates in-place, returning self' do
617
+ ret_val = @hshlike.send(method_to_test, {:a => "aaa", :b => 200, :nil_val => "here", :new_key => "zzz" })
618
+ ret_val.should equal(@hshlike)
619
+ @hshlike.should be_hash_eql({:a=>"aaa", :b=>200, :c=>300, :nil_val=>"here", :false_val=>false, :new_key=>"zzz"})
620
+ end
621
+ end
622
+
623
+ shared_examples_for :merging_method_returning_new do |method_to_test|
624
+ it 'does not alter state, returning a new object' do
625
+ ret_val = @hshlike.send(method_to_test, {:a => "aaa", :b => 200, :nil_val => "here", :new_key => "zzz" })
626
+ ret_val.should_not equal(@hshlike)
627
+ @hshlike.should be_hash_eql({:a=>100, :b=>200, :c=>300, :nil_val=>nil, :false_val=>false })
628
+ ret_val.should be_hash_eql({:a=>"aaa", :b=>200, :c=>300, :nil_val=>"here", :false_val=>false, :new_key=>"zzz"})
629
+ end
630
+ it 'returns an object of same class' do
631
+ ret_val = @hshlike_subklass_inst.send(method_to_test, {:a => "aaa", :b => 200, :nil_val => "here", :new_key => "zzz" })
632
+ ret_val.should be_a(@hshlike_subklass)
633
+ end
634
+ end
635
+
636
+ # ===========================================================================
637
+ #
638
+ # Filters
639
+
640
+ shared_examples_for :hashlike_filter do |method_to_test|
641
+ it 'passes every key-value pair to block' do
642
+ seen_args = []
643
+ ret_val = @hshlike.send(method_to_test){|key,val| seen_args << [key, val] ; val && (val.to_i > 150) }
644
+ #
645
+ seen_args.should be_array_eql([[:a, 100], [:b, 200], [:c, 300], [:nil_val, nil], [:false_val, false]])
646
+ end
647
+ it 'adapts to the arity of the block' do
648
+ seen_args = []
649
+ ret_val = @hshlike.send(method_to_test){|arg| seen_args << [arg] ; @hshlike[arg] && (@hshlike[arg].to_i > 150) }
650
+ #
651
+ seen_args.should be_array_eql([[:a], [:b], [:c], [:nil_val], [:false_val]])
652
+ end
653
+ describe 'with no block' do
654
+ it('returns an enumerator'){ @hshlike.send(method_to_test).should enumerate_method(@hshlike, method_to_test) }
655
+ end
656
+ end
657
+
658
+ shared_examples_for :rejection_filter do |method_to_test|
659
+ it 'deletes every key-value pair for which the block evaluates truthy' do
660
+ ret_val = @hshlike.send(method_to_test){|key,val| val && (val.to_i > 150) }
661
+ ret_val.should be_hash_eql({ :a => 100, :nil_val => nil, :false_val => false })
662
+ @hshlike.should be_hash_eql({ :a => 100, :nil_val => nil, :false_val => false })
663
+ #
664
+ ret_val = @hshlike.send(method_to_test){|key,val| 1 }
665
+ ret_val.should be_empty
666
+ end
667
+ end
668
+
669
+ shared_examples_for :selection_filter do |method_to_test|
670
+ it 'deletes every key-value pair for which the block evaluates truthy' do
671
+ ret_val = @hshlike.keep_if{|key,val| val }
672
+ ret_val.should be_hash_eql({ :a => 100, :b => 200, :c => 300 })
673
+ #
674
+ ret_val = @hshlike.keep_if{|key,val| val && (val.to_i > 150) }
675
+ ret_val.should be_hash_eql({ :b => 200, :c => 300 })
676
+ #
677
+ ret_val = @hshlike.keep_if{|key,val| false }
678
+ ret_val.should be_empty
679
+ end
680
+ end
681
+
682
+ shared_examples_for :filter_modifies_self_returns_nil_if_unchanged do |method_to_test, force_unchanged|
683
+ it 'modifies in-place and returns self if changes were made' do
684
+ ret_val = @hshlike.send(method_to_test){|key,val| val && (val.to_i > 150) }
685
+ ret_val.should equal(@hshlike)
686
+ end
687
+ it 'modifies in-place and returns self if changes were made (arity 1)' do
688
+ ret_val = @hshlike.send(method_to_test){|key| @hshlike[key] && (@hshlike[key].to_i > 150) }
689
+ ret_val.should equal(@hshlike)
690
+ end
691
+ it 'returns nil if unchanged' do
692
+ ret_val = @hshlike.send(method_to_test){|key,val| force_unchanged }
693
+ #
694
+ ret_val.should be_nil
695
+ @hshlike.should be_hash_eql(HashlikeHelper::BASE_HSH)
696
+ end
697
+ end
698
+
699
+ shared_examples_for :filter_modifies_self_returns_self do |method_to_test, force_unchanged|
700
+ it 'modifies in-place and returns self if changes were made' do
701
+ ret_val = @hshlike.send(method_to_test){|key,val| val && (val.to_i > 150) }
702
+ ret_val.should equal(@hshlike)
703
+ end
704
+ it 'modifies in-place and returns self if changes were made (arity 1)' do
705
+ ret_val = @hshlike.send(method_to_test){|key| @hshlike[key] && (@hshlike[key].to_i > 150) }
706
+ ret_val.should equal(@hshlike)
707
+ end
708
+ it 'returns self if unchanged' do
709
+ ret_val = @hshlike.send(method_to_test){|key,val| force_unchanged }
710
+ #
711
+ ret_val.should equal(@hshlike)
712
+ @hshlike.should be_hash_eql(HashlikeHelper::BASE_HSH)
713
+ end
714
+ end
715
+
716
+ shared_examples_for :filter_does_not_modify_self_returns_same_class do |method_to_test, force_unchanged|
717
+ it 'modifies in-place and returns self' do
718
+ ret_val = @hshlike.send(method_to_test){|key,val| val && (val.to_i > 150) }
719
+ ret_val.should_not be_hash_eql(@hshlike)
720
+ ret_val.should_not equal(@hshlike)
721
+ @hshlike.should be_hash_eql(HashlikeHelper::BASE_HSH)
722
+ end
723
+ it 'is == if unchanged' do
724
+ ret_val = @hshlike.send(method_to_test){|key,val| force_unchanged }
725
+ #
726
+ ret_val.should_not equal(@hshlike)
727
+ ret_val.should be_hash_eql(@hshlike)
728
+ @hshlike.should be_hash_eql(HashlikeHelper::BASE_HSH)
729
+ end
730
+ it 'returns same class as caller' do
731
+ ret_val = @hshlike_subklass_inst.send(method_to_test){|key,val| val && (val.to_i > 150) }
732
+ ret_val.should be_a(@hshlike_subklass)
733
+ ret_val.should be_a(@hshlike.class)
734
+ ret_val.class.should_not == @hshlike.class
735
+ end
736
+ end
737
+
738
+ shared_examples_for :hashlike_clear do
739
+ it 'removes all key/value pairs' do
740
+ ret_val = @hshlike.clear
741
+ ret_val.should be_hash_eql(@empty_hshlike)
742
+ ret_val.should be_empty
743
+ @hshlike.should be_empty
744
+ end
745
+ end
746
+
747
+ # ===========================================================================
748
+ #
749
+ # Conversion
750
+
751
+ shared_examples_for :hashlike_to_hash do
752
+ it 'returns a new Hash with each key set to its associated value' do
753
+ ret_val = @hshlike.to_hash
754
+ ret_val.should be_an_instance_of(Hash)
755
+ ret_val.should == HashlikeHelper::BASE_HSH
756
+ end
757
+ end
758
+
759
+ shared_examples_for :hashlike_invert do
760
+ if (RUBY_VERSION >= '1.9')
761
+ it 'returns a new Hash using the values as keys, and the keys as values' do
762
+ ret_val = @hshlike.invert
763
+ ret_val.should == { 100 => :a, 200 => :b, 300 => :c, nil => :nil_val, false => :false_val }
764
+ end
765
+ it 'with duplicate values, the result will contain only one of them as a key' do
766
+ @hshlike[:a] = 999
767
+ @hshlike[:new_key] = 999
768
+ @hshlike.invert.should == { 999 => :new_key, 200 => :b, 300 => :c, nil => :nil_val, false => :false_val }
769
+ end
770
+ it 'returns a Hash, not a self.class' do
771
+ ret_val = @hshlike.invert
772
+ ret_val.should be_an_instance_of(Hash)
773
+ end
774
+ else
775
+ it 'does not have tests for invert on Ruby < 1.9'
776
+ end
777
+ end
778
+
779
+ shared_examples_for :hashlike_flatten do
780
+ if (RUBY_VERSION >= '1.9')
781
+ it 'with no arg returns a one-dimensional flattening' do
782
+ ret_as_hash = HashlikeHelper::BASE_HSH_WITH_ARRAY_VALS.flatten
783
+ ret_val = @hshlike_with_array_vals.flatten
784
+ ret_val.should == ret_as_hash
785
+ ret_val.should == [ :a, [100, 111], :b, 200, :c, [1, [2, 3, [4, 5, 6]]], ]
786
+ @hshlike_with_array_vals.should be_hash_eql(HashlikeHelper::BASE_HSH_WITH_ARRAY_VALS)
787
+ end
788
+ it 'with no arg is same as level = 1' do
789
+ @hshlike_with_array_vals.flatten(1).should == @hshlike_with_array_vals.flatten
790
+ end
791
+ it 'with level == nil, returns a complete flattening' do
792
+ ret_as_hash = HashlikeHelper::BASE_HSH_WITH_ARRAY_VALS.flatten(nil)
793
+ ret_val = @hshlike_with_array_vals.flatten(nil)
794
+ ret_val.should == ret_as_hash
795
+ ret_val.should == [ :a, 100, 111, :b, 200, :c, 1, 2, 3, 4, 5, 6, ]
796
+ end
797
+ it 'with an arg, flattens to that level (0)' do
798
+ ret_as_hash = HashlikeHelper::BASE_HSH_WITH_ARRAY_VALS.flatten(0)
799
+ ret_val = @hshlike_with_array_vals.flatten(0)
800
+ ret_val.should == ret_as_hash
801
+ ret_val.should == [ [:a, [100, 111]], [:b, 200], [:c, [1, [2, 3, [4, 5, 6]]]], ]
802
+ end
803
+ it 'with an arg, flattens to that level (3)' do
804
+ ret_as_hash = HashlikeHelper::BASE_HSH_WITH_ARRAY_VALS.flatten(3)
805
+ ret_val = @hshlike_with_array_vals.flatten(3)
806
+ ret_val.should == ret_as_hash
807
+ ret_val.should == [ :a, 100, 111, :b, 200, :c, 1, 2, 3, [4, 5, 6],]
808
+ end
809
+ it 'with an arg, flattens to that level (4)' do
810
+ ret_as_hash = HashlikeHelper::BASE_HSH_WITH_ARRAY_VALS.flatten(4)
811
+ ret_val = @hshlike_with_array_vals.flatten(4)
812
+ ret_val.should == ret_as_hash
813
+ ret_val.should == [ :a, 100, 111, :b, 200, :c, 1, 2, 3, 4, 5, 6, ]
814
+ ret_val.should == @hshlike_with_array_vals.flatten(999)
815
+ end
816
+ else
817
+ it 'does not have tests for invert on Ruby < 1.9'
818
+ end
819
+ end
820
+
821
+
822
+
823
+ end
824
+ end ; HASHLIKE_BEHAVIOR_SPEC = true