require File.expand_path(File.dirname(__FILE__) + '/test_helper')

class HashTest < Test::Unit::TestCase
  include Rumonade
  include MonadAxiomTestHelpers

  def test_when_unit_with_2_elt_array_returns_hash
    assert_equal({ :k => :v }, Hash.unit([:k, :v]))
  end

  def test_when_unit_with_non_2_elt_array_raises
    assert_raise(ArgumentError) { Hash.unit([1]) }
    assert_raise(ArgumentError) { Hash.unit([1, 2, 3]) }
    assert_raise(ArgumentError) { Hash.unit([1, 2, 3, 4]) }
    assert_raise(ArgumentError) { Hash.unit("not an array at all") }
  end

  def test_when_empty_returns_empty_array
    assert_equal({}, Hash.empty)
  end

  def test_monad_axioms
    f = lambda { |p| Hash.unit([p[0].downcase, p[1] * 2]) }
    g = lambda { |p| Hash.unit([p[0].upcase, p[1] * 5]) }
    [["Foo", 1], ["Bar", 2]].each do |value|
      assert_monad_axiom_1(Hash, value, f)
      assert_monad_axiom_2(Hash.unit(value))
      assert_monad_axiom_3(Hash.unit(value), f, g)
    end
  end

  def test_flat_map_behaves_correctly
    assert_equal({ "FOO" => 2, "BAR" => 4 }, { "Foo" => 1, "Bar" => 2 }.flat_map { |p| { p[0].upcase => p[1] * 2 } })
  end

  # Special case: because Hash#map is built into Ruby, must preserve existing behavior
  def test_map_still_behaves_normally_returning_array_of_arrays
   assert_equal([["FOO", 2], ["BAR", 4]], { "Foo" => 1, "Bar" => 2 }.map { |p| [p[0].upcase, p[1] * 2] })
  end

  # We add Hash#map_with_monad to return a Hash, as one might expect Hash#map to do
  def test_map_with_monad_behaves_correctly_returning_hash
    assert_equal({ "FOO" => 2, "BAR" => 4 }, { "Foo" => 1, "Bar" => 2 }.map_with_monad { |p| [p[0].upcase, p[1] * 2] })
  end

  # Special case: because Hash#flatten is built into Ruby, must preserve existing behavior
  unless (RUBY_ENGINE.to_s rescue nil) == 'rbx' # Except on Rubinius, where it's broken, apparently
    def test_flatten_still_behaves_normaly_returning_array_of_alternating_keys_and_values    
      assert_equal ["Foo", 1, "Bar", 2], { "Foo" => 1, "Bar" => 2 }.flatten
    end
  end

  def test_shallow_flatten_raises_type_error
    assert_raise(TypeError) { { "Foo" => "Bar" }.shallow_flatten }
  end

  def test_get_returns_option_of_value
    assert_equal None, { "Foo" => 1, "Bar" => 2 }.get("Baz")
    assert_equal Some(1), { "Foo" => 1, "Bar" => 2 }.get("Foo")
  end
end