summaryrefslogtreecommitdiff
path: root/spec/ruby/core/hash/shared/each.rb
blob: b2483c8116ced27c2637c46a0d1606a1c811ce1b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
describe :hash_each, shared: true do

  # 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 }.send(@method) { |*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.send(@method) { |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.send(@method) { |k,| ary << k }
    ary.sort.should == ["a", "b", "c"]
  end

  ruby_version_is ""..."3.0" do
    it "yields 2 values and not an Array of 2 elements when given a callable of arity 2" do
      obj = Object.new
      def obj.foo(key, value)
        ScratchPad << key << value
      end

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

      ScratchPad.record([])
      { "a" => 1 }.send(@method, &-> key, value { ScratchPad << key << value })
      ScratchPad.recorded.should == ["a", 1]
    end
  end

  ruby_version_is "3.0" do
    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 }.send(@method, &obj.method(:foo))
      }.should raise_error(ArgumentError)

      -> {
        { "a" => 1 }.send(@method, &-> key, value { })
      }.should raise_error(ArgumentError)
    end
  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 }.send(@method, &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 }.send(@method, &obj.method(:foo))
    }.should raise_error(ArgumentError)
  end

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

    h.send(@method) 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