require File.expand_path("../../test_helper", __FILE__)

class ExtraDataTest < Test::Unit::TestCase
  setup do
    @parent = mock("parent")
    @interface = mock("interface")
    @ed = VirtualBox::ExtraData.new(@parent, @interface)
    @ed["foo"] = "bar"
    @ed.clear_dirty!
  end

  context "relationships" do
    setup do
      @caller = mock("caller")
      @caller.stubs(:name).returns("foocaller")
    end

    context "populating" do
      setup do
        @interface = mock("interface")
        @interface.stubs(:get_extra_data_keys).returns([])
      end

      should "return a ExtraData object" do
        result = VirtualBox::ExtraData.populate_relationship(@caller, @interface)
        assert result.is_a?(VirtualBox::ExtraData)
      end

      should "not be dirty" do
        result = VirtualBox::ExtraData.populate_relationship(@caller, @interface)
        assert !result.changed?
      end

      should "add the value of each key to the ED hash" do
        expected_hash = { :a => 1, :b => 2, :c => 4 }
        @interface.expects(:get_extra_data_keys).returns(expected_hash.keys)

        expected_hash.each do |k, v|
          @interface.expects(:get_extra_data).with(k).returns(v)
        end

        result = VirtualBox::ExtraData.populate_relationship(@caller, @interface)
        assert_equal expected_hash.length, result.length
        result.each do |k, v|
          assert_equal expected_hash[k], v
        end
      end
    end

    context "saving" do
      should "call save on the ExtraData object" do
        object = mock("object")
        object.expects(:save).once

        VirtualBox::ExtraData.save_relationship(@caller, object)
      end
    end
  end

  context "setting dirty state" do
    setup do
      @ed = VirtualBox::ExtraData.new(@parent, @interface)
    end

    should "not be dirty initially" do
      assert !@ed.changed?
    end

    should "be dirty when setting a value" do
      @ed["foo"] = "bar"
      assert @ed.changed?
      assert @ed.changes.has_key?(:foo)
    end
  end

  context "global extra data" do
    setup do
      global = mock("global")
      global.expects(:extra_data).once.returns("foo")
      VirtualBox::Global.expects(:global).returns(global)
      @global = VirtualBox::ExtraData.global(true)
    end

    should "call the global extra data if it has never been loaded" do
      assert_equal "foo", VirtualBox::ExtraData.global
    end

    should "return the same object if it exists for global data, rather than recreating it" do
      VirtualBox::Global.expects(:global).never
      assert_equal @global, VirtualBox::ExtraData.global
    end

    should "return a new object if reload is true" do
      global = mock("global")
      global.expects(:extra_data).once.returns("bar")
      VirtualBox::Global.expects(:global).returns(global)
      assert !@global.equal?(VirtualBox::ExtraData.global(true))
    end
  end

  context "constructor" do
    should "set the parent and interface with the given argument" do
      ed = VirtualBox::ExtraData.new("JOEY", @interface)
      assert_equal "JOEY", ed.parent
      assert_equal @interface, ed.interface
    end
  end

  context "saving extra data" do
    setup do
      @interface.stubs(:set_extra_data)
    end

    should "only save changed keys" do
      @interface.expects(:set_extra_data).never
      @interface.expects(:set_extra_data).with("bar", "baz").once

      @ed["bar"] = "baz"
      @ed.save
    end

    should "convert values to strings" do
      @interface.expects(:set_extra_data).never
      @interface.expects(:set_extra_data).with("bar", "7").once

      @ed["bar"] = 7
      @ed.save
    end

    should "clear the dirty status of keys" do
      @ed["bar"] = "baz"
      assert @ed.bar_changed?
      @ed.save
      assert !@ed.bar_changed?
    end

    should "remove nil keys from the hash" do
      @ed["bar"] = "baz"
      @ed.save
      @ed["bar"] = nil
      assert @ed.bar_changed?
      @ed.save
      assert !@ed.keys.include?("bar")
    end
  end

  context "deleteting extra data" do
    should "remove it from the interface and the hash" do
      key = :foo
      @interface.expects(:set_extra_data).with(key.to_s, nil).once
      @ed.expects(:hash_delete).with(key.to_s)
      @ed.delete(key)
    end
  end
end
