require_relative '../../spec_helper'
require_relative 'fixtures/classes'

describe "Array#uniq" do
  it "returns an array with no duplicates" do
    ["a", "a", "b", "b", "c"].uniq.should == ["a", "b", "c"]
  end

  it "properly handles recursive arrays" do
    empty = ArraySpecs.empty_recursive_array
    empty.uniq.should == [empty]

    array = ArraySpecs.recursive_array
    array.uniq.should == [1, 'two', 3.0, array]
  end

  it "uses eql? semantics" do
    [1.0, 1].uniq.should == [1.0, 1]
  end

  it "compares elements first with hash" do
    x = mock('0')
    x.should_receive(:hash).at_least(1).and_return(0)
    y = mock('0')
    y.should_receive(:hash).at_least(1).and_return(0)

    [x, y].uniq.should == [x, y]
  end

  it "does not compare elements with different hash codes via eql?" do
    x = mock('0')
    x.should_not_receive(:eql?)
    y = mock('1')
    y.should_not_receive(:eql?)

    x.should_receive(:hash).at_least(1).and_return(0)
    y.should_receive(:hash).at_least(1).and_return(1)

    [x, y].uniq.should == [x, y]
  end

  ruby_version_is '2.7' do
    it "compares elements with matching hash codes with #eql?" do
      a = Array.new(2) do
        obj = mock('0')
        obj.should_receive(:hash).at_least(1).and_return(0)

        def obj.eql?(o)
          false
        end

        obj
      end

      a.uniq.should == a

      a = Array.new(2) do
        obj = mock('0')
        obj.should_receive(:hash).at_least(1).and_return(0)

        def obj.eql?(o)
          true
        end

        obj
      end

      a.uniq.size.should == 1
    end
  end

  ruby_version_is ''...'2.7' do
    it "compares elements with matching hash codes with #eql?" do
      a = Array.new(2) do
        obj = mock('0')
        obj.should_receive(:hash).at_least(1).and_return(0)

        def obj.eql?(o)
          # It's undefined whether the impl does a[0].eql?(a[1]) or
          # a[1].eql?(a[0]) so we taint both.
          taint
          o.taint
          false
        end

        obj
      end

      a.uniq.should == a
      a[0].tainted?.should == true
      a[1].tainted?.should == true

      a = Array.new(2) do
        obj = mock('0')
        obj.should_receive(:hash).at_least(1).and_return(0)

        def obj.eql?(o)
          # It's undefined whether the impl does a[0].eql?(a[1]) or
          # a[1].eql?(a[0]) so we taint both.
          taint
          o.taint
          true
        end

        obj
      end

      a.uniq.size.should == 1
      a[0].tainted?.should == true
      a[1].tainted?.should == true
    end
  end

  it "compares elements based on the value returned from the block" do
    a = [1, 2, 3, 4]
    a.uniq { |x| x >= 2 ? 1 : 0 }.should == [1, 2]
  end

  it "yields items in order" do
    a = [1, 2, 3]
    yielded = []
    a.uniq { |v| yielded << v }
    yielded.should == a
  end

  it "handles nil and false like any other values" do
    [nil, false, 42].uniq { :foo }.should == [nil]
    [false, nil, 42].uniq { :bar }.should == [false]
  end

  it "returns subclass instance on Array subclasses" do
    ArraySpecs::MyArray[1, 2, 3].uniq.should be_an_instance_of(ArraySpecs::MyArray)
  end

  it "properly handles an identical item even when its #eql? isn't reflexive" do
    x = mock('x')
    x.should_receive(:hash).at_least(1).and_return(42)
    x.stub!(:eql?).and_return(false) # Stubbed for clarity and latitude in implementation; not actually sent by MRI.

    [x, x].uniq.should == [x]
  end

  describe "given an array of BasicObject subclasses that define ==, eql?, and hash" do
    # jruby/jruby#3227
    it "filters equivalent elements using those definitions" do

      basic = Class.new(BasicObject) do
        attr_reader :x

        def initialize(x)
          @x = x
        end

        def ==(rhs)
          @x == rhs.x
        end
        alias_method :eql?, :==

        def hash
          @x.hash
        end
      end

      a = [basic.new(3), basic.new(2), basic.new(1), basic.new(4), basic.new(1), basic.new(2), basic.new(3)]
      a.uniq.should == [basic.new(3), basic.new(2), basic.new(1), basic.new(4)]
    end
  end
end

describe "Array#uniq!" do
  it "modifies the array in place" do
    a = [ "a", "a", "b", "b", "c" ]
    a.uniq!
    a.should == ["a", "b", "c"]
  end

  it "returns self" do
    a = [ "a", "a", "b", "b", "c" ]
    a.should equal(a.uniq!)
  end

  it "properly handles recursive arrays" do
    empty = ArraySpecs.empty_recursive_array
    empty_dup = empty.dup
    empty.uniq!
    empty.should == empty_dup

    array = ArraySpecs.recursive_array
    expected = array[0..3]
    array.uniq!
    array.should == expected
  end

  it "compares elements first with hash" do
    x = mock('0')
    x.should_receive(:hash).at_least(1).and_return(0)
    y = mock('0')
    y.should_receive(:hash).at_least(1).and_return(0)

    a = [x, y]
    a.uniq!
    a.should == [x, y]
  end

  it "does not compare elements with different hash codes via eql?" do
    x = mock('0')
    x.should_not_receive(:eql?)
    y = mock('1')
    y.should_not_receive(:eql?)

    x.should_receive(:hash).at_least(1).and_return(0)
    y.should_receive(:hash).at_least(1).and_return(1)

    a = [x, y]
    a.uniq!
    a.should == [x, y]
  end

  it "returns nil if no changes are made to the array" do
    [ "a", "b", "c" ].uniq!.should == nil
  end

  it "raises a #{frozen_error_class} on a frozen array when the array is modified" do
    dup_ary = [1, 1, 2]
    dup_ary.freeze
    -> { dup_ary.uniq! }.should raise_error(frozen_error_class)
  end

  # see [ruby-core:23666]
  it "raises a #{frozen_error_class} on a frozen array when the array would not be modified" do
    -> { ArraySpecs.frozen_array.uniq!}.should raise_error(frozen_error_class)
    -> { ArraySpecs.empty_frozen_array.uniq!}.should raise_error(frozen_error_class)
  end

  it "doesn't yield to the block on a frozen array" do
    -> { ArraySpecs.frozen_array.uniq!{ raise RangeError, "shouldn't yield"}}.should raise_error(frozen_error_class)
  end

  it "compares elements based on the value returned from the block" do
    a = [1, 2, 3, 4]
    a.uniq! { |x| x >= 2 ? 1 : 0 }.should == [1, 2]
  end

  it "properly handles an identical item even when its #eql? isn't reflexive" do
    x = mock('x')
    x.should_receive(:hash).at_least(1).and_return(42)
    x.stub!(:eql?).and_return(false) # Stubbed for clarity and latitude in implementation; not actually sent by MRI.

    a = [x, x]
    a.uniq!
    a.should == [x]
  end
end
