https://github.com/RubyMoney/money
Raw File
Tip revision: 945ba4f68bb546ea3a506003c40104b0ae057090 authored by Shane Emmons on 08 October 2010, 21:20:19 UTC
update VERSION to 3.1.5
Tip revision: 945ba4f
money_spec.rb
# encoding: utf-8

require "spec_helper"

describe Money do

  describe "#new" do
    it "rounds the given cents to an integer" do
      Money.new(1.00, "USD").cents.should == 1
      Money.new(1.01, "USD").cents.should == 1
      Money.new(1.50, "USD").cents.should == 2
    end

    it "is associated to the singleton instance of Bank::VariableExchange by default" do
      Money.new(0).bank.should be_equal(Money::Bank::VariableExchange.instance)
    end

    # DEPRECATED: v3.5.0
    it "accepts { :currency => 'foo' } as the value for the 'currency' argument" do
      money = Money.new(20, :currency => "EUR")
      money.currency.should == Money::Currency.new("EUR")

      money = Money.new(20, :currency => nil)
      money.currency.should == Money.default_currency
    end
  end

  describe "#cents" do
    it "returns the amount of cents passed to the constructor" do
      Money.new(200_00, "USD").cents.should == 200_00
    end
  end

  describe "#dollars" do
    it "gets cents as dollars" do
      Money.new_with_dollars(1).should == Money.new(100)
      Money.new_with_dollars(1, "USD").should == Money.new(100, "USD")
      Money.new_with_dollars(1, "EUR").should == Money.new(100, "EUR")
    end

    it "should respect :subunit_to_unit currency property" do
      Money.new(1_00,  "USD").dollars.should == 1
      Money.new(1_000, "TND").dollars.should == 1
      Money.new(1,     "CLP").dollars.should == 1
    end

    it "should not loose precision" do
      Money.new(100_37).dollars.should == 100.37
      Money.new_with_dollars(100.37).dollars.should == 100.37
    end
  end

  specify "#currency returns the currency passed to the constructor" do
    Money.new(200_00, "USD").currency.should == Money::Currency.new("USD")
  end

  specify "#currency_string returns the iso_code of the currency object" do
    Money.new(200_00, "USD").currency_as_string.should == Money::Currency.new("USD").to_s
    Money.new(200_00, "USD").currency_as_string.should == "USD"
    Money.new(200_00, "EUR").currency_as_string.should == "EUR"
    Money.new(200_00, "YEN").currency_as_string.should == "YEN"
  end

  specify "#currency_string= set the currency object using the provided string" do
    obj = Money.new(200_00, "USD")
    obj.currency_as_string = "EUR"
    obj.currency.should == Money::Currency.new("EUR")
    obj.currency_as_string = "YEN"
    obj.currency.should == Money::Currency.new("YEN")
    obj.currency_as_string = "USD"
    obj.currency.should == Money::Currency.new("USD")
  end

  specify "#zero? returns whether the amount is 0" do
    Money.new(0, "USD").should be_zero
    Money.new(0, "EUR").should be_zero
    Money.new(1, "USD").should_not be_zero
    Money.new(10, "YEN").should_not be_zero
    Money.new(-1, "EUR").should_not be_zero
  end

  specify "#nonzero? returns whether the amount is not 0" do
    Money.new(0, "USD").should_not be_nonzero
    Money.new(0, "EUR").should_not be_nonzero
    Money.new(1, "USD").should be_nonzero
    Money.new(10, "YEN").should be_nonzero
    Money.new(-1, "EUR").should be_nonzero
  end

  specify "#nonzero? has the same return-value semantics as Numeric#nonzero?" do
    Money.new(0, "USD").nonzero?.should be_nil

    money = Money.new(1, "USD")
    money.nonzero?.should be_equal(money)
  end

  specify "#exchange_to exchanges the amount via its exchange bank" do
    money = Money.new(100_00, "USD")
    money.bank.should_receive(:exchange_with).with(Money.new(100_00, Money::Currency.new("USD")), Money::Currency.new("EUR")).and_return(Money.new(200_00, Money::Currency.new('EUR')))
    money.exchange_to("EUR")
  end

  specify "#exchange_to exchanges the amount properly" do
    money = Money.new(100_00, "USD")
    money.bank.should_receive(:exchange_with).with(Money.new(100_00, Money::Currency.new("USD")), Money::Currency.new("EUR")).and_return(Money.new(200_00, Money::Currency.new('EUR')))
    money.exchange_to("EUR").should == Money.new(200_00, "EUR")
  end

  specify "#== returns true if and only if their amount and currency are equal" do
    Money.new(1_00, "USD").should == Money.new(1_00, "USD")
    Money.new(1_00, "USD").should_not == Money.new(1_00, "EUR")
    Money.new(1_00, "USD").should_not == Money.new(2_00, "USD")
    Money.new(1_00, "USD").should_not == Money.new(99_00, "EUR")
  end

  specify "#== can be used to compare with a String money value" do
    Money.new(1_00, "USD").should == "1.00"
    Money.new(1_00, "USD").should_not == "2.00"
    Money.new(1_00, "GBP").should_not == "1.00"
  end

  specify "#== can be used to compare with a Numeric money value" do
    Money.new(1_00, "USD").should == 1
    Money.new(1_57, "USD").should == 1.57
    Money.new(1_00, "USD").should_not == 2
    Money.new(1_00, "GBP").should_not == 1
  end

  specify "#== can be used to compare with an object that responds to #to_money" do
    klass = Class.new do
      def initialize(money)
        @money = money
      end

      def to_money
        @money
      end
    end

    Money.new(1_00, "USD").should == klass.new(Money.new(1_00, "USD"))
    Money.new(2_50, "USD").should == klass.new(Money.new(2_50, "USD"))
    Money.new(2_50, "USD").should_not == klass.new(Money.new(3_00, "USD"))
    Money.new(1_00, "GBP").should_not == klass.new(Money.new(1_00, "USD"))
  end

  specify "#== returns false if used to compare with an object that doesn't respond to #to_money" do
    Money.new(1_00, "USD").should_not == Object.new
    Money.new(1_00, "USD").should_not == Class
    Money.new(1_00, "USD").should_not == Kernel
    Money.new(1_00, "USD").should_not == /foo/
    Money.new(1_00, "USD").should_not == nil
  end

  specify "#eql? returns true if and only if their amount and currency are equal" do
    Money.new(1_00, "USD").eql?(Money.new(1_00, "USD")).should be true
    Money.new(1_00, "USD").eql?(Money.new(1_00, "EUR")).should be false
    Money.new(1_00, "USD").eql?(Money.new(2_00, "USD")).should be false
    Money.new(1_00, "USD").eql?(Money.new(99_00, "EUR")).should be false
  end

  specify "#eql? can be used to compare with a String money value" do
    Money.new(1_00, "USD").eql?("1.00").should be true
    Money.new(1_00, "USD").eql?("2.00").should be false
    Money.new(1_00, "GBP").eql?("1.00").should be false
  end

  specify "#eql? can be used to compare with a Numeric money value" do
    Money.new(1_00, "USD").eql?(1).should be true
    Money.new(1_57, "USD").eql?(1.57).should be true
    Money.new(1_00, "USD").eql?(2).should be false
    Money.new(1_00, "GBP").eql?(1).should be false
  end

  specify "#eql? can be used to compare with an object that responds to #to_money" do
    klass = Class.new do
      def initialize(money)
        @money = money
      end

      def to_money
        @money
      end
    end

    Money.new(1_00, "USD").eql?(klass.new(Money.new(1_00, "USD"))).should be true
    Money.new(2_50, "USD").eql?(klass.new(Money.new(2_50, "USD"))).should be true
    Money.new(2_50, "USD").eql?(klass.new(Money.new(3_00, "USD"))).should be false
    Money.new(1_00, "GBP").eql?(klass.new(Money.new(1_00, "USD"))).should be false
  end

  specify "#eql? returns false if used to compare with an object that doesn't respond to #to_money" do
    Money.new(1_00, "USD").eql?(Object.new).should be false
    Money.new(1_00, "USD").eql?(Class).should be false
    Money.new(1_00, "USD").eql?(Kernel).should be false
    Money.new(1_00, "USD").eql?(/foo/).should be false
    Money.new(1_00, "USD").eql?(nil).should be false
  end

  specify "#hash should return the same value for equal objects" do
    Money.new(1_00, :eur).hash.should == Money.new(1_00, :eur).hash
    Money.new(2_00, :usd).hash.should == Money.new(2_00, :usd).hash
    Money.new(1_00, :eur).hash.should_not == Money.new(2_00, :eur).hash
    Money.new(1_00, :eur).hash.should_not == Money.new(1_00, :usd).hash
    Money.new(1_00, :eur).hash.should_not == Money.new(2_00, :usd).hash
  end

  specify "#hash can be used to return the intersection of Money object arrays" do
    intersection = [Money.new(1_00, :eur), Money.new(1_00, :usd)] & [Money.new(1_00, :eur)]
    intersection.should == [Money.new(1_00, :eur)]
  end

  specify "#<=> can be used to compare with a String money value" do
    (Money.new(1_00) <=> "1.00").should == 0
    (Money.new(1_00) <=> ".99").should > 0
    (Money.new(1_00) <=> "2.00").should < 0
  end

  specify "#<=> can be used to compare with a Numeric money value" do
    (Money.new(1_00) <=> 1).should == 0
    (Money.new(1_00) <=> 0.99).should > 0
    (Money.new(1_00) <=> 2.00).should < 0
  end

  specify "#<=> can be used to compare with an object that responds to #to_money" do
    klass = Class.new do
      def initialize(money)
        @money = money
      end

      def to_money
        @money
      end
    end

    (Money.new(1_00) <=> klass.new(Money.new(1_00))).should == 0
    (Money.new(1_00) <=> klass.new(Money.new(99))).should > 0
    (Money.new(1_00) <=> klass.new(Money.new(2_00))).should < 0
  end

  specify "#<=> raises ArgumentError when used to compare with an object that doesn't respond to #to_money" do
    expected_message = /Comparison .+ failed/
    lambda{ Money.new(1_00) <=> Object.new  }.should raise_error(ArgumentError, expected_message)
    lambda{ Money.new(1_00) <=> Class       }.should raise_error(ArgumentError, expected_message)
    lambda{ Money.new(1_00) <=> Kernel      }.should raise_error(ArgumentError, expected_message)
    lambda{ Money.new(1_00) <=> /foo/       }.should raise_error(ArgumentError, expected_message)
  end

  describe "#*" do
    it "should multiply current money amount by the multiplier while retaining the currency" do
      (Money.new(1_00, "USD") * 10).should == Money.new(10_00, "USD")
    end

    it "should multiply Money by Fixnum and returns Money" do
      ts = [
        {:a => Money.new( 10, :USD), :b =>  4, :c => Money.new( 40, :USD)},
        {:a => Money.new( 10, :USD), :b => -4, :c => Money.new(-40, :USD)},
        {:a => Money.new(-10, :USD), :b =>  4, :c => Money.new(-40, :USD)},
        {:a => Money.new(-10, :USD), :b => -4, :c => Money.new( 40, :USD)},
      ]
      ts.each do |t|
        (t[:a] * t[:b]).should == t[:c]
      end
    end

    it "should not multiply Money by Money (same currency)" do
      lambda { Money.new( 10, :USD) * Money.new( 4, :USD) }.should raise_error(ArgumentError)
    end

    it "should not multiply Money by Money (different currency)" do
      lambda { Money.new( 10, :USD) * Money.new( 4, :EUR) }.should raise_error(ArgumentError)
    end
  end

  describe "#/" do
    it "divides current money amount by the divisor while retaining the currency" do
      (Money.new(10_00, "USD") / 10).should == Money.new(1_00, "USD")
    end

    it "divides Money by Fixnum and returns Money" do
      ts = [
        {:a => Money.new( 13, :USD), :b =>  4, :c => Money.new( 3, :USD)},
        {:a => Money.new( 13, :USD), :b => -4, :c => Money.new(-4, :USD)},
        {:a => Money.new(-13, :USD), :b =>  4, :c => Money.new(-4, :USD)},
        {:a => Money.new(-13, :USD), :b => -4, :c => Money.new( 3, :USD)},
      ]
      ts.each do |t|
        (t[:a] / t[:b]).should == t[:c]
      end
    end

    it "divides Money by Money (same currency) and returns Float" do
      ts = [
        {:a => Money.new( 13, :USD), :b => Money.new( 4, :USD), :c =>  3.25},
        {:a => Money.new( 13, :USD), :b => Money.new(-4, :USD), :c => -3.25},
        {:a => Money.new(-13, :USD), :b => Money.new( 4, :USD), :c => -3.25},
        {:a => Money.new(-13, :USD), :b => Money.new(-4, :USD), :c =>  3.25},
      ]
      ts.each do |t|
        (t[:a] / t[:b]).should == t[:c]
      end
    end

    it "divides Money by Money (different currency) and returns Float" do
      ts = [
        {:a => Money.new( 13, :USD), :b => Money.new( 4, :EUR), :c =>  1.625},
        {:a => Money.new( 13, :USD), :b => Money.new(-4, :EUR), :c => -1.625},
        {:a => Money.new(-13, :USD), :b => Money.new( 4, :EUR), :c => -1.625},
        {:a => Money.new(-13, :USD), :b => Money.new(-4, :EUR), :c =>  1.625},
      ]
      ts.each do |t|
        t[:b].should_receive(:exchange_to).once.with(t[:a].currency).and_return(Money.new(t[:b].cents * 2, :USD))
        (t[:a] / t[:b]).should == t[:c]
      end
    end
  end

  specify "#div -> money / fixnum" do
    ts = [
      {:a => Money.new( 13, :USD), :b =>  4, :c => Money.new( 3, :USD)},
      {:a => Money.new( 13, :USD), :b => -4, :c => Money.new(-4, :USD)},
      {:a => Money.new(-13, :USD), :b =>  4, :c => Money.new(-4, :USD)},
      {:a => Money.new(-13, :USD), :b => -4, :c => Money.new( 3, :USD)},
    ]
    ts.each do |t|
      t[:a].div(t[:b]).should == t[:c]
    end
  end

  specify "#div -> money / money (same currency)" do
    ts = [
      {:a => Money.new( 13, :USD), :b => Money.new( 4, :USD), :c =>  3.25},
      {:a => Money.new( 13, :USD), :b => Money.new(-4, :USD), :c => -3.25},
      {:a => Money.new(-13, :USD), :b => Money.new( 4, :USD), :c => -3.25},
      {:a => Money.new(-13, :USD), :b => Money.new(-4, :USD), :c =>  3.25},
    ]
    ts.each do |t|
      t[:a].div(t[:b]).should == t[:c]
    end
  end

  specify "#div -> money / money (different currency)" do
    ts = [
      {:a => Money.new( 13, :USD), :b => Money.new( 4, :EUR), :c =>  1.625},
      {:a => Money.new( 13, :USD), :b => Money.new(-4, :EUR), :c => -1.625},
      {:a => Money.new(-13, :USD), :b => Money.new( 4, :EUR), :c => -1.625},
      {:a => Money.new(-13, :USD), :b => Money.new(-4, :EUR), :c =>  1.625},
    ]
    ts.each do |t|
      t[:b].should_receive(:exchange_to).once.with(t[:a].currency).and_return(Money.new(t[:b].cents * 2, :USD))
      t[:a].div(t[:b]).should == t[:c]
    end
  end

  specify "#divmod -> money `divmod` fixnum" do
    ts = [
      {:a => Money.new( 13, :USD), :b =>  4, :c => [Money.new( 3, :USD), Money.new( 1, :USD)]},
      {:a => Money.new( 13, :USD), :b => -4, :c => [Money.new(-4, :USD), Money.new(-3, :USD)]},
      {:a => Money.new(-13, :USD), :b =>  4, :c => [Money.new(-4, :USD), Money.new( 3, :USD)]},
      {:a => Money.new(-13, :USD), :b => -4, :c => [Money.new( 3, :USD), Money.new(-1, :USD)]},
    ]
    ts.each do |t|
      t[:a].divmod(t[:b]).should == t[:c]
    end
  end

  specify "#divmod -> money `divmod` money (same currency)" do
    ts = [
      {:a => Money.new( 13, :USD), :b => Money.new( 4, :USD), :c => [ 3, Money.new( 1, :USD)]},
      {:a => Money.new( 13, :USD), :b => Money.new(-4, :USD), :c => [-4, Money.new(-3, :USD)]},
      {:a => Money.new(-13, :USD), :b => Money.new( 4, :USD), :c => [-4, Money.new( 3, :USD)]},
      {:a => Money.new(-13, :USD), :b => Money.new(-4, :USD), :c => [ 3, Money.new(-1, :USD)]},
    ]
    ts.each do |t|
      t[:a].divmod(t[:b]).should == t[:c]
    end
  end

  specify "#divmod -> money `divmod` money (different currency)" do
    ts = [
      {:a => Money.new( 13, :USD), :b => Money.new( 4, :EUR), :c => [ 1, Money.new( 5, :USD)]},
      {:a => Money.new( 13, :USD), :b => Money.new(-4, :EUR), :c => [-2, Money.new(-3, :USD)]},
      {:a => Money.new(-13, :USD), :b => Money.new( 4, :EUR), :c => [-2, Money.new( 3, :USD)]},
      {:a => Money.new(-13, :USD), :b => Money.new(-4, :EUR), :c => [ 1, Money.new(-5, :USD)]},
    ]
    ts.each do |t|
      t[:b].should_receive(:exchange_to).once.with(t[:a].currency).and_return(Money.new(t[:b].cents * 2, :USD))
      t[:a].divmod(t[:b]).should == t[:c]
    end
  end

  specify "#modulo -> money `modulo` fixnum" do
    ts = [
      {:a => Money.new( 13, :USD), :b =>  4, :c => Money.new( 1, :USD)},
      {:a => Money.new( 13, :USD), :b => -4, :c => Money.new(-3, :USD)},
      {:a => Money.new(-13, :USD), :b =>  4, :c => Money.new( 3, :USD)},
      {:a => Money.new(-13, :USD), :b => -4, :c => Money.new(-1, :USD)},
    ]
    ts.each do |t|
      t[:a].modulo(t[:b]).should == t[:c]
    end
  end

  specify "#modulo -> money `modulo` money (same currency)" do
    ts = [
      {:a => Money.new( 13, :USD), :b => Money.new( 4, :USD), :c => Money.new( 1, :USD)},
      {:a => Money.new( 13, :USD), :b => Money.new(-4, :USD), :c => Money.new(-3, :USD)},
      {:a => Money.new(-13, :USD), :b => Money.new( 4, :USD), :c => Money.new( 3, :USD)},
      {:a => Money.new(-13, :USD), :b => Money.new(-4, :USD), :c => Money.new(-1, :USD)},
    ]
    ts.each do |t|
      t[:a].modulo(t[:b]).should == t[:c]
    end
  end

  specify "#modulo -> money `modulo` money (different currency)" do
    ts = [
      {:a => Money.new( 13, :USD), :b => Money.new( 4, :EUR), :c => Money.new( 5, :USD)},
      {:a => Money.new( 13, :USD), :b => Money.new(-4, :EUR), :c => Money.new(-3, :USD)},
      {:a => Money.new(-13, :USD), :b => Money.new( 4, :EUR), :c => Money.new( 3, :USD)},
      {:a => Money.new(-13, :USD), :b => Money.new(-4, :EUR), :c => Money.new(-5, :USD)},
    ]
    ts.each do |t|
      t[:b].should_receive(:exchange_to).once.with(t[:a].currency).and_return(Money.new(t[:b].cents * 2, :USD))
      t[:a].modulo(t[:b]).should == t[:c]
    end
  end

  specify "#% -> money % fixnum" do
    ts = [
      {:a => Money.new( 13, :USD), :b =>  4, :c => Money.new( 1, :USD)},
      {:a => Money.new( 13, :USD), :b => -4, :c => Money.new(-3, :USD)},
      {:a => Money.new(-13, :USD), :b =>  4, :c => Money.new( 3, :USD)},
      {:a => Money.new(-13, :USD), :b => -4, :c => Money.new(-1, :USD)},
    ]
    ts.each do |t|
      (t[:a] % t[:b]).should == t[:c]
    end
  end

  specify "#% -> money % money (same currency)" do
    ts = [
      {:a => Money.new( 13, :USD), :b => Money.new( 4, :USD), :c => Money.new( 1, :USD)},
      {:a => Money.new( 13, :USD), :b => Money.new(-4, :USD), :c => Money.new(-3, :USD)},
      {:a => Money.new(-13, :USD), :b => Money.new( 4, :USD), :c => Money.new( 3, :USD)},
      {:a => Money.new(-13, :USD), :b => Money.new(-4, :USD), :c => Money.new(-1, :USD)},
    ]
    ts.each do |t|
      (t[:a] % t[:b]).should == t[:c]
    end
  end

  specify "#% -> money % money (different currency)" do
    ts = [
      {:a => Money.new( 13, :USD), :b => Money.new( 4, :EUR), :c => Money.new( 5, :USD)},
      {:a => Money.new( 13, :USD), :b => Money.new(-4, :EUR), :c => Money.new(-3, :USD)},
      {:a => Money.new(-13, :USD), :b => Money.new( 4, :EUR), :c => Money.new( 3, :USD)},
      {:a => Money.new(-13, :USD), :b => Money.new(-4, :EUR), :c => Money.new(-5, :USD)},
    ]
    ts.each do |t|
      t[:b].should_receive(:exchange_to).once.with(t[:a].currency).and_return(Money.new(t[:b].cents * 2, :USD))
      (t[:a] % t[:b]).should == t[:c]
    end
  end

  specify "#remainder -> money `remainder` fixnum" do
    ts = [
      {:a => Money.new( 13, :USD), :b =>  4, :c => Money.new( 1, :USD)},
      {:a => Money.new( 13, :USD), :b => -4, :c => Money.new( 1, :USD)},
      {:a => Money.new(-13, :USD), :b =>  4, :c => Money.new(-1, :USD)},
      {:a => Money.new(-13, :USD), :b => -4, :c => Money.new(-1, :USD)},
    ]
    ts.each do |t|
      t[:a].remainder(t[:b]).should == t[:c]
    end
  end

  specify "#remainder -> money `remainder` money (same currency)" do
    ts = [
      {:a => Money.new( 13, :USD), :b => Money.new( 4, :USD), :c => Money.new( 1, :USD)},
      {:a => Money.new( 13, :USD), :b => Money.new(-4, :USD), :c => Money.new( 1, :USD)},
      {:a => Money.new(-13, :USD), :b => Money.new( 4, :USD), :c => Money.new(-1, :USD)},
      {:a => Money.new(-13, :USD), :b => Money.new(-4, :USD), :c => Money.new(-1, :USD)},
    ]
    ts.each do |t|
      t[:a].remainder(t[:b]).should == t[:c]
    end
  end

  specify "#remainder -> money `remainder` money (different currency)" do
    ts = [
      {:a => Money.new( 13, :USD), :b => Money.new( 4, :EUR), :c => Money.new( 5, :USD)},
      {:a => Money.new( 13, :USD), :b => Money.new(-4, :EUR), :c => Money.new( 5, :USD)},
      {:a => Money.new(-13, :USD), :b => Money.new( 4, :EUR), :c => Money.new(-5, :USD)},
      {:a => Money.new(-13, :USD), :b => Money.new(-4, :EUR), :c => Money.new(-5, :USD)},
    ]
    ts.each do |t|
      t[:b].should_receive(:exchange_to).once.with(t[:a].currency).and_return(Money.new(t[:b].cents * 2, :USD))
      t[:a].remainder(t[:b]).should == t[:c]
    end
  end

  specify "#abs correctly returns the absolute value as a new Money object" do
    n = Money.new(-1, :USD)
    n.abs.should == Money.new( 1, :USD)
    n.should     == Money.new(-1, :USD)
  end

  specify "Money.to_s works" do
    Money.new(10_00).to_s.should == "10.00"
  end

  specify "Money.to_s should respect :subunit_to_unit currency property" do
    Money.new(10_00, "BHD").to_s.should == "1.00"
  end

  specify "Money.to_s shouldn't have decimal when :subunit_to_unit is 1" do
    Money.new(10_00, "CLP").to_s.should == "1000"
  end

  specify "Money.to_f works" do
    Money.new(10_00).to_f.should == 10.0
  end

  specify "Money.to_f should respect :subunit_to_unit currency property" do
    Money.new(10_00, "BHD").to_f.should == 1.0
  end

  specify "#symbol works as documented" do
    currency = Money::Currency.new("EUR")
    currency.should_receive(:symbol).and_return("€")
    Money.empty(currency).symbol.should == "€"

    currency = Money::Currency.new("EUR")
    currency.should_receive(:symbol).and_return(nil)
    Money.empty(currency).symbol.should == "¤"
  end

  specify "#delimiter works as documented" do
    Money.empty("USD").delimiter.should == ","
    Money.empty("EUR").delimiter.should == ","
    Money.empty("BRL").delimiter.should == "."
  end

  specify "#separator works as documented" do
    Money.empty("USD").separator.should == "."
    Money.empty("EUR").separator.should == "."
    Money.empty("BRL").separator.should == ","
  end

  describe "#format" do
    it "returns the monetary value as a string" do
      Money.ca_dollar(100).format.should == "$1.00"
    end

    it "should respect :subunit_to_unit currency property" do
      Money.new(10_00, "BHD").format.should == "ب.د1.00"
    end

    it "doesn't display a decimal when :subunit_to_unit is 1" do
      Money.new(10_00, "CLP").format.should == "$1.000"
    end

    it "respects the delimiter and separator defaults" do
      one_thousand = Proc.new do |currency|
        Money.new(1000_00, currency).format
      end

      # Pounds
      one_thousand["GBP"].should == "£1,000.00"

      # Dollars
      one_thousand["USD"].should == "$1,000.00"
      one_thousand["CAD"].should == "$1,000.00"
      one_thousand["AUD"].should == "$1,000.00"
      one_thousand["NZD"].should == "$1,000.00"
      one_thousand["ZWD"].should == "$1,000.00"

      # Yen
      one_thousand["JPY"].should == "¥1,000.00"
      one_thousand["CNY"].should == "¥10,000.00"

      # Euro
      one_thousand["EUR"].should == "€1,000.00"

      # Rupees
      one_thousand["INR"].should == "₨1,000.00"
      one_thousand["NPR"].should == "₨1,000.00"
      one_thousand["SCR"].should == "₨1,000.00"
      one_thousand["LKR"].should == "₨1,000.00"

      # Brazilian Real
      one_thousand["BRL"].should == "R$ 1.000,00"

      # Other
      one_thousand["SEK"].should == "kr1,000.00"
      one_thousand["GHC"].should == "₵1,000.00"
    end

    describe "if the monetary value is 0" do
      before :each do
        @money = Money.us_dollar(0)
      end

      it "returns 'free' when :display_free is true" do
        @money.format(:display_free => true).should == 'free'
      end

      it "returns '$0.00' when :display_free is false or not given" do
        @money.format.should == '$0.00'
        @money.format(:display_free => false).should == '$0.00'
        @money.format(:display_free => nil).should == '$0.00'
      end

      it "returns the value specified by :display_free if it's a string-like object" do
        @money.format(:display_free => 'gratis').should == 'gratis'
      end
    end

    specify "#format(:with_currency => true) works as documented" do
      Money.ca_dollar(100).format(:with_currency => true).should == "$1.00 CAD"
      Money.us_dollar(85).format(:with_currency => true).should == "$0.85 USD"
    end

    # DEPRECATED: v3.5.0
    specify "#format(:with_currency) works as documented" do
      Money.ca_dollar(100).format(:with_currency).should == "$1.00 CAD"
      Money.us_dollar(85).format(:with_currency).should == "$0.85 USD"
    end

    specify "#format(:no_cents => true) works as documented" do
      Money.ca_dollar(100).format(:no_cents => true).should == "$1"
      Money.ca_dollar(599).format(:no_cents => true).should == "$5"
      Money.ca_dollar(570).format(:no_cents => true, :with_currency => true).should == "$5 CAD"
      Money.ca_dollar(39000).format(:no_cents => true).should == "$390"
    end

    specify "#format(:no_cents => true) should respect :subunit_to_unit currency property" do
      Money.new(10_00, "BHD").format(:no_cents => true).should == "ب.د1"
    end

    # DEPRECATED: v3.5.0
    specify "#format(:no_cents) works as documented" do
      Money.ca_dollar(100).format(:no_cents).should == "$1"
      Money.ca_dollar(599).format(:no_cents).should == "$5"
      Money.ca_dollar(570).format(:no_cents, :with_currency).should == "$5 CAD"
      Money.ca_dollar(39000).format(:no_cents).should == "$390"
    end

    # DEPRECATED: v3.5.0
    specify "#format(:no_cents) should respect :subunit_to_unit currency property" do
      Money.new(10_00, "BHD").format(:no_cents).should == "ب.د1"
    end

    specify "#format(:symbol => a symbol string) uses the given value as the money symbol" do
      Money.new(100, "GBP").format(:symbol => "£").should == "£1.00"
    end

    specify "#format(:symbol => true) returns symbol based on the given currency code" do
      one = Proc.new do |currency|
        Money.new(100, currency).format(:symbol => true)
      end

      # Pounds
      one["GBP"].should == "£1.00"

      # Dollars
      one["USD"].should == "$1.00"
      one["CAD"].should == "$1.00"
      one["AUD"].should == "$1.00"
      one["NZD"].should == "$1.00"
      one["ZWD"].should == "$1.00"

      # Yen
      one["JPY"].should == "¥1.00"
      one["CNY"].should == "¥10.00"

      # Euro
      one["EUR"].should == "€1.00"

      # Rupees
      one["INR"].should == "₨1.00"
      one["NPR"].should == "₨1.00"
      one["SCR"].should == "₨1.00"
      one["LKR"].should == "₨1.00"

      # Brazilian Real
      one["BRL"].should == "R$ 1,00"

      # Other
      one["SEK"].should == "kr1.00"
      one["GHC"].should == "₵1.00"
    end

    specify "#format(:symbol => true) returns $ when currency code is not recognized" do
      currency = Money::Currency.new("EUR")
      currency.should_receive(:symbol).and_return(nil)
      Money.new(100, currency).format(:symbol => true).should == "¤1.00"
    end

    specify "#format(:symbol => some non-Boolean value that evaluates to true) returs symbol based on the given currency code" do
      Money.new(100, "GBP").format(:symbol => true).should == "£1.00"
      Money.new(100, "EUR").format(:symbol => true).should == "€1.00"
      Money.new(100, "SEK").format(:symbol => true).should == "kr1.00"
    end

    specify "#format with :symbol == "", nil or false returns the amount without a symbol" do
      money = Money.new(100, "GBP")
      money.format(:symbol => "").should == "1.00"
      money.format(:symbol => nil).should == "1.00"
      money.format(:symbol => false).should == "1.00"
    end

    specify "#format without :symbol assumes that :symbol is set to true" do
      money = Money.new(100)
      money.format.should == "$1.00"

      money = Money.new(100, "GBP")
      money.format.should == "£1.00"

      money = Money.new(100, "EUR")
      money.format.should == "€1.00"
    end

    specify "#format(:separator => a separator string) works as documented" do
      Money.us_dollar(100).format(:separator => ",").should == "$1,00"
    end

    specify "#format will default separator to '.' if currency isn't recognized" do
      Money.new(100, "ZWD").format.should == "$1.00"
    end

    specify "#format(:delimiter => a delimiter string) works as documented" do
      Money.us_dollar(100000).format(:delimiter => ".").should == "$1.000.00"
      Money.us_dollar(200000).format(:delimiter => "").should  == "$2000.00"
    end

    specify "#format(:delimiter => false or nil) works as documented" do
      Money.us_dollar(100000).format(:delimiter => false).should == "$1000.00"
      Money.us_dollar(200000).format(:delimiter => nil).should   == "$2000.00"
    end

    specify "#format will default delimiter to ',' if currency isn't recognized" do
      Money.new(100000, "ZWD").format.should == "$1,000.00"
    end

    specify "#format(:html => true) works as documented" do
      string = Money.ca_dollar(570).format(:html => true, :with_currency => true)
      string.should == "$5.70 <span class=\"currency\">CAD</span>"
    end

    it "should insert commas into the result if the amount is sufficiently large" do
      Money.us_dollar(1_000_000_000_12).format.should == "$1,000,000,000.12"
      Money.us_dollar(1_000_000_000_12).format(:no_cents => true).should == "$1,000,000,000"
    end
  end


  describe "Money.empty" do
    it "Money.empty creates a new Money object of 0 cents" do
      Money.empty.should == Money.new(0)
    end
  end

  describe "Money.ca_dollar" do
    it "creates a new Money object of the given value in CAD" do
      Money.ca_dollar(50).should == Money.new(50, "CAD")
    end
  end

  describe "Money.us_dollar" do
    it "creates a new Money object of the given value in USD" do
      Money.us_dollar(50).should == Money.new(50, "USD")
    end
  end

  describe "Money.euro" do
    it "creates a new Money object of the given value in EUR" do
      Money.euro(50).should == Money.new(50, "EUR")
    end
  end


  describe "Money.new_with_dollars" do
    it "converts given amount to cents" do
      Money.new_with_dollars(1).should == Money.new(100)
      Money.new_with_dollars(1, "USD").should == Money.new(100, "USD")
      Money.new_with_dollars(1, "EUR").should == Money.new(100, "EUR")
    end

    it "should respect :subunit_to_unit currency property" do
      Money.new_with_dollars(1, "USD").should == Money.new(1_00,  "USD")
      Money.new_with_dollars(1, "TND").should == Money.new(1_000, "TND")
      Money.new_with_dollars(1, "CLP").should == Money.new(1,     "CLP")
    end

    it "should not loose precision" do
      Money.new_with_dollars(1234).cents.should == 1234_00
      Money.new_with_dollars(100.37).cents.should == 100_37
      Money.new_with_dollars(BigDecimal.new('1234')).cents.should == 1234_00
    end

    it "accepts a currency options" do
      m = Money.new_with_dollars(1)
      m.currency.should == Money.default_currency

      m = Money.new_with_dollars(1, Money::Currency.wrap("EUR"))
      m.currency.should == Money::Currency.wrap("EUR")

      m = Money.new_with_dollars(1, "EUR")
      m.currency.should == Money::Currency.wrap("EUR")
    end

    it "accepts a bank options" do
      m = Money.new_with_dollars(1)
      m.bank.should == Money.default_bank

      m = Money.new_with_dollars(1, "EUR", bank = Object.new)
      m.bank.should == bank
    end

    it "is associated to the singleton instance of Bank::VariableExchange by default" do
      Money.new_with_dollars(0).bank.should be_equal(Money::Bank::VariableExchange.instance)
    end
  end

  describe "Money.from_string" do
    it "converts given amount to cents" do
      Money.from_string("1").should == Money.new(1_00)
      Money.from_string("1").should == Money.new(1_00, "USD")
      Money.from_string("1", "EUR").should == Money.new(1_00, "EUR")
    end

    it "should respect :subunit_to_unit currency property" do
      Money.from_string("1", "USD").should == Money.new(1_00,  "USD")
      Money.from_string("1", "TND").should == Money.new(1_000, "TND")
      Money.from_string("1", "CLP").should == Money.new(1,     "CLP")
    end

    it "accepts a currency options" do
      m = Money.from_string("1")
      m.currency.should == Money.default_currency

      m = Money.from_string("1", Money::Currency.wrap("EUR"))
      m.currency.should == Money::Currency.wrap("EUR")

      m = Money.from_string("1", "EUR")
      m.currency.should == Money::Currency.wrap("EUR")
    end
  end

  describe "Money.from_fixnum" do
    it "converts given amount to cents" do
      Money.from_fixnum(1).should == Money.new(1_00)
      Money.from_fixnum(1).should == Money.new(1_00, "USD")
      Money.from_fixnum(1, "EUR").should == Money.new(1_00, "EUR")
    end

    it "should respect :subunit_to_unit currency property" do
      Money.from_fixnum(1, "USD").should == Money.new(1_00,  "USD")
      Money.from_fixnum(1, "TND").should == Money.new(1_000, "TND")
      Money.from_fixnum(1, "CLP").should == Money.new(1,     "CLP")
    end

    it "accepts a currency options" do
      m = Money.from_fixnum(1)
      m.currency.should == Money.default_currency

      m = Money.from_fixnum(1, Money::Currency.wrap("EUR"))
      m.currency.should == Money::Currency.wrap("EUR")

      m = Money.from_fixnum(1, "EUR")
      m.currency.should == Money::Currency.wrap("EUR")
    end
  end

  describe "Money.from_float" do
    it "converts given amount to cents" do
      Money.from_float(1.2).should == Money.new(1_20)
      Money.from_float(1.2).should == Money.new(1_20, "USD")
      Money.from_float(1.2, "EUR").should == Money.new(1_20, "EUR")
    end

    it "should respect :subunit_to_unit currency property" do
      Money.from_float(1.2, "USD").should == Money.new(1_20,  "USD")
      Money.from_float(1.2, "TND").should == Money.new(1_200, "TND")
      Money.from_float(1.2, "CLP").should == Money.new(1,     "CLP")
    end

    it "accepts a currency options" do
      m = Money.from_float(1.2)
      m.currency.should == Money.default_currency

      m = Money.from_float(1.2, Money::Currency.wrap("EUR"))
      m.currency.should == Money::Currency.wrap("EUR")

      m = Money.from_float(1.2, "EUR")
      m.currency.should == Money::Currency.wrap("EUR")
    end
  end

  describe "Money.from_bigdecimal" do
    it "converts given amount to cents" do
      Money.from_bigdecimal(BigDecimal.new("1")).should == Money.new(1_00)
      Money.from_bigdecimal(BigDecimal.new("1")).should == Money.new(1_00, "USD")
      Money.from_bigdecimal(BigDecimal.new("1"), "EUR").should == Money.new(1_00, "EUR")
    end

    it "should respect :subunit_to_unit currency property" do
      Money.from_bigdecimal(BigDecimal.new("1"), "USD").should == Money.new(1_00,  "USD")
      Money.from_bigdecimal(BigDecimal.new("1"), "TND").should == Money.new(1_000, "TND")
      Money.from_bigdecimal(BigDecimal.new("1"), "CLP").should == Money.new(1,     "CLP")
    end

    it "accepts a currency options" do
      m = Money.from_bigdecimal(BigDecimal.new("1"))
      m.currency.should == Money.default_currency

      m = Money.from_bigdecimal(BigDecimal.new("1"), Money::Currency.wrap("EUR"))
      m.currency.should == Money::Currency.wrap("EUR")

      m = Money.from_bigdecimal(BigDecimal.new("1"), "EUR")
      m.currency.should == Money::Currency.wrap("EUR")
    end
  end

  describe "Money.from_numeric" do
    it "converts given amount to cents" do
      Money.from_numeric(1).should == Money.new(1_00)
      Money.from_numeric(1.0).should == Money.new(1_00)
      Money.from_numeric(BigDecimal.new("1")).should == Money.new(1_00)
    end

    it "should raise ArgumentError with unsupported argument" do
      lambda { Money.from_numeric("100") }.should raise_error(ArgumentError)
    end

    it "should optimize workload" do
      Money.should_receive(:from_fixnum).with(1, "USD").and_return(Money.new(1_00,  "USD"))
      Money.from_numeric(1, "USD").should == Money.new(1_00,  "USD")
      Money.should_receive(:from_bigdecimal).with(BigDecimal.new("1.0"), "USD").and_return(Money.new(1_00,  "USD"))
      Money.from_numeric(1.0, "USD").should == Money.new(1_00,  "USD")
    end

    it "should respect :subunit_to_unit currency property" do
      Money.from_numeric(1, "USD").should == Money.new(1_00,  "USD")
      Money.from_numeric(1, "TND").should == Money.new(1_000, "TND")
      Money.from_numeric(1, "CLP").should == Money.new(1,     "CLP")
    end

    it "accepts a bank option" do
      Money.from_numeric(1).should == Money.new(1_00)
      Money.from_numeric(1).should == Money.new(1_00, "USD")
      Money.from_numeric(1, "EUR").should == Money.new(1_00, "EUR")
    end

    it "accepts a currency options" do
      m = Money.from_numeric(1)
      m.currency.should == Money.default_currency

      m = Money.from_numeric(1, Money::Currency.wrap("EUR"))
      m.currency.should == Money::Currency.wrap("EUR")

      m = Money.from_numeric(1, "EUR")
      m.currency.should == Money::Currency.wrap("EUR")
    end
  end


  describe "Money.add_rate" do
    it "saves rate into current bank" do
      Money.add_rate("EUR", "USD", 10)
      Money.new(10_00, "EUR").exchange_to("USD").should == Money.new(100_00, "USD")
    end
  end


  # Sets $VERBOSE to nil for the duration of the block and back to its original value afterwards.
  #
  #   silence_warnings do
  #     value = noisy_call # no warning voiced
  #   end
  #
  #   noisy_call # warning voiced
  def silence_warnings
    old_verbose, $VERBOSE = $VERBOSE, nil
    yield
  ensure
    $VERBOSE = old_verbose
  end

