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

describe "Data#deconstruct_keys" do
  it "returns a hash of attributes" do
    klass = Data.define(:x, :y)
    d = klass.new(1, 2)

    d.deconstruct_keys([:x, :y]).should == {x: 1, y: 2}
  end

  it "requires one argument" do
    klass = Data.define(:x, :y)
    d = klass.new(1, 2)

    -> {
      d.deconstruct_keys
    }.should.raise(ArgumentError, /wrong number of arguments \(given 0, expected 1\)/)
  end

  it "returns only specified keys" do
    klass = Data.define(:x, :y)
    d = klass.new(1, 2)

    d.deconstruct_keys([:x, :y]).should == {x: 1, y: 2}
    d.deconstruct_keys([:x]    ).should == {x: 1}
    d.deconstruct_keys([]      ).should == {}
  end

  it "accepts string attribute names" do
    klass = Data.define(:x, :y)
    d = klass.new(1, 2)

    d.deconstruct_keys(['x', 'y']).should == {'x' => 1, 'y' => 2}
  end

  it "returns an empty hash when there are more keys than attributes" do
    klass = Data.define(:x, :y)
    d = klass.new(1, 2)

    d.deconstruct_keys([:x, :y, :x]).should == {}
  end

  it "returns at first not existing attribute name" do
    klass = Data.define(:x, :y)
    d = klass.new(1, 2)

    d.deconstruct_keys([:a, :x]).should == {}
    d.deconstruct_keys([:x, :a]).should == {x: 1}
  end

  it "accepts nil argument and return all the attributes" do
    klass = Data.define(:x, :y)
    d = klass.new(1, 2)

    d.deconstruct_keys(nil).should == {x: 1, y: 2}
  end

  ruby_version_is "4.0" do # https://bugs.ruby-lang.org/issues/21844
    it "tries to convert a key with #to_str if index is not a String nor a Symbol, but responds to #to_str" do
      klass = Data.define(:x, :y)
      d = klass.new(1, 2)

      key = mock("to_str")
      key.should_receive(:to_str).and_return("y")

      d.deconstruct_keys([key]).should == { "y" => 2 }
    end

    it "raise an error on argument position number" do
      klass = Data.define(:x, :y)
      d = klass.new(1, 2)

      -> {
        d.deconstruct_keys([0, 1])
      }.should.raise(TypeError, "0 is not a symbol nor a string")
    end

    it "raises a TypeError if the conversion with #to_str does not return a String" do
      klass = Data.define(:x, :y)
      d = klass.new(1, 2)

      key = mock("to_str")
      key.should_receive(:to_str).and_return(0)

      -> {
        d.deconstruct_keys([key])
      }.should raise_consistent_error(TypeError, /can't convert MockObject into String/)
    end

    it "raises TypeError if index is not a Symbol and not convertible to String" do
      klass = Data.define(:x, :y)
      d = klass.new(1, 2)

      -> {
        d.deconstruct_keys([0, []])
      }.should.raise(TypeError, "0 is not a symbol nor a string")
    end
  end

  it "raise TypeError if passed anything except nil or array" do
    klass = Data.define(:x, :y)
    d = klass.new(1, 2)

    -> { d.deconstruct_keys('x') }.should.raise(TypeError, /expected Array or nil/)
    -> { d.deconstruct_keys(1)   }.should.raise(TypeError, /expected Array or nil/)
    -> { d.deconstruct_keys(:x)  }.should.raise(TypeError, /expected Array or nil/)
    -> { d.deconstruct_keys({})  }.should.raise(TypeError, /expected Array or nil/)
  end
end
