require "spec"
require "./spec_helper"
require "spec/helpers/iterate"

module SomeInterface; end

private record One do
  include SomeInterface
end

private record Two do
  include SomeInterface
end

private struct InterfaceEnumerable
  include Enumerable(SomeInterface)

  def each(&)
    yield One.new
    yield Two.new
  end
end

private class SpecEnumerable
  include Enumerable(Int32)

  def each(&)
    yield 1
    yield 2
    yield 3
  end
end

private class SpecEmptyEnumerable
  include Enumerable(Int32)

  def each(&block : T -> _)
  end
end

private class SpecCountUpIterator
  include Iterator(Int32)

  def initialize(@size : Int32, @count = 0)
  end

  def next
    (@count += 1) <= @size ? (@count - 1) : stop
  end
end

describe "Enumerable" do
  describe "all? with block" do
    it "returns true" do
      ["ant", "bear", "cat"].all? { |word| word.size >= 3 }.should be_true
    end

    it "returns false" do
      ["ant", "bear", "cat"].all? { |word| word.size >= 4 }.should be_false
    end
  end

  describe "all? without block" do
    it "returns true" do
      [15].all?.should be_true
    end

    it "returns false" do
      [nil, true, 99].all?.should be_false
    end
  end

  describe "all? with pattern" do
    it "returns true" do
      [2, 3, 4].all?(1..5).should be_true
    end

    it "returns false" do
      [2, 3, 4].all?(1..3).should be_false
    end
  end

  describe "any? with block" do
    it "returns true if at least one element fulfills the condition" do
      ["ant", "bear", "cat"].any? { |word| word.size >= 4 }.should be_true
    end

    it "returns false if all elements does not fulfill the condition" do
      ["ant", "bear", "cat"].any? { |word| word.size > 4 }.should be_false
    end
  end

  describe "any? without block" do
    it "returns true if at least one element is truthy" do
      [nil, true, 99].any?.should be_true
    end

    it "returns false if all elements are falsey" do
      [nil, false].any?.should be_false
    end
  end

  describe "any? with pattern" do
    it "returns true" do
      [nil, true, 99].any?(Int32).should be_true
    end

    it "returns false" do
      [nil, false].any?(Int32).should be_false
    end
  end

  describe "compact map" do
    it { Set{1, nil, 2, nil, 3}.compact_map { |x| x.try &.+(1) }.should eq([2, 3, 4]) }
  end

  describe "size without block" do
    it "returns the number of elements in the Enumerable" do
      SpecEnumerable.new.size.should eq 3
    end
  end

  describe "count with block" do
    it "returns the number of the times the item is present" do
      %w(a b c a d A).count("a").should eq 2
    end
  end

  describe "cycle" do
    it "calls forever if we don't break" do
      called = 0
      elements = [] of Int32
      (1..2).cycle do |e|
        elements << e
        called += 1
        break if called == 6
      end
      called.should eq 6
      elements.should eq [1, 2, 1, 2, 1, 2]
    end

    it "calls the block n times given the optional argument" do
      called = 0
      elements = [] of Int32
      (1..2).cycle(3) do |e|
        elements << e
        called += 1
      end
      called.should eq 6
      elements.should eq [1, 2, 1, 2, 1, 2]
    end
  end

  describe "to_a" do
    it "with a block" do
      SpecEnumerable.new.to_a { |e| e*2 }.should eq [2, 4, 6]
    end

    it "without a block" do
      SpecEnumerable.new.to_a.should eq [1, 2, 3]
    end

    it "without a block of an interface type" do
      InterfaceEnumerable.new.to_a.should eq [One.new, Two.new]
    end
  end

  describe "#to_set" do
    context "without block" do
      it "creates a Set from the unique elements of the collection" do
        {1, 1, 2, 3}.to_set.should eq Set{1, 2, 3}
      end
    end

    context "with block" do
      it "creates a Set from running the block against the collection's elements" do
        {1, 2, 3, 4, 5}.to_set { |i| i // 2 }.should eq Set{0, 1, 2}
      end
    end
  end

  describe "chunk" do
    it "works" do
      [1].chunk { true }.to_a.should eq [{true, [1]}]
      [1, 2].chunk { false }.to_a.should eq [{false, [1, 2]}]
      [1, 1, 2, 3, 3].chunk(&.itself).to_a.should eq [{1, [1, 1]}, {2, [2]}, {3, [3, 3]}]
      [1, 1, 2, 3, 3].chunk(&.<=(2)).to_a.should eq [{true, [1, 1, 2]}, {false, [3, 3]}]
      (0..10).chunk(&.//(3)).to_a.should eq [{0, [0, 1, 2]}, {1, [3, 4, 5]}, {2, [6, 7, 8]}, {3, [9, 10]}]
    end

    it "work with class" do
      [1, 1, 2, 3, 3].chunk(&.class).to_a.should eq [{Int32, [1, 1, 2, 3, 3]}]
    end

    it "works with block" do
      res = [] of Tuple(Bool, Array(Int32))
      [1, 2, 3].chunk { |x| x < 3 }.each { |(k, v)| res << {k, v} }
      res.should eq [{true, [1, 2]}, {false, [3]}]
    end

    it "rewind" do
      i = (0..10).chunk(&.//(3))
      i.next.should eq({0, [0, 1, 2]})
      i.next.should eq({1, [3, 4, 5]})
    end

    it "returns elements of the Enumerable in an Array of Tuple, {v, ary}, where 'ary' contains the consecutive elements for which the block returned the value 'v'" do
      result = [1, 2, 3, 2, 3, 2, 1].chunk { |x| x < 3 ? 1 : 0 }.to_a
      result.should eq [{1, [1, 2]}, {0, [3]}, {1, [2]}, {0, [3]}, {1, [2, 1]}]
    end

    it "returns elements for which the block returns Enumerable::Chunk::Alone in separate Arrays" do
      result = [1, 2, 3, 2, 1].chunk { |x| x < 2 ? Enumerable::Chunk::Alone : false }.to_a
      result.should eq [{Enumerable::Chunk::Alone, [1]}, {false, [2, 3, 2]}, {Enumerable::Chunk::Alone, [1]}]
    end

    it "alone all" do
      result = [1, 2].chunk { Enumerable::Chunk::Alone }.to_a
      result.should eq [{Enumerable::Chunk::Alone, [1]}, {Enumerable::Chunk::Alone, [2]}]
    end

    it "does not return elements for which the block returns Enumerable::Chunk::Drop" do
      result = [1, 2, 3, 3, 2, 1].chunk { |x| x == 2 ? Enumerable::Chunk::Drop : 1 }.to_a
      result.should eq [{1, [1]}, {1, [3, 3]}, {1, [1]}]
    end

    it "drop all" do
      result = [1, 2].chunk { Enumerable::Chunk::Drop }.to_a
      result.should be_a(Array(Tuple(NoReturn, Array(Int32))))
      result.size.should eq 0
    end

    it "nil allowed as value" do
      result = [1, 2, 3, 2, 1].chunk { |x| x == 2 ? nil : 1 }.to_a
      result.should eq [{1, [1]}, {nil, [2]}, {1, [3]}, {nil, [2]}, {1, [1]}]
    end

    it "nil 2 case" do
      result = [nil, nil, 1, 1, nil].chunk(&.itself).to_a
      result.should eq [{nil, [nil, nil]}, {1, [1, 1]}, {nil, [nil]}]
    end

    it "reuses true" do
      iter = [1, 1, 2, 3, 3].chunk(reuse: true, &.itself)
      a = iter.next.should be_a(Tuple(Int32, Array(Int32)))
      a.should eq({1, [1, 1]})

      b = iter.next.should be_a(Tuple(Int32, Array(Int32)))
      b.should eq({2, [2]})
      b[1].should be(a[1])

      c = iter.next.should be_a(Tuple(Int32, Array(Int32)))
      c.should eq({3, [3, 3]})
      c[1].should be(a[1])
    end
  end

  describe "chunks" do
    it "works" do
      [1].chunks { true }.should eq [{true, [1]}]
      [1, 2].chunks { false }.should eq [{false, [1, 2]}]
      [1, 1, 2, 3, 3].chunks(&.itself).should eq [{1, [1, 1]}, {2, [2]}, {3, [3, 3]}]
      [1, 1, 2, 3, 3].chunks(&.<=(2)).should eq [{true, [1, 1, 2]}, {false, [3, 3]}]
      (0..10).chunks(&.//(3)).should eq [{0, [0, 1, 2]}, {1, [3, 4, 5]}, {2, [6, 7, 8]}, {3, [9, 10]}]
    end

    it "work with class" do
      [1, 1, 2, 3, 3].chunks(&.class).should eq [{Int32, [1, 1, 2, 3, 3]}]
    end

    it "work with pure enumerable" do
      SpecEnumerable.new.chunks(&.//(2)).should eq [{0, [1]}, {1, [2, 3]}]
    end

    it "returns elements for which the block returns Enumerable::Chunk::Alone in separate Arrays" do
      result = [1, 2, 3, 2, 1].chunks { |x| x < 2 ? Enumerable::Chunk::Alone : false }
      result.should eq [{Enumerable::Chunk::Alone, [1]}, {false, [2, 3, 2]}, {Enumerable::Chunk::Alone, [1]}]
    end

    it "alone all" do
      result = [1, 2].chunks { Enumerable::Chunk::Alone }
      result.should eq [{Enumerable::Chunk::Alone, [1]}, {Enumerable::Chunk::Alone, [2]}]
    end

    it "does not return elements for which the block returns Enumerable::Chunk::Drop" do
      result = [1, 2, 3, 3, 2, 1].chunks { |x| x == 2 ? Enumerable::Chunk::Drop : 1 }
      result.should eq [{1, [1]}, {1, [3, 3]}, {1, [1]}]
    end

    it "drop all" do
      result = [1, 2].chunks { Enumerable::Chunk::Drop }
      result.should be_a(Array(Tuple(NoReturn, Array(Int32))))
      result.size.should eq 0
    end

    it "nil allowed as value" do
      result = [1, 2, 3, 2, 1].chunks { |x| x == 2 ? nil : 1 }
      result.should eq [{1, [1]}, {nil, [2]}, {1, [3]}, {nil, [2]}, {1, [1]}]
    end

    it "nil 2 case" do
      result = [nil, nil, 1, 1, nil].chunks(&.itself)
      result.should eq [{nil, [nil, nil]}, {1, [1, 1]}, {nil, [nil]}]
    end
  end

  describe "#each_cons" do
    context "iterator" do
      it "iterates" do
        iter = [1, 2, 3, 4, 5].each_cons(3)
        iter.next.should eq([1, 2, 3])
        iter.next.should eq([2, 3, 4])
        iter.next.should eq([3, 4, 5])
        iter.next.should be_a(Iterator::Stop)
      end

      it "iterates with reuse = true" do
        iter = [1, 2, 3, 4, 5].each_cons(3, reuse: true)

        a = iter.next
        a.should eq([1, 2, 3])

        b = iter.next
        b.should be(a)
      end

      it "iterates with reuse = array" do
        reuse = [] of Int32
        iter = [1, 2, 3, 4, 5].each_cons(3, reuse: reuse)

        a = iter.next
        a.should eq([1, 2, 3])
        a.should be(reuse)
      end

      it "iterates with reuse = deque" do
        reuse = Deque(Int32).new
        iter = [1, 2, 3, 4, 5].each_cons(3, reuse: reuse)

        a = iter.next
        a.should eq(Deque{1, 2, 3})
        a.should be(reuse)
      end
    end

    context "yield" do
      it "returns running pairs" do
        array = [] of Array(Int32)
        [1, 2, 3, 4].each_cons(2) { |pair| array << pair }
        array.should eq([[1, 2], [2, 3], [3, 4]])
      end

      it "returns running triples" do
        array = [] of Array(Int32)
        [1, 2, 3, 4, 5].each_cons(3) { |triple| array << triple }
        array.should eq([[1, 2, 3], [2, 3, 4], [3, 4, 5]])
      end

      it "yields running pairs with reuse = true" do
        array = [] of Array(Int32)
        object_ids = Set(UInt64).new
        [1, 2, 3, 4].each_cons(2, reuse: true) do |pair|
          object_ids << pair.object_id
          array << pair.dup
        end
        array.should eq([[1, 2], [2, 3], [3, 4]])
        object_ids.size.should eq(1)
      end

      it "yields running pairs with reuse = array" do
        array = [] of Array(Int32)
        reuse = [] of Int32
        [1, 2, 3, 4].each_cons(2, reuse: reuse) do |pair|
          pair.should be(reuse)
          array << pair.dup
        end
        array.should eq([[1, 2], [2, 3], [3, 4]])
      end

      it "yields running pairs with reuse = deque" do
        array = [] of Deque(Int32)
        reuse = Deque(Int32).new
        [1, 2, 3, 4].each_cons(2, reuse: reuse) do |pair|
          pair.should be(reuse)
          array << pair.dup
        end
        array.should eq([Deque{1, 2}, Deque{2, 3}, Deque{3, 4}])
      end
    end
  end

  describe "#each_cons_pair" do
    it "returns running pairs" do
      array = [] of Array(Int32)
      [1, 2, 3, 4].each_cons_pair { |a, b| array << [a, b] }
      array.should eq([[1, 2], [2, 3], [3, 4]])
    end
  end

  describe "each_slice" do
    it "returns partial slices" do
      array = [] of Array(Int32)
      [1, 2, 3].each_slice(2) { |slice| array << slice }
      array.should eq([[1, 2], [3]])
      array[0].should_not be(array[1])
    end

    it "returns full slices" do
      array = [] of Array(Int32)
      [1, 2, 3, 4].each_slice(2) { |slice| array << slice }
      array.should eq([[1, 2], [3, 4]])
      array[0].should_not be(array[1])
    end

    it "reuses with true" do
      array = [] of Array(Int32)
      object_ids = Set(UInt64).new
      [1, 2, 3, 4].each_slice(2, reuse: true) do |slice|
        object_ids << slice.object_id
        array << slice.dup
      end
      array.should eq([[1, 2], [3, 4]])
      object_ids.size.should eq(1)
    end

    it "reuses with existing array" do
      array = [] of Array(Int32)
      reuse = [] of Int32
      [1, 2, 3, 4].each_slice(2, reuse: reuse) do |slice|
        slice.should be(reuse)
        array << slice.dup
      end
      array.should eq([[1, 2], [3, 4]])
    end

    it "returns each_slice iterator" do
      iter = [1, 2, 3, 4, 5].each_slice(2)
      iter.next.should eq([1, 2])
      iter.next.should eq([3, 4])
      iter.next.should eq([5])
      iter.next.should be_a(Iterator::Stop)
    end
  end

  describe "each_step" do
    it_iterates "yields every 2nd element", %w[a c e], %w[a b c d e f].each_step(2)
    it_iterates "accepts an optional offset parameter", %w[b d f], %w[a b c d e f].each_step(2, offset: 1)
    it_iterates "accepts an offset of 0", %w[a c e], %w[a b c d e f].each_step(2, offset: 0)
    it_iterates "accepts an offset larger then the step size", %w[d f], %w[a b c d e f].each_step(2, offset: 3)

    it_iterates "accepts a step larger then the enumerable size", %w[a], %w[a b c d e f].each_step(7)
    it_iterates "accepts an offset larger then the enumerable size", %w[], %w[a b c d e f].each_step(1, offset: 7)

    it "doesn't accept a negative step" do
      expect_raises(ArgumentError) do
        %w[a b c d e f].each_step(-2)
      end
      expect_raises(ArgumentError) do
        %w[a b c d e f].each_step(-2) { }
      end
    end

    it "doesn't accept a step of 0" do
      expect_raises(ArgumentError) do
        %w[a b c d e f].each_step(0)
      end
      expect_raises(ArgumentError) do
        %w[a b c d e f].each_step(0) { }
      end
    end

    it "doesn't accept a negative offset" do
      expect_raises(ArgumentError) do
        %w[a b c d e f].each_step(2, offset: -2)
      end
      expect_raises(ArgumentError) do
        %w[a b c d e f].each_step(2, offset: -2) { }
      end
    end
  end

  describe "each_with_index" do
    it "yields the element and the index" do
      collection = [] of {String, Int32}
      ["a", "b", "c"].each_with_index do |e, i|
        collection << {e, i}
      end
      collection.should eq [{"a", 0}, {"b", 1}, {"c", 2}]
    end

    it "accepts an optional offset parameter" do
      collection = [] of {String, Int32}
      ["Alice", "Bob"].each_with_index(1) do |e, i|
        collection << {e, i}
      end
      collection.should eq [{"Alice", 1}, {"Bob", 2}]
    end

    it "gets each_with_index iterator" do
      iter = [1, 2].each_with_index
      iter.next.should eq({1, 0})
      iter.next.should eq({2, 1})
      iter.next.should be_a(Iterator::Stop)
    end
  end

  describe "each_with_object" do
    it "yields the element and the given object" do
      collection = [] of {Int32, String}
      object = "a"
      (1..3).each_with_object(object) do |e, o|
        collection << {e, o}
      end.should be(object)
      collection.should eq [{1, object}, {2, object}, {3, object}]
    end

    it "gets each_with_object iterator" do
      iter = [1, 2].each_with_object("a")
      iter.next.should eq({1, "a"})
      iter.next.should eq({2, "a"})
      iter.next.should be_a(Iterator::Stop)
    end
  end

  describe "#empty?" do
    it { SpecEnumerable.new.empty?.should be_false }
    it { SpecEmptyEnumerable.new.empty?.should be_true }
  end

  describe "#present?" do
    it { SpecEnumerable.new.present?.should be_true }
    it { SpecEmptyEnumerable.new.present?.should be_false }
  end

  describe "find" do
    it "finds" do
      [1, 2, 3].find { |x| x > 2 }.should eq(3)
    end

    it "doesn't find" do
      [1, 2, 3].find { |x| x > 3 }.should be_nil
    end

    it "doesn't find with default value" do
      [1, 2, 3].find(-1) { |x| x > 3 }.should eq(-1)
    end
  end

  describe "find!" do
    it "finds" do
      [1, 2, 3].find! { |x| x > 2 }.should eq(3)
    end

    it "raises if not found" do
      expect_raises Enumerable::NotFoundError do
        [1, 2, 3].find! { false }
      end
    end
  end

  describe "find_value" do
    it "finds and returns the first truthy block result" do
      [1, 2, 3].find_value { |i| "1" if i == 1 }.should eq "1"
      {1, 2, 3}.find_value { |i| "2" if i == 2 }.should eq "2"
      (1..3).find_value { |i| "3" if i == 3 }.should eq "3"

      # Block returns `true && expression` vs the above `expression if true`.
      # Same idea, but a different idiom. It serves as an allegory for the next
      # test which checks `false` vs `nil`.
      [1, 2, 3].find_value { |i| i == 1 && "1" }.should eq "1"
      {1, 2, 3}.find_value { |i| i == 2 && "2" }.should eq "2"
      (1..3).find_value { |i| i == 3 && "3" }.should eq "3"
    end

    it "returns the default value if there are no truthy block results" do
      {1, 2, 3}.find_value { |i| "4" if i == 4 }.should be_nil
      {1, 2, 3}.find_value "nope" { |i| "4" if i == 4 }.should eq "nope"
      ([] of Int32).find_value false { true }.should be_false

      # Same as above but returns `false` instead of `nil`.
      {1, 2, 3}.find_value { |i| i == 4 && "4" }.should be_nil
      {1, 2, 3}.find_value "nope" { |i| i == 4 && "4" }.should eq "nope"
    end
  end

  describe "first" do
    it "calls block if empty" do
      (1...1).first { 10 }.should eq(10)
    end

    it "gets first" do
      (1..3).first.should eq(1)
    end

    it "raises if enumerable empty" do
      expect_raises Enumerable::EmptyError do
        (1...1).first
      end
    end

    it { [-1, -2, -3].first.should eq(-1) }
  end

  describe "first?" do
    it "gets first?" do
      (1..3).first?.should eq(1)
    end

    it "returns nil if enumerable empty" do
      (1...1).first?.should be_nil
    end
  end

  describe "flat_map" do
    it "does example 1" do
      [1, 2, 3, 4].flat_map { |e| [e, -e] }.should eq([1, -1, 2, -2, 3, -3, 4, -4])
    end

    it "does example 2" do
      [[1, 2], [3, 4]].flat_map { |e| e + [100] }.should eq([1, 2, 100, 3, 4, 100])
    end

    it "does example 3" do
      [[1, 2, 3], 4, 5].flat_map { |e| e }.should eq([1, 2, 3, 4, 5])
    end

    it "does example 4" do
      [{1 => 2}, {3 => 4}].flat_map { |e| e }.should eq([{1 => 2}, {3 => 4}])
    end

    it "flattens iterators" do
      [[1, 2], [3, 4]].flat_map(&.each).should eq([1, 2, 3, 4])
    end

    it "accepts mixed element types" do
      [[1, 2, 3], ['a', 'b'].each, "cde"].flat_map { |e| e }.should eq([1, 2, 3, 'a', 'b', "cde"])
    end
  end

  describe "group_by" do
    it { [1, 2, 2, 3].group_by { |x| x == 2 }.should eq({true => [2, 2], false => [1, 3]}) }

    it "groups can group by size (like the doc example)" do
      %w(Alice Bob Ary).group_by(&.size).should eq({3 => ["Bob", "Ary"],
                                                    5 => ["Alice"]})
    end
  end

  describe "in_groups_of" do
    it { [1, 2, 3].in_groups_of(1).should eq([[1], [2], [3]]) }
    it { [1, 2, 3].in_groups_of(2).should eq([[1, 2], [3, nil]]) }
    it { [1, 2, 3, 4].in_groups_of(3).should eq([[1, 2, 3], [4, nil, nil]]) }
    it { ([] of Int32).in_groups_of(2).should eq([] of Array(Array(Int32 | Nil))) }
    it { [1, 2, 3].in_groups_of(2, "x").should eq([[1, 2], [3, "x"]]) }

    it "raises argument error if size is less than 0" do
      expect_raises ArgumentError, "Size must be positive" do
        [1, 2, 3].in_groups_of(0)
      end
    end

    it "takes a block" do
      sums = [] of Int32
      [1, 2, 4, 5].in_groups_of(3, 10) { |a| sums << a.sum }
      sums.should eq([7, 25])
    end

    it "reuses with true" do
      array = [] of Array(Int32)
      object_ids = Set(UInt64).new
      [1, 2, 4, 5].in_groups_of(3, 10, reuse: true) do |group|
        object_ids << group.object_id
        array << group.dup
      end
      array.should eq([[1, 2, 4], [5, 10, 10]])
      object_ids.size.should eq(1)
    end

    it "reuses with existing array" do
      array = [] of Array(Int32)
      reuse = [] of Int32
      [1, 2, 4, 5].in_groups_of(3, 10, reuse: reuse) do |slice|
        slice.should be(reuse)
        array << slice.dup
      end
      array.should eq([[1, 2, 4], [5, 10, 10]])
    end
  end

  describe "in slices of" do
    it { [1, 2, 3].in_slices_of(1).should eq([[1], [2], [3]]) }
    it { [1, 2, 3].in_slices_of(2).should eq([[1, 2], [3]]) }
    it { [1, 2, 3, 4].in_slices_of(3).should eq([[1, 2, 3], [4]]) }
    it { ([] of Int32).in_slices_of(2).should eq([] of Array(Int32)) }

    it "raises argument error if size is less than 0" do
      expect_raises ArgumentError, "Size must be positive" do
        [1, 2, 3].in_slices_of(0)
      end
    end
  end

  describe "includes?" do
    it "is true if the object exists in the collection" do
      [1, 2, 3].includes?(2).should be_true
    end

    it "is false if the object is not part of the collection" do
      [1, 2, 3].includes?(5).should be_false
    end
  end

  describe "index with a block" do
    it "returns the index of the first element where the block returns true" do
      ["Alice", "Bob"].index { |name| name.size < 4 }.should eq 1
    end

    it "returns nil if no object could be found" do
      ["Alice", "Bob"].index { |name| name.size < 3 }.should be_nil
    end
  end

  describe "index with an object" do
    it "returns the index of that object if found" do
      ["Alice", "Bob"].index("Alice").should eq 0
    end

    it "returns nil if the object was not found" do
      ["Alice", "Bob"].index("Mallory").should be_nil
    end
  end

  describe "index! with a block" do
    it "returns the index of the first element where the block returns true" do
      ["Alice", "Bob"].index! { |name| name.size < 4 }.should eq 1
    end

    it "raises if not found" do
      expect_raises Enumerable::NotFoundError do
        ["Alice", "Bob"].index! { |name| name.size < 3 }
      end
    end
  end

  describe "index! with an object" do
    it "returns the index of that object if found" do
      ["Alice", "Bob"].index!("Alice").should eq 0
    end

    it "raises if not found" do
      expect_raises Enumerable::NotFoundError do
        ["Alice", "Bob"].index!("Mallory")
      end
    end
  end

  describe "index_by" do
    it "creates a hash indexed by the value returned by the block" do
      hash = ["Anna", "Ary", "Alice"].index_by(&.size)
      hash.should eq({4 => "Anna", 3 => "Ary", 5 => "Alice"})
    end

    it "overrides values if a value is returned twice" do
      hash = ["Anna", "Ary", "Alice", "Bob"].index_by(&.size)
      hash.should eq({4 => "Anna", 3 => "Bob", 5 => "Alice"})
    end
  end

  describe "reduce" do
    it { [1, 2, 3].reduce { |memo, i| memo + i }.should eq(6) }
    it { [1, 2, 3].reduce(10) { |memo, i| memo + i }.should eq(16) }
    it { [1, 2, 3].reduce([] of Int32) { |memo, i| memo.unshift(i) }.should eq([3, 2, 1]) }
    it { [[0, 1], [2, 3], [4, 5]].reduce([] of Int32) { |memo, i| memo.concat(i) }.should eq([0, 1, 2, 3, 4, 5]) }

    it "raises if empty" do
      expect_raises Enumerable::EmptyError do
        ([] of Int32).reduce { |memo, i| memo + i }
      end
    end

    it "does not raise if empty if there is a memo argument" do
      result = ([] of Int32).reduce(10) { |memo, i| memo + i }
      result.should eq 10
    end

    it "allows block return type to be different from element type" do
      [1, 2, 3].reduce { |x, y| "#{x}-#{y}" }.should eq("1-2-3")
      [1].reduce { |x, y| "#{x}-#{y}" }.should eq(1)
      {1}.reduce { |x, y| "#{x}-#{y}" }.should eq(1)

      expect_raises Enumerable::EmptyError do
        ([] of Int32).reduce { |x, y| "#{x}-#{y}" }
      end

      expect_raises Enumerable::EmptyError do
        Tuple.new.reduce { |x, y| "#{x}-#{y}" }
      end
    end
  end

  describe "reduce?" do
    it { [1, 2, 3].reduce? { |memo, i| memo + i }.should eq(6) }

    it "returns nil if empty" do
      ([] of Int32).reduce? { |memo, i| memo + i }.should be_nil
    end

    it "allows block return type to be different from element type" do
      [1, 2, 3].reduce? { |x, y| "#{x}-#{y}" }.should eq("1-2-3")
      [1].reduce? { |x, y| "#{x}-#{y}" }.should eq(1)
      {1}.reduce? { |x, y| "#{x}-#{y}" }.should eq(1)
      ([] of Int32).reduce? { |x, y| "#{x}-#{y}" }.should be_nil
      Tuple.new.reduce? { |x, y| "#{x}-#{y}" }.should be_nil
    end
  end

  describe "#accumulate" do
    context "prefix sums" do
      it { SpecEnumerable.new.accumulate.should eq([1, 3, 6]) }
      it { [1.5, 3.75, 6.125].accumulate.should eq([1.5, 5.25, 11.375]) }
      it { Array(Int32).new.accumulate.should eq(Array(Int32).new) }
    end

    context "prefix sums, with init" do
      it { SpecEnumerable.new.accumulate(0).should eq([0, 1, 3, 6]) }
      it { [1.5, 3.75, 6.125].accumulate(0.5).should eq([0.5, 2.0, 5.75, 11.875]) }
      it { Array(Int32).new.accumulate(7).should eq([7]) }

      it "preserves initial type" do
        x = SpecEnumerable.new.accumulate(4.0)
        x.should be_a(Array(Float64))
        x.should eq([4.0, 5.0, 7.0, 10.0])
      end
    end

    context "generic cumulative fold" do
      it { SpecEnumerable.new.accumulate { |x, y| x * 10 + y }.should eq([1, 12, 123]) }
      it { Array(Int32).new.accumulate { raise "" }.should eq(Array(Int32).new) }
    end

    context "generic cumulative fold, with init" do
      it { SpecEnumerable.new.accumulate(4) { |x, y| x * 10 + y }.should eq([4, 41, 412, 4123]) }
      it { Array(Int32).new.accumulate(7) { raise "" }.should eq([7]) }

      it "preserves initial type" do
        x = [4, 3, 2].accumulate("X") { |x, y| x * y }
        x.should be_a(Array(String))
        x.should eq(%w(X XXXX XXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXX))
      end
    end
  end

  describe "#join" do
    it "()" do
      [1, 2, 3].join.should eq("123")
    end

    it "(separator)" do
      ["Ruby", "Crystal", "Python"].join(", ").should eq "Ruby, Crystal, Python"
    end

    it "(&)" do
      str = [1, 2, 3].join { |x| x + 1 }
      str.should eq("234")
    end

    it "(separator, &)" do
      str = [1, 2, 3].join(", ") { |x| x + 1 }
      str.should eq("2, 3, 4")
    end

    it "(io)" do
      io = IO::Memory.new
      [1, 2, 3].join(io)
      io.to_s.should eq("123")
    end

    it "(io, separator)" do
      io = IO::Memory.new
      ["Ruby", "Crystal", "Python"].join(io, ", ")
      io.to_s.should eq "Ruby, Crystal, Python"
    end

    it "(separator, io) (deprecated)" do
      io = IO::Memory.new
      ["Ruby", "Crystal", "Python"].join(", ", io)
      io.to_s.should eq "Ruby, Crystal, Python"
    end

    it "(io, &)" do
      io = IO::Memory.new
      [1, 2, 3].join(io) { |x, io| io << x + 1 }
      io.to_s.should eq("234")
    end

    it "(io, separator, &)" do
      io = IO::Memory.new
      [1, 2, 3].join(io, ", ") { |x, io| io << x + 1 }
      io.to_s.should eq("2, 3, 4")
    end

    it "(separator, io, &) (deprecated)" do
      str = IO::Memory.new
      [1, 2, 3].join(", ", str) { |x, io| io << x + 1 }
      str.to_s.should eq("2, 3, 4")
    end
  end

  describe "map" do
    it "applies the function to each element and returns a new array" do
      result = [1, 2, 3].map { |i| i * 10 }
      result.should eq [10, 20, 30]
    end

    it "leaves the original unmodified" do
      original = [1, 2, 3]
      original.map { |i| i * 10 }
      original.should eq [1, 2, 3]
    end
  end

  describe "map_with_index" do
    it "yields the element and the index" do
      result = SpecEnumerable.new.map_with_index { |e, i| "Value ##{i}: #{e}" }
      result.should eq ["Value #0: 1", "Value #1: 2", "Value #2: 3"]
    end

    it "yields the element and the index of an iterator" do
      str = "hello"
      result = str.each_char.map_with_index { |char, i| "#{char}#{i}" }
      result.should eq ["h0", "e1", "l2", "l3", "o4"]
    end
  end

  describe "max" do
    it { [1, 2, 3].max.should eq(3) }
    it { [1, 2, 3].max(0).should eq([] of Int32) }
    it { [1, 2, 3].max(1).should eq([3]) }
    it { [1, 2, 3].max(2).should eq([3, 2]) }
    it { [1, 2, 3].max(3).should eq([3, 2, 1]) }
    it { [1, 2, 3].max(4).should eq([3, 2, 1]) }
    it { ([] of Int32).max(0).should eq([] of Int32) }
    it { ([] of Int32).max(5).should eq([] of Int32) }
    it {
      (0..1000).map { |x| (x*137 + x*x*139) % 5000 }.max(10).should eq([
        4992, 4990, 4980, 4972, 4962, 4962, 4960, 4960, 4952, 4952,
      ])
    }

    it "does not modify the array" do
      xs = [7, 5, 2, 4, 9]
      xs.max(2).should eq([9, 7])
      xs.should eq([7, 5, 2, 4, 9])
    end

    it "raises if empty" do
      expect_raises Enumerable::EmptyError do
        ([] of Int32).max
      end
    end

    it "raises if n is negative" do
      expect_raises ArgumentError do
        ([1, 2, 3] of Int32).max(-1)
      end
    end

    it "raises if not comparable" do
      expect_raises ArgumentError do
        [Float64::NAN, 1.0, 2.0, Float64::NAN].max
      end
    end

    it "raises if not comparable in max(n)" do
      expect_raises ArgumentError do
        [Float64::NAN, 1.0, 2.0, Float64::NAN].max(2)
      end
    end
  end

  describe "max?" do
    it "returns nil if empty" do
      ([] of Int32).max?.should be_nil
    end
  end

  describe "max_by" do
    it { [-1, -2, -3].max_by { |x| -x }.should eq(-3) }
  end

  describe "max_by?" do
    it "returns nil if empty" do
      ([] of Int32).max_by? { |x| -x }.should be_nil
    end
  end

  describe "max_of" do
    it { [-1, -2, -3].max_of { |x| -x }.should eq(3) }

    it "raises if not comparable" do
      expect_raises ArgumentError do
        [-1.0, Float64::NAN, -3.0].max_of { |x| -x }
      end
    end
  end

  describe "max_of?" do
    it "returns nil if empty" do
      ([] of Int32).max_of? { |x| -x }.should be_nil
    end
  end

  describe "min" do
    it { [1, 2, 3].min.should eq(1) }
    it { [1, 2, 3].min(0).should eq([] of Int32) }
    it { [1, 2, 3].min(1).should eq([1]) }
    it { [1, 2, 3].min(2).should eq([1, 2]) }
    it { [1, 2, 3].min(3).should eq([1, 2, 3]) }
    it { [1, 2, 3].min(4).should eq([1, 2, 3]) }
    it { ([] of Int32).min(0).should eq([] of Int32) }
    it { ([] of Int32).min(1).should eq([] of Int32) }
    it {
      (0..1000).map { |x| (x*137 + x*x*139) % 5000 }.min(10).should eq([
        0, 10, 20, 26, 26, 26, 26, 30, 32, 32,
      ])
    }

    it "does not modify the array" do
      xs = [7, 5, 2, 4, 9]
      xs.min(2).should eq([2, 4])
      xs.should eq([7, 5, 2, 4, 9])
    end

    it "raises if empty" do
      expect_raises Enumerable::EmptyError do
        ([] of Int32).min
      end
    end

    it "raises if n is negative" do
      expect_raises ArgumentError do
        ([1, 2, 3] of Int32).min(-1)
      end
    end

    it "raises if not comparable" do
      expect_raises ArgumentError do
        [-1.0, Float64::NAN, -3.0].min
      end
    end

    it "raises if not comparable in min(n)" do
      expect_raises ArgumentError do
        [Float64::NAN, 1.0, 2.0, Float64::NAN].min(2)
      end
    end
  end

  describe "min?" do
    it "returns nil if empty" do
      ([] of Int32).min?.should be_nil
    end
  end

  describe "min_by" do
    it { [1, 2, 3].min_by { |x| -x }.should eq(3) }
  end

  describe "min_by?" do
    it "returns nil if empty" do
      ([] of Int32).min_by? { |x| -x }.should be_nil
    end
  end

  describe "min_of" do
    it { [1, 2, 3].min_of { |x| -x }.should eq(-3) }

    it "raises if not comparable" do
      expect_raises ArgumentError do
        [-1.0, Float64::NAN, -3.0].min_of { |x| -x }
      end
    end
  end

  describe "min_of?" do
    it "returns nil if empty" do
      ([] of Int32).min_of? { |x| -x }.should be_nil
    end
  end

  describe "minmax" do
    it { [1, 2, 3].minmax.should eq({1, 3}) }

    it "raises if empty" do
      expect_raises Enumerable::EmptyError do
        ([] of Int32).minmax
      end
    end
  end

  describe "minmax?" do
    it "returns two nils if empty" do
      ([] of Int32).minmax?.should eq({nil, nil})
    end

    it "raises if not comparable" do
      expect_raises ArgumentError do
        [-1.0, Float64::NAN, -3.0].minmax
      end
    end
  end

  describe "minmax_by" do
    it { [-1, -2, -3].minmax_by { |x| -x }.should eq({-1, -3}) }
  end

  describe "minmax_by?" do
    it "returns two nils if empty" do
      ([] of Int32).minmax_by? { |x| -x }.should eq({nil, nil})
    end
  end

  describe "minmax_of" do
    it { [-1, -2, -3].minmax_of { |x| -x }.should eq({1, 3}) }

    it "raises if not comparable" do
      expect_raises ArgumentError do
        [-1.0, Float64::NAN, -3.0].minmax_of { |x| -x }
      end
    end
  end

  describe "minmax_of?" do
    it "returns two nils if empty" do
      ([] of Int32).minmax_of? { |x| -x }.should eq({nil, nil})
    end
  end

  describe "none?" do
    it { [1, 2, 2, 3].none? { |x| x == 1 }.should be_false }
    it { [1, 2, 2, 3].none? { |x| x == 0 }.should be_true }
  end

  describe "none? without block" do
    it { [nil, false].none?.should be_true }
    it { [nil, false, true].none?.should be_false }
  end

  describe "none? with pattern" do
    it { [2, 3, 4].none?(5..7).should be_true }
    it { [1, false, nil].none?(Bool).should be_false }
  end

  describe "one?" do
    it { [1, 2, 2, 3].one? { |x| x == 1 }.should be_true }
    it { [1, 2, 2, 3].one? { |x| x == 2 }.should be_false }
    it { [1, 2, 2, 3].one? { |x| x == 0 }.should be_false }
    it { [1, 2, false].one?.should be_false }
    it { [1, false, false].one?.should be_true }
    it { [false].one?.should be_false }
    it { [1, 5, 9].one?(3..6).should be_true }
    it { [1, false, 2].one?(Int32).should be_false }
  end

  describe "partition" do
    it { [1, 2, 2, 3].partition { |x| x == 2 }.should eq({[2, 2], [1, 3]}) }
    it { [1, 2, 3, 4, 5, 6].partition(&.even?).should eq({[2, 4, 6], [1, 3, 5]}) }

    it "with mono type on union type" do
      ints, others = [1, true, nil, 3, false, "string", 'c'].partition(Int32)
      ints.should eq([1, 3])
      others.should eq([true, nil, false, "string", 'c'])
      ints.should be_a(Array(Int32))
      others.should be_a(Array(Bool | String | Char | Nil))
    end

    it "with union type on union type" do
      ints_bools, others = [1, true, nil, 3, false, "string", 'c'].partition(Int32 | Bool)
      ints_bools.should eq([1, true, 3, false])
      others.should eq([nil, "string", 'c'])
      ints_bools.should be_a(Array(Int32 | Bool))
      others.should be_a(Array(String | Char | Nil))
    end

    it "with missing type on union type" do
      symbols, others = [1, true, nil, 3, false, "string", 'c'].partition(Symbol)
      symbols.empty?.should be_true
      others.should eq([1, true, nil, 3, false, "string", 'c'])
      symbols.should be_a(Array(Symbol))
      others.should be_a(Array(Int32 | Bool | String | Char | Nil))
    end

    it "with mono type on mono type" do
      ints, others = [1, 3].partition(Int32)
      ints.should eq([1, 3])
      others.empty?.should be_true
      ints.should be_a(Array(Int32))
      others.should be_a(Array(NoReturn))
    end
  end

  describe "reject" do
    it "rejects the values for which the block returns true" do
      [1, 2, 3, 4].reject(&.even?).should eq([1, 3])
    end

    it "rejects with pattern" do
      [1, 2, 3, 4, 5, 6].reject(2..4).should eq([1, 5, 6])
    end

    it "with type" do
      ints = [1, true, false, 3].reject(Bool)
      ints.should eq([1, 3])
      ints.should be_a(Array(Int32))
    end

    it "with type, for tuples" do
      ints = {1, true, false, 3}.reject(Int32)
      ints.should eq([true, false])
      ints.should be_a(Array(Bool))
    end
  end

  describe "sample" do
    describe "single-element" do
      it "samples without random" do
        [1].sample.should eq(1)

        x = SpecEnumerable.new.sample
        [1, 2, 3].should contain(x)
      end

      it "samples with random" do
        SpecEnumerable.new.sample(Random.new(1)).should eq(1)
        [1, 2, 3].sample(Random.new(1)).should eq(2)
      end

      it "raises on empty self" do
        expect_raises(IndexError) { Array(Int32).new.sample }
        expect_raises(IndexError) { SpecEmptyEnumerable.new.sample }
      end
    end

    describe "multiple-element" do
      it "samples 0 elements" do
        ary = [1].sample(0)
        ary.should eq([] of Int32)
        ary.should be_a(Array(Int32))

        ary = SpecEmptyEnumerable.new.sample(0)
        ary.should eq([] of Int32)
        ary.should be_a(Array(Int32))
      end

      it "samples 1 element" do
        [1].sample(1).should eq([1])

        x = [1, 2, 3].sample(1)
        x.size.should eq(1)
        x = x.first
        [1, 2, 3].should contain(x)
      end

      it "samples k elements out of n" do
        ary = [1].sample(1)
        ary.should eq([1])

        a = {1, 2, 3, 4, 5}
        b = a.sample(3)
        set = Set.new(b)
        set.size.should eq(3)

        set.each do |e|
          a.should contain(e)
        end
      end

      it "raises on k < 0" do
        expect_raises(ArgumentError) { Array(Int32).new.sample(-1) }
        expect_raises(ArgumentError) { SpecEnumerable.new.sample(-1) }
      end

      it "samples k elements out of n, where k > n" do
        a = SpecEnumerable.new
        b = a.sample(10)
        b.size.should eq(3)
        set = Set.new(b)
        set.size.should eq(3)

        set.each do |e|
          a.should contain(e)
        end

        SpecEmptyEnumerable.new.sample(1).should eq([] of Int32)
      end

      it "samples k elements out of n, with random" do
        a = (1..5)
        b = a.sample(3, Random.new(1))
        b.should eq([4, 3, 1])
      end
    end
  end

  describe "select" do
    it "selects the values for which the block returns true" do
      [1, 2, 3, 4].select(&.even?).should eq([2, 4])
    end

    it "with pattern" do
      [1, 2, 3, 4, 5].select(2..4).should eq([2, 3, 4])
    end

    it "with type" do
      ints = [1, true, nil, 3, false].select(Int32)
      ints.should eq([1, 3])
      ints.should be_a(Array(Int32))
    end
  end

  describe "skip" do
    it "returns an array without the skipped elements" do
      [1, 2, 3, 4, 5, 6].skip(3).should eq [4, 5, 6]
    end

    it "returns an empty array when skipping more elements than array size" do
      [1, 2].skip(3).should eq [] of Int32
    end

    it "raises if count is negative" do
      expect_raises(ArgumentError) do
        [1, 2].skip(-1)
      end
    end
  end

  describe "skip_while" do
    it "skips elements while the condition holds true" do
      result = [1, 2, 3, 4, 5, 0].skip_while { |i| i < 3 }
      result.should eq [3, 4, 5, 0]
    end

    it "returns an empty array if the condition is always true" do
      [1, 2, 3].skip_while { true }.should eq [] of Int32
    end

    it "returns the full Array if the first check is false" do
      [5, 0, 1, 2, 3].skip_while { |x| x < 4 }.should eq [5, 0, 1, 2, 3]
    end

    it "does not yield to the block anymore once it returned false" do
      called = 0
      [1, 2, 3, 4, 4].skip_while do |i|
        called += 1
        i < 3
      end
      called.should eq 3
    end
  end

  describe "sum" do
    it { ([] of Int32).sum.should eq(0) }
    it { [1, 2, 3].sum.should eq(6) }
    it { [1, 2, 3].sum(4).should eq(10) }
    it { [1, 2, 3].sum(4.5).should eq(10.5) }
    it { (1..3).sum { |x| x * 2 }.should eq(12) }
    it { (1..3).sum(1.5) { |x| x * 2 }.should eq(13.5) }
    it { [1, 3_u64].sum(0_i32).should eq(4_u32) }
    it { [1, 3].sum(0_u64).should eq(4_u64) }
    it { [1, 10000000000_u64].sum(0_u64).should eq(10000000001) }
    pending_wasm32 "raises if union types are summed", tags: %w[slow] do
      assert_compile_error <<-CRYSTAL,
        require "prelude"
        [1, 10000000000_u64].sum
        CRYSTAL
        "`Enumerable#sum` and `#product` do not support Union " +
        "types. Instead, use `Enumerable#sum(initial)` and " +
        "`#product(initial)`, respectively, with an initial value " +
        "of the intended type of the call."
    end

    it "uses additive_identity from type" do
      typeof([1, 2, 3].sum).should eq(Int32)
      typeof([1.5, 2.5, 3.5].sum).should eq(Float64)
      typeof([1, 2, 3].sum(&.to_f)).should eq(Float64)
      typeof(([1, 2, 3] of Float32).sum).should eq(Float32)
    end

    it "array of arrays" do
      [[1, 2, 3], [3, 4, 5]].sum.should eq [1, 2, 3, 3, 4, 5]
      [[[1, 2], [3]], [[1, 2], [3, 4, 5]]].sum.should eq [[1, 2], [3], [1, 2], [3, 4, 5]]
      Deque{[1, 2, 3], [3, 4, 5]}.sum.should eq [1, 2, 3, 3, 4, 5]
    end

    it "strings" do
      ["foo", "bar"].sum.should eq "foobar"
      [1, 2, 3].sum(&.to_s).should eq "123"
    end

    it "float" do
      [1.0, 2.0, 3.5, 4.5].sum.should eq 11.0
      ([1.0, 2.0, 3.5, 4.5] of Float32).sum.should eq 11.0
    end

    it "slices" do
      [Slice[1, 2], Slice[3, 'a', 'b', 'c']].sum.should eq(Slice[1, 2, 3, 'a', 'b', 'c'])
    end
  end

  describe "product" do
    it { ([] of Int32).product.should eq(1) }
    it { [1, 2, 3].product.should eq(6) }
    it { [1, 2, 3].product(4).should eq(24) }
    it { [1, 2, 3].product(4.5).should eq(27) }
    it { (1..3).product { |x| x * 2 }.should eq(48) }
    it { (1..3).product(1.5) { |x| x * 2 }.should eq(72) }

    it "uses zero from type" do
      typeof([1, 2, 3].product).should eq(Int32)
      typeof([1.5, 2.5, 3.5].product).should eq(Float64)
      typeof([1, 2, 3].product(&.to_f)).should eq(Float64)
    end

    it { [1, 3_u64].product(3_i32).should eq(9_u32) }
    it { [1, 3].product(3_u64).should eq(9_u64) }
    it { [1, 10000000000_u64].product(3_u64).should eq(30000000000_u64) }
    pending_wasm32 "raises if union types are multiplied", tags: %w[slow] do
      assert_compile_error <<-CRYSTAL,
        require "prelude"
        [1, 10000000000_u64].product
        CRYSTAL
        "`Enumerable#sum` and `#product` do not support Union " +
        "types. Instead, use `Enumerable#sum(initial)` and " +
        "`#product(initial)`, respectively, with an initial value " +
        "of the intended type of the call."
    end
  end

  describe "first" do
    it { (1..3).first(1).should eq([1]) }
    it { (1..3).first(4).should eq([1, 2, 3]) }

    it "raises if count is negative" do
      expect_raises(ArgumentError) do
        (1..2).first(-1)
      end
    end
  end

  describe "take_while" do
    it "keeps elements while the block returns true" do
      [1, 2, 3, 4, 5, 0].take_while { |i| i < 3 }.should eq [1, 2]
    end

    it "returns the full Array if the condition is always true" do
      [1, 2, 3, -3].take_while { true }.should eq [1, 2, 3, -3]
    end

    it "returns an empty Array if the block is false for the first element" do
      [1, 2, -1, 0].take_while { |i| i <= 0 }.should eq [] of Int32
    end

    it "does not call the block again once it returned false" do
      called = 0
      [1, 2, 3, 4, 0].take_while do |i|
        called += 1
        i < 3
      end
      called.should eq 3
    end
  end

  describe "tally_by" do
    it "returns a hash with counts according to the value returned by the block" do
      %w[a A b B c C C].tally_by(&.downcase).should eq({"a" => 2, "b" => 2, "c" => 3})
    end

    context "with hash" do
      it "returns a hash with counts according to the value returned by the block" do
        hash = {} of Char => Int32
        words = ["Crystal", "Ruby"]
        words.each { |word| word.chars.tally_by(hash, &.downcase) }

        hash.should eq(
          {'c' => 1, 'r' => 2, 'y' => 2, 's' => 1, 't' => 1, 'a' => 1, 'l' => 1, 'u' => 1, 'b' => 1}
        )
      end
    end
  end

  describe "tally" do
    it "returns a hash with counts according to the value" do
      %w[1 2 3 3 3 2].tally.should eq({"1" => 1, "2" => 2, "3" => 3})
    end

    context "with hash" do
      it "returns a hash with counts according to the value" do
        hash = {} of Char => Int32
        words = ["crystal", "ruby"]
        words.each(&.chars.tally(hash))

        hash.should eq(
          {'c' => 1, 'r' => 2, 'y' => 2, 's' => 1, 't' => 1, 'a' => 1, 'l' => 1, 'u' => 1, 'b' => 1}
        )
      end

      it "updates existing hash with counts according to the value" do
        hash = {'a' => 1, 'b' => 1, 'c' => 1, 'd' => 1}
        words = ["crystal", "ruby"]
        words.each(&.chars.tally(hash))

        hash.should eq(
          {'a' => 2, 'b' => 2, 'c' => 2, 'd' => 1, 'r' => 2, 'y' => 2, 's' => 1, 't' => 1, 'l' => 1, 'u' => 1}
        )
      end

      it "ignores the default value" do
        hash = Hash(Char, Int32).new(100)
        words = ["crystal", "ruby"]
        words.each(&.chars.tally(hash))

        hash.should eq(
          {'c' => 1, 'r' => 2, 'y' => 2, 's' => 1, 't' => 1, 'a' => 1, 'l' => 1, 'u' => 1, 'b' => 1}
        )
      end

      it "returns a hash with Int64 counts according to the value" do
        hash = {} of Char => Int64
        words = ["crystal", "ruby"]
        words.each(&.chars.tally(hash))

        hash.should eq(
          {'c' => 1, 'r' => 2, 'y' => 2, 's' => 1, 't' => 1, 'a' => 1, 'l' => 1, 'u' => 1, 'b' => 1}
        )
      end

      it "tallies an interface type" do
        InterfaceEnumerable.new.tally.should eq({One.new => 1, Two.new => 1})
      end
    end
  end

  describe "to_a" do
    it "converts to an Array" do
      (1..3).to_a.should eq [1, 2, 3]
    end
  end

  describe "to_h" do
    it "for tuples" do
      hash = Tuple.new({"a", 1}, {"c", 2}).to_h
      hash.should be_a(Hash(String, Int32))
      hash.should eq({"a" => 1, "c" => 2})

      hash = Tuple.new({1, 1.0}, {'a', "aaa"}).to_h
      hash.should be_a(Hash(Int32 | Char, Float64 | String))
      hash.should eq({1 => 1.0, 'a' => "aaa"})
    end

    it "for array" do
      [['a', 'b'], ['c', 'd']].to_h.should eq({'a' => 'b', 'c' => 'd'})
    end

    it "with block" do
      (1..3).to_h { |i| {i, i ** 2} }.should eq({1 => 1, 2 => 4, 3 => 9})
    end
  end

  describe "zip" do
    it "works for Iterators as receiver" do
      SpecCountUpIterator.new(3).zip(1..3, 2..4).should eq([{0, 1, 2}, {1, 2, 3}, {2, 3, 4}])
    end
  end

  describe "zip?" do
    it "works for Iterators as receiver" do
      SpecCountUpIterator.new(3).zip?(1..2, 2..4).should eq([{0, 1, 2}, {1, 2, 3}, {2, nil, 4}])
    end
  end
end