end

describe "Actions involving two Money objects" do
  describe "if the other Money object has the same currency" do
    specify "#<=> compares the two object amounts" do
      (Money.new(1_00, "USD") <=> Money.new(1_00, "USD")).should == 0
      (Money.new(1_00, "USD") <=> Money.new(99, "USD")).should > 0
      (Money.new(1_00, "USD") <=> Money.new(2_00, "USD")).should < 0
    end

    specify "#+ adds other amount to current amount and returns a Money while retaining the currency" do
      (Money.new(10_00, "USD") + Money.new(90, "USD")).should == Money.new(10_90, "USD")
    end

    specify "#- subtracts other amount from current amount and returns a Money while retaining the currency" do
      (Money.new(10_00, "USD") - Money.new(90, "USD")).should == Money.new(9_10, "USD")
    end

    specify "#* should raise ArgumentError" do
      lambda { Money.new(10_00, "USD") * Money.new(2, "USD") }.should raise_error(ArgumentError)
    end

    specify "#/ divides current amount by other amount and returns a Float" do
      (Money.new(10_00, "USD") / Money.new(100_00, "USD")).should == 0.1
    end
  end

  describe "if the other Money object has a different currency" do
    specify "#<=> converts other object amount to current currency, then compares the two object amounts" do
      target = Money.new(200_00, "EUR")
      target.should_receive(:exchange_to).with(Money::Currency.new("USD")).and_return(Money.new(300_00, "USD"))
      (Money.new(100_00, "USD") <=> target).should < 0

      target = Money.new(200_00, "EUR")
      target.should_receive(:exchange_to).with(Money::Currency.new("USD")).and_return(Money.new(100_00, "USD"))
      (Money.new(100_00, "USD") <=> target).should == 0

      target = Money.new(200_00, "EUR")
      target.should_receive(:exchange_to).with(Money::Currency.new("USD")).and_return(Money.new(99_00, "USD"))
      (Money.new(100_00, "USD") <=> target).should > 0
    end

    specify "#+ converts other object amount to current currency, then adds other amount to current amount and returns a Money" do
      other = Money.new(90, "EUR")
      other.should_receive(:exchange_to).with(Money::Currency.new("USD")).and_return(Money.new(9_00, "USD"))
      (Money.new(10_00, "USD") + other).should == Money.new(19_00, "USD")
    end

    specify "#- converts other object amount to current currency, then subtracts other amount from current amount and returns a Money" do
      other = Money.new(90, "EUR")
      other.should_receive(:exchange_to).with(Money::Currency.new("USD")).and_return(Money.new(9_00, "USD"))
      (Money.new(10_00, "USD") - other).should == Money.new(1_00, "USD")
    end

    specify "#* should raise ArgumentError" do
      lambda { Money.new(10_00, "USD") * Money.new(10, "EUR") }.should raise_error(ArgumentError)
    end

    specify "#/ converts other object amount to current currency, then divides current amount by other amount and returns a Float" do
      other = Money.new(1000, "EUR")
      other.should_receive(:exchange_to).with(Money::Currency.new("USD")).and_return(Money.new(100_00, "USD"))
      (Money.new(10_00, "USD") / other).should == 0.1
    end
  end
end
back to top