require 'spec_helper'

module Danconia
  describe Money do
    context 'instantiation' do
      it 'should accept integers and strings' do
        expect(Money(10).amount).to eq BigDecimal('10')
        expect(Money('10.235').amount).to eq BigDecimal('10.235')
      end

      it 'non numeric values are treated as zero' do
        expect(Money('a')).to eq Money(0)
      end

      it 'should use the default currency if not specified' do
        with_config do |config|
          config.default_currency = 'ARS'
          expect(Money(0).currency.code).to eq 'ARS'

          config.default_currency = 'EUR'
          expect(Money(0).currency.code).to eq 'EUR'
        end
      end
    end

    context 'arithmetic' do
      it 'does the operation on the amount' do
        expect(Money(3) + Money(2)).to eq Money(5)
        expect(Money(3.5) + 0.5).to eq Money(4)
        expect(Money(78.55) * 0.25).to eq Money(19.6375)
        expect(Money(3) / 2).to eq Money(1.5)
        expect { Money(3.5) + 'a' }.to raise_error TypeError
      end

      it 'should preserve the currency' do
        expect(Money(1, 'ARS') + 2).to eq Money(3, 'ARS')
        expect(Money(1, 'ARS') + Money(2, 'ARS')).to eq Money(3, 'ARS')
      end

      it 'should exchange the other currency if it is different' do
        expect(Money(1, 'ARS') + Money(1, 'USD', exchange: fake_exchange(rate: 4))).to eq Money(5, 'ARS')
      end

      it 'should return a new object with the same options' do
        e = fake_exchange
        m1 = Money(4, decimals: 3, exchange: e)
        m2 = m1 * 2
        expect(m2).to_not eql m1
        expect(m2.decimals).to eq 3
        expect(m2.exchange).to eq e
      end

      it 'round should return a money object with the same currency' do
        expect(Money(1.9, 'ARS').round).to eq Money(2, 'ARS')
      end
    end

    context 'comparisson' do
      it 'two money objects are equal when the amount and currency are the same' do
        expect(Money(1.01)).to eq Money(1.01)
        expect(Money(1.01)).not_to eq Money(1.02)
        expect(Money(1, 'ARS')).to eq Money(1, 'ARS')
        expect(Money(1, 'USD')).not_to eq Money(1, 'ARS')
      end

      it 'allows to compare against numeric values when using the default currency' do
        expect(Money(1)).to eq 1
        expect(Money(1.35)).to eq 1.35
        expect(Money(1)).not_to eq 2
        expect(Money(1, 'ARS')).not_to eq 1
      end

      it 'should exchange to the source currency if they differ' do
        with_config do |config|
          config.default_exchange = fake_exchange(rate: 4)

          expect(Money(3, 'ARS') < Money(1, 'USD')).to be true
          expect(Money(4, 'ARS') < Money(1, 'USD')).to be false
        end
      end

      it 'should work with uniq' do
        expect([Money(1), Money(1)].uniq.size).to eq 1
        expect([Money(1), Money(1.1)].uniq.size).to eq 2
        expect([Money(1, 'ARS'), Money(1, 'USD')].uniq.size).to eq 2
      end
    end

    context 'to_s' do
      it 'should add the currency symbol' do
        expect(Money(3.25).to_s).to eq '$3.25'

        with_config do |config|
          config.default_exchange = double 'exchange', available_currencies: [{code: 'EUR', symbol: '€'}, {code: 'JPY', symbol: '¥'}]

          expect(Money(1, 'EUR').to_s).to eq '€1.00'
          expect(Money(1, 'JPY').to_s).to eq '¥1.00'
          expect(Money(1, 'OTHER').to_s).to eq '$1.00'
        end
      end

      it 'should round according to decimals' do
        expect(Money(3.256, decimals: 2).to_s).to eq '$3.26'
        expect(Money(3.2517, decimals: 3).to_s).to eq '$3.252'
      end

      it 'nil should be like zero' do
        expect(Money(nil).to_s).to eq '$0.00'
      end
    end

    context '.inspect' do
      it 'should display the object internals' do
        expect(Money(10.25, 'ARS', decimals: 3).inspect).to eq '#<Danconia::Money 10.25 ARS>'
      end
    end

    context 'exchange_to' do
      it 'should use the exchange passed to the instance to get the rate' do
        expect(Money(2, 'USD', exchange: fake_exchange(rate: 3)).exchange_to('ARS')).to eq Money(6, 'ARS')
      end

      it 'should use the default exchange if not set' do
        with_config do |config|
          config.default_exchange = Exchanges::FixedRates.new(rates: {'USDEUR' => 3, 'USDARS' => 4})
          expect(Money(2, 'USD').exchange_to('EUR')).to eq Money(6, 'EUR')
          expect(Money(2, 'USD').exchange_to('ARS')).to eq Money(8, 'ARS')
        end
      end

      it 'if no rate if found should raise error' do
        expect { Money(2, 'USD').exchange_to('ARS') }.to raise_error Errors::ExchangeRateNotFound
      end

      it 'exchange between the same currency is always 1' do
        expect(Money(5, 'EUR').exchange_to('EUR')).to eq Money(5, 'EUR')
      end

      it 'should return a new object with the same opts' do
        m1 = Money(1, 'USD', decimals: 0, exchange: fake_exchange(rate: 3))
        m2 = m1.exchange_to('ARS')
        expect(m2).to_not eql m1
        expect(m2.decimals).to eq 0
        expect(m1).to eq Money(1, 'USD')
      end
    end

    context 'delegation' do
      it 'should delegate missing methods to the amount' do
        expect(Money(10).positive?).to be true
        expect(Money(10).respond_to?(:positive?)).to be true
      end
    end

    def fake_exchange args = {}
      double 'exchange', args.reverse_merge(rate: nil, available_currencies: [])
    end
  end
end