require "spec_helper"
require "hamster/hash"

describe Hamster::Hash do
  describe "#[]=" do
    it 'raises error pointing to #put' do
      expect { subject[:A] = 'aye' }
        .to raise_error(NoMethodError, /Hamster::Hash.*`put'/)
    end
  end

  describe "#put" do
    let(:hash) { H["A" => "aye", "B" => "bee", "C" => "see"] }

    context "with a block" do
      it "passes the value to the block" do
        hash.put("A") { |value| expect(value).to eq("aye") }
      end

      it "replaces the value with the result of the block" do
        result = hash.put("A") { |value| "FLIBBLE" }
        expect(result.get("A")).to eq("FLIBBLE")
      end

      it "supports to_proc methods" do
        result = hash.put("A", &:upcase)
        expect(result.get("A")).to eq("AYE")
      end

      context "if there is no existing association" do
        it "passes nil to the block" do
          hash.put("D") { |value| expect(value).to be_nil }
        end

        it "stores the result of the block as the new value" do
          result = hash.put("D") { |value| "FLIBBLE" }
          expect(result.get("D")).to eq("FLIBBLE")
        end
      end
    end

    context "with a unique key" do
      let(:result) { hash.put("D", "dee") }

      it "preserves the original" do
        result
        expect(hash).to eql(H["A" => "aye", "B" => "bee", "C" => "see"])
      end

      it "returns a copy with the superset of key/value pairs" do
        expect(result).to eql(H["A" => "aye", "B" => "bee", "C" => "see", "D" => "dee"])
      end
    end

    context "with a duplicate key" do
      let(:result) { hash.put("C", "sea") }

      it "preserves the original" do
        result
        expect(hash).to eql(H["A" => "aye", "B" => "bee", "C" => "see"])
      end

      it "returns a copy with the superset of key/value pairs" do
        expect(result).to eql(H["A" => "aye", "B" => "bee", "C" => "sea"])
      end
    end

    context "with duplicate key and identical value" do
      let(:hash) { H["X" => 1, "Y" => 2] }
      let(:result) { hash.put("X", 1) }

      it "returns the original hash unmodified" do
        expect(result).to be(hash)
      end

      context "with big hash (force nested tries)" do
        let(:keys) { (0..99).map(&:to_s) }
        let(:values) { (100..199).to_a }
        let(:hash) { H[keys.zip(values)] }

        it "returns the original hash unmodified for all changes" do
          keys.each_with_index do |key, index|
            result = hash.put(key, values[index])
            expect(result).to be(hash)
          end
        end
      end
    end

    context "with unequal keys which hash to the same value" do
      let(:hash) { H[DeterministicHash.new('a', 1) => 'aye'] }

      it "stores and can retrieve both" do
        result = hash.put(DeterministicHash.new('b', 1), 'bee')
        expect(result.get(DeterministicHash.new('a', 1))).to eql('aye')
        expect(result.get(DeterministicHash.new('b', 1))).to eql('bee')
      end
    end

    context "when a String is inserted as key and then mutated" do
      it "is not affected" do
        string = "a string!"
        hash = H.empty.put(string, 'a value!')
        string.upcase!
        expect(hash['a string!']).to eq('a value!')
        expect(hash['A STRING!']).to be_nil
      end
    end
  end
end
