require_relative '../../../spec_helper'
require_relative '../../enumerable/shared/enumeratorized'

describe "Enumerator::Product#each" do
  it_behaves_like :enumeratorized_with_origin_size, :each, Enumerator::Product.new([1, 2], [:a, :b])

  it "yields each element of Cartesian product of enumerators" do
    enum = Enumerator::Product.new([1, 2], [:a, :b])
    acc = []
    enum.each { |e| acc << e }
    acc.should == [[1, :a], [1, :b], [2, :a], [2, :b]]
  end

  it "calls #each_entry method on enumerators" do
    object1 = Object.new
    def object1.each_entry
      yield 1
      yield 2
    end

    object2 = Object.new
    def object2.each_entry
      yield :a
      yield :b
    end

    enum = Enumerator::Product.new(object1, object2)
    acc = []
    enum.each { |e| acc << e }
    acc.should == [[1, :a], [1, :b], [2, :a], [2, :b]]
  end

  it "raises a NoMethodError if the object doesn't respond to #each_entry" do
    -> {
      Enumerator::Product.new(Object.new).each {}
    }.should raise_error(NoMethodError, /undefined method [`']each_entry' for/)
  end

  it "returns enumerator if not given a block" do
    enum = Enumerator::Product.new([1, 2], [:a, :b])
    enum.each.should.kind_of?(Enumerator)

    enum = Enumerator::Product.new([1, 2], [:a, :b])
    enum.each.to_a.should == [[1, :a], [1, :b], [2, :a], [2, :b]]
  end

  it "returns self if given a block" do
    enum = Enumerator::Product.new([1, 2], [:a, :b])
    enum.each {}.should.equal?(enum)
  end

  it "doesn't accept arguments" do
    Enumerator::Product.instance_method(:each).arity.should == 0
  end

  it "yields each element to a block that takes multiple arguments" do
    enum = Enumerator::Product.new([1, 2], [:a, :b])

    acc = []
    enum.each { |x, y| acc << x }
    acc.should == [1, 1, 2, 2]

    acc = []
    enum.each { |x, y| acc << y }
    acc.should == [:a, :b, :a, :b]

    acc = []
    enum.each { |x, y, z| acc << z }
    acc.should == [nil, nil, nil, nil]
  end
end
