gorillib 0.0.8 → 0.1.0

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