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.
- data/CHANGELOG.textile +6 -0
- data/README.textile +34 -11
- data/VERSION +1 -1
- data/gorillib.gemspec +63 -5
- data/lib/gorillib/enumerable/sum.rb +2 -2
- data/lib/gorillib/hash/compact.rb +2 -29
- data/lib/gorillib/hash/deep_compact.rb +2 -12
- data/lib/gorillib/hash/deep_dup.rb +4 -0
- data/lib/gorillib/hash/deep_merge.rb +2 -14
- data/lib/gorillib/hash/indifferent_access.rb +207 -0
- data/lib/gorillib/hash/keys.rb +2 -40
- data/lib/gorillib/hash/reverse_merge.rb +2 -24
- data/lib/gorillib/hash/slice.rb +2 -51
- data/lib/gorillib/hash/tree_merge.rb +4 -0
- data/lib/gorillib/hashlike.rb +824 -0
- data/lib/gorillib/hashlike/compact.rb +60 -0
- data/lib/gorillib/hashlike/deep_compact.rb +18 -0
- data/lib/gorillib/hashlike/deep_dup.rb +15 -0
- data/lib/gorillib/hashlike/deep_merge.rb +20 -0
- data/lib/gorillib/hashlike/hashlike_via_accessors.rb +169 -0
- data/lib/gorillib/hashlike/keys.rb +59 -0
- data/lib/gorillib/hashlike/reverse_merge.rb +31 -0
- data/lib/gorillib/hashlike/slice.rb +67 -0
- data/lib/gorillib/hashlike/tree_merge.rb +76 -0
- data/lib/gorillib/metaprogramming/mattr_accessor.rb +1 -1
- data/lib/gorillib/receiver.rb +315 -0
- data/lib/gorillib/receiver/active_model_shim.rb +19 -0
- data/lib/gorillib/receiver/acts_as_hash.rb +191 -0
- data/lib/gorillib/receiver/acts_as_loadable.rb +42 -0
- data/lib/gorillib/receiver/tree_diff.rb +74 -0
- data/lib/gorillib/receiver/validations.rb +30 -0
- data/lib/gorillib/struct/acts_as_hash.rb +108 -0
- data/lib/gorillib/struct/hashlike_iteration.rb +0 -0
- data/notes/fancy_hashes_and_receivers.textile +120 -0
- data/notes/hash_rdocs.textile +97 -0
- data/spec/hash/deep_merge_spec.rb +0 -2
- data/spec/hash/indifferent_access_spec.rb +391 -0
- data/spec/hash/slice_spec.rb +35 -12
- data/spec/hashlike/behave_same_as_hash_spec.rb +105 -0
- data/spec/hashlike/hashlike_behavior_spec.rb +824 -0
- data/spec/hashlike/hashlike_via_accessors_fuzzing_spec.rb +37 -0
- data/spec/hashlike/hashlike_via_accessors_spec.rb +262 -0
- data/spec/hashlike_spec.rb +302 -0
- data/spec/metaprogramming/aliasing_spec.rb +3 -0
- data/spec/metaprogramming/cattr_accessor_spec.rb +2 -0
- data/spec/metaprogramming/class_attribute_spec.rb +2 -0
- data/spec/metaprogramming/delegation_spec.rb +2 -0
- data/spec/metaprogramming/mattr_accessor_spec.rb +2 -0
- data/spec/metaprogramming/singleton_class_spec.rb +3 -0
- data/spec/receiver/acts_as_hash_spec.rb +286 -0
- data/spec/receiver_spec.rb +478 -0
- data/spec/spec_helper.rb +11 -6
- data/spec/string/truncate_spec.rb +1 -0
- data/spec/struct/acts_as_hash_fuzz_spec.rb +67 -0
- data/spec/struct/acts_as_hash_spec.rb +426 -0
- data/spec/support/hashlike_fuzzing_helper.rb +127 -0
- data/spec/support/hashlike_helper.rb +75 -0
- data/spec/support/hashlike_struct_helper.rb +37 -0
- data/spec/support/hashlike_via_delegation.rb +30 -0
- data/spec/support/matchers/be_array_eql.rb +12 -0
- data/spec/support/matchers/be_hash_eql.rb +14 -0
- data/spec/support/matchers/enumerate_method.rb +10 -0
- data/spec/support/matchers/evaluate_to_true.rb +5 -0
- 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
|