Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ to avoid duplication of specs, we have shared specs that are re-used in other sp
bit tricky however, so let's go over it.

Commonly, if a shared spec is only reused within its own module, the shared spec will live within a
shared directory inside that module's directory. For example, the `core/hash/shared/key.rb` spec is
shared directory inside that module's directory. For example, the `core/hash/shared/iteration.rb` spec is
only used by `Hash` specs, and so it lives inside `core/hash/shared/`.

When a shared spec is used across multiple modules or classes, it lives within the `shared/` directory.
Expand All @@ -243,25 +243,25 @@ variables from the implementor spec: `@method` and `@object`, which the implemen
Here's an example of a snippet of a shared spec and two specs which integrates it:

```ruby
# core/hash/shared/key.rb
describe :hash_key_p, shared: true do
it "returns true if the key's matching value was false" do
{ xyz: false }.send(@method, :xyz).should == true
# core/hash/shared/iteration.rb
describe :hash_iteration_no_block, shared: true do
it "returns an Enumerator if called on a non-empty hash without a block" do
{}.send(@method).should.instance_of?(Enumerator)
end
end

# core/hash/key_spec.rb
describe "Hash#key?" do
it_behaves_like :hash_key_p, :key?
# core/hash/select_spec.rb
describe "Hash#select" do
it_behaves_like :hash_iteration_no_block, :select
end

# core/hash/include_spec.rb
describe "Hash#include?" do
it_behaves_like :hash_key_p, :include?
# core/hash/reject_spec.rb
describe "Hash#reject?" do
it_behaves_like :hash_iteration_no_block, :reject
end
```

In the example, the first `describe` defines the shared spec `:hash_key_p`, which defines a spec that
In the example, the first `describe` defines the shared spec `:hash_iteration_no_block`, which defines a spec that
calls the `@method` method with an expectation. In the implementor spec, we use `it_behaves_like` to
integrate the shared spec. `it_behaves_like` takes 3 parameters: the key of the shared spec, a method,
and an object. These last two parameters are accessible via `@method` and `@object` in the shared spec.
Expand Down
106 changes: 103 additions & 3 deletions core/hash/each_pair_spec.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,111 @@
require_relative '../../spec_helper'
require_relative 'fixtures/classes'
require_relative 'shared/iteration'
require_relative 'shared/each'
require_relative '../enumerable/shared/enumeratorized'

describe "Hash#each_pair" do
it_behaves_like :hash_each, :each_pair
it_behaves_like :hash_iteration_no_block, :each_pair
it_behaves_like :enumeratorized_with_origin_size, :each_pair, { 1 => 2, 3 => 4, 5 => 6 }

# This is inconsistent with below, MRI checks the block arity in rb_hash_each_pair()
it "yields a [[key, value]] Array for each pair to a block expecting |*args|" do
all_args = []
{ 1 => 2, 3 => 4 }.each_pair { |*args| all_args << args }
all_args.sort.should == [[[1, 2]], [[3, 4]]]
end

it "yields the key and value of each pair to a block expecting |key, value|" do
r = {}
h = { a: 1, b: 2, c: 3, d: 5 }
h.each_pair { |k,v| r[k.to_s] = v.to_s }.should.equal?(h)
r.should == { "a" => "1", "b" => "2", "c" => "3", "d" => "5" }
end

it "yields the key only to a block expecting |key,|" do
ary = []
h = { "a" => 1, "b" => 2, "c" => 3 }
h.each_pair { |k,| ary << k }
ary.sort.should == ["a", "b", "c"]
end

it "always yields an Array of 2 elements, even when given a callable of arity 2" do
obj = Object.new
def obj.foo(key, value)
end

-> {
{ "a" => 1 }.each_pair(&obj.method(:foo))
}.should.raise(ArgumentError)

-> {
{ "a" => 1 }.each_pair(&-> key, value { })
}.should.raise(ArgumentError)
end

it "yields an Array of 2 elements when given a callable of arity 1" do
obj = Object.new
def obj.foo(key_value)
ScratchPad << key_value
end

ScratchPad.record([])
{ "a" => 1 }.each_pair(&obj.method(:foo))
ScratchPad.recorded.should == [["a", 1]]
end

it "raises an error for a Hash when an arity enforcing callable of arity >2 is passed in" do
obj = Object.new
def obj.foo(key, value, extra)
end

-> {
{ "a" => 1 }.each_pair(&obj.method(:foo))
}.should.raise(ArgumentError)
end

it "uses the same order as keys() and values()" do
h = { a: 1, b: 2, c: 3, d: 5 }
keys = []
values = []

h.each_pair do |k, v|
keys << k
values << v
end

keys.should == h.keys
values.should == h.values
end

# Confirming the argument-splatting works from child class for both k, v and [k, v]
it "properly expands (or not) child class's 'each'-yielded args" do
cls1 = Class.new(Hash) do
attr_accessor :k_v
def each
super do |k, v|
@k_v = [k, v]
yield k, v
end
end
end

cls2 = Class.new(Hash) do
attr_accessor :k_v
def each
super do |k, v|
@k_v = [k, v]
yield([k, v])
end
end
end

obj1 = cls1.new
obj1['a'] = 'b'
obj1.map {|k, v| [k, v]}.should == [['a', 'b']]
obj1.k_v.should == ['a', 'b']

obj2 = cls2.new
obj2['a'] = 'b'
obj2.map {|k, v| [k, v]}.should == [['a', 'b']]
obj2.k_v.should == ['a', 'b']
end
end
10 changes: 3 additions & 7 deletions core/hash/each_spec.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
require_relative '../../spec_helper'
require_relative 'fixtures/classes'
require_relative 'shared/iteration'
require_relative 'shared/each'
require_relative '../enumerable/shared/enumeratorized'

describe "Hash#each" do
it_behaves_like :hash_each, :each
it_behaves_like :hash_iteration_no_block, :each
it_behaves_like :enumeratorized_with_origin_size, :each, { 1 => 2, 3 => 4, 5 => 6 }
it "is an alias of Hash#each_pair" do
Hash.instance_method(:each).should == Hash.instance_method(:each_pair)
end
end
118 changes: 116 additions & 2 deletions core/hash/element_set_spec.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,121 @@
require_relative '../../spec_helper'
require_relative 'fixtures/classes'
require_relative 'shared/store'

describe "Hash#[]=" do
it_behaves_like :hash_store, :[]=
it "associates the key with the value and return the value" do
h = { a: 1 }
h[:b] = 2
h.should == { b:2, a:1 }
end

it "returns the assigned value" do
h = {}
h.send(:[]=, 1, 2).should == 2
end

it "duplicates string keys using dup semantics" do
# dup doesn't copy singleton methods
key = +"foo"
def key.reverse() "bar" end
h = {}
h[key] = 0
h.keys[0].reverse.should == "oof"
end

it "stores unequal keys that hash to the same value" do
h = {}
k1 = ["x"]
k2 = ["y"]
# So they end up in the same bucket
k1.should_receive(:hash).and_return(0)
k2.should_receive(:hash).and_return(0)

h[k1] = 1
h[k2] = 2
h.size.should == 2
end

it "accepts keys with private #hash method" do
key = HashSpecs::KeyWithPrivateHash.new
h = {}
h[key] = "foo"
h[key].should == "foo"
end

it " accepts keys with an Integer hash" do
o = mock(hash: 1 << 100)
h = {}
h[o] = 1
h[o].should == 1
end

it "duplicates and freezes string keys" do
key = +"foo"
h = {}
h[key] = 0
key << "bar"

h.should == { "foo" => 0 }
h.keys[0].should.frozen?
end

it "doesn't duplicate and freeze already frozen string keys" do
key = "foo".freeze
h = {}
h[key] = 0
h.keys[0].should.equal?(key)
end

it "keeps the existing key in the hash if there is a matching one" do
h = { "a" => 1, "b" => 2, "c" => 3, "d" => 4 }
key1 = HashSpecs::ByValueKey.new(13)
key2 = HashSpecs::ByValueKey.new(13)
h[key1] = 41
key_in_hash = h.keys.last
key_in_hash.should.equal?(key1)
h[key2] = 42
last_key = h.keys.last
last_key.should.equal?(key_in_hash)
last_key.should_not.equal?(key2)
end

it "keeps the existing String key in the hash if there is a matching one" do
h = { "a" => 1, "b" => 2, "c" => 3, "d" => 4 }
key1 = "foo".dup
key2 = "foo".dup
key1.should_not.equal?(key2)
h[key1] = 41
frozen_key = h.keys.last
frozen_key.should_not.equal?(key1)
h[key2] = 42
h.keys.last.should.equal?(frozen_key)
h.keys.last.should_not.equal?(key2)
end

it "raises a FrozenError if called on a frozen instance" do
-> { HashSpecs.frozen_hash[1] = 2 }.should.raise(FrozenError)
end

it "does not raise an exception if changing the value of an existing key during iteration" do
hash = {1 => 2, 3 => 4, 5 => 6}
hash.each { hash[1] = :foo }
hash.should == {1 => :foo, 3 => 4, 5 => 6}
end

it "does not dispatch to hash for Boolean, Integer, Float, String, or Symbol" do
code = <<-EOC
load '#{fixture __FILE__, "name.rb"}'
hash = {}
[true, false, 1, 2.0, "hello", :ok].each do |value|
hash[value] = 42
raise "incorrect value" unless hash[value] == 42
hash[value] = 43
raise "incorrect value" unless hash[value] == 43
end
puts "OK"
puts hash.size
EOC
result = ruby_exe(code, args: "2>&1")
result.should == "OK\n6\n"
end
end
9 changes: 6 additions & 3 deletions core/hash/filter_spec.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
require_relative '../../spec_helper'
require_relative 'shared/select'

describe "Hash#filter" do
it_behaves_like :hash_select, :filter
it "is an alias of Hash#select" do
Hash.instance_method(:filter).should == Hash.instance_method(:select)
end
end

describe "Hash#filter!" do
it_behaves_like :hash_select!, :filter!
it "is an alias of Hash#select!" do
Hash.instance_method(:filter!).should == Hash.instance_method(:select!)
end
end
6 changes: 3 additions & 3 deletions core/hash/has_key_spec.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
require_relative '../../spec_helper'
require_relative 'fixtures/classes'
require_relative 'shared/key'

describe "Hash#has_key?" do
it_behaves_like :hash_key_p, :has_key?
it "is an alias of Hash#include?" do
Hash.instance_method(:has_key?).should == Hash.instance_method(:include?)
end
end
15 changes: 12 additions & 3 deletions core/hash/has_value_spec.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
require_relative '../../spec_helper'
require_relative 'fixtures/classes'
require_relative 'shared/value'

describe "Hash#has_value?" do
it_behaves_like :hash_value_p, :has_value?
it "returns true if the value exists in the hash" do
{ a: :b }.has_value?(:a).should == false
{ 1 => 2 }.has_value?(2).should == true
h = Hash.new(5)
h.has_value?(5).should == false
h = Hash.new { 5 }
h.has_value?(5).should == false
end

it "uses == semantics for comparing values" do
{ 5 => 2.0 }.has_value?(2).should == true
end
end
39 changes: 36 additions & 3 deletions core/hash/include_spec.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,40 @@
require_relative '../../spec_helper'
require_relative 'fixtures/classes'
require_relative 'shared/key'

describe "Hash#include?" do
it_behaves_like :hash_key_p, :include?
it "returns true if argument is a key" do
h = { a: 1, b: 2, c: 3, 4 => 0 }
h.include?(:a).should == true
h.include?(:b).should == true
h.include?(2).should == false
h.include?(4).should == true

not_supported_on :opal do
h.include?('b').should == false
h.include?(4.0).should == false
end
end

it "returns true if the key's matching value was nil" do
{ xyz: nil }.include?(:xyz).should == true
end

it "returns true if the key's matching value was false" do
{ xyz: false }.include?(:xyz).should == true
end

it "returns true if the key is nil" do
{ nil => 'b' }.include?(nil).should == true
{ nil => nil }.include?(nil).should == true
end

it "compares keys with the same #hash value via #eql?" do
x = mock('x')
x.stub!(:hash).and_return(42)

y = mock('y')
y.stub!(:hash).and_return(42)
y.should_receive(:eql?).and_return(false)

{ x => nil }.include?(y).should == false
end
end
Loading
Loading