# encoding: utf-8 require "spec_helper" describe Money, "parsing" do bar = '{ "priority": 1, "iso_code": "BAR", "iso_numeric": "840", "name": "Dollar with 4 decimal places", "symbol": "$", "subunit": "Cent", "subunit_to_unit": 10000, "symbol_first": true, "html_entity": "$", "decimal_mark": ".", "thousands_separator": "," }' eu4 = '{ "priority": 1, "iso_code": "EU4", "iso_numeric": "841", "name": "Euro with 4 decimal places", "symbol": "€", "subunit": "Cent", "subunit_to_unit": 10000, "symbol_first": true, "html_entity": "€", "decimal_mark": ",", "thousands_separator": "." }' describe ".parse" do describe "on different values" do STRING_TO_MONEY = { "20.15" => Money.new(20.15), "100" => Money.new(100.00), "100.37" => Money.new(100.37), "100,37" => Money.new(100.37), "100 000" => Money.new(100_000.00), "100,000.00" => Money.new(100_000.00), "1,000" => Money.new(1_000.00), "-1,000" => Money.new(-1_000.00), "1,000.5" => Money.new(1_000.50), "1,000.51" => Money.new(1_000.51), "1,000.505" => Money.new(1_000.505), "1,000.504" => Money.new(1_000.504), "1,000.5055" => Money.new(1_000.506), "1,000.5044" => Money.new(1_000.504), "1,000.0000" => Money.new(1_000.00), "1,000.5000" => Money.new(1_000.50), "1,000.5099" => Money.new(1_000.510), "1.550" => Money.new(1.550), "25." => Money.new(25.00), ".75" => Money.new(0.75), "100 USD" => Money.new(100.00, "USD"), "-100 USD" => Money.new(-100.00, "USD"), "100 EUR" => Money.new(100.00, "EUR"), "100.37 EUR" => Money.new(100.37, "EUR"), "100,37 EUR" => Money.new(100.37, "EUR"), "100,000.00 USD" => Money.new(100_000.00, "USD"), "100.000,00 EUR" => Money.new(100_000.00, "EUR"), "1,000 USD" => Money.new(1_000.00, "USD"), "-1,000 USD" => Money.new(-1_000.00, "USD"), "1,000.5500 USD" => Money.new(1_000.55, "USD"), "-1,000.6500 USD" => Money.new(-1_000.65, "USD"), "1.550 USD" => Money.new(1.55, "USD"), "USD 100" => Money.new(100.00, "USD"), "EUR 100" => Money.new(100.00, "EUR"), "EUR 100.37" => Money.new(100.37, "EUR"), "CAD -100.37" => Money.new(-100.37, "CAD"), "EUR 100,37" => Money.new(100.37, "EUR"), "EUR -100,37" => Money.new(-100.37, "EUR"), "USD 100,000.00" => Money.new(100_000.00, "USD"), "EUR 100.000,00" => Money.new(100_000.00, "EUR"), "USD 1,000" => Money.new(1_000.00, "USD"), "USD -1,000" => Money.new(-1_000.00, "USD"), "USD 1,000.9000" => Money.new(1_000.90, "USD"), "USD -1,000.090" => Money.new(-1_000.09, "USD"), "USD 1.5500" => Money.new(1.55, "USD"), "$100 USD" => Money.new(100.00, "USD"), "$1,194.59 USD" => Money.new(1_194.59, "USD"), "$-1,955 USD" => Money.new(-1_955.00, "USD"), "$1,194.5900 USD" => Money.new(1_194.59, "USD"), "$-1,955.000 USD" => Money.new(-1_955.00, "USD"), "$1.99000 USD" => Money.new(1.99, "USD"), } it "works as documented" do STRING_TO_MONEY.each do |string, money| Money.parse(string).should == money end end end it "coerces input to string" do Money.parse(20, "USD").should == Money.new(20.00, "USD") end it "raises error if optional currency doesn't match string currency" do expect { Money.parse("10.10 USD", 'EUR') }.to raise_error(/Mismatching Currencies/) end it "ignores unrecognized data" do Money.parse("hello 2000 world").should == Money.new(2000.00) end it "parses european-formatted inputs under 10EUR" do five_ninety_five = Money.new(5.95, 'EUR') Money.parse('EUR 5,95').should == five_ninety_five #TODO: try and handle these #Money.parse('€5,95').should == five_ninety_five #Money.parse('$5.95').should == five_ninety_five end it "parses european-formatted inputs with multiple thousands-seperators" do Money.parse('EUR 1.234.567,89').should == Money.new(1234567.89, 'EUR') Money.parse('EUR 1.111.234.567,89').should == Money.new(1111234567.89, 'EUR') end describe 'currency assumption' do context 'opted in' do before do Money.assume_from_symbol = true end it "parses formatted inputs with the currency passed as a symbol" do Money.parse("$5.95").should == Money.new(5.95, 'USD') Money.parse("€5.95").should == Money.new(5.95, 'EUR') Money.parse(" €5.95 ").should == Money.new(5.95, 'EUR') Money.parse("£9.99").should == Money.new(9.99, 'GBP') end it 'should assume default currency if not a recognised symbol' do Money.parse("L9.99").should == Money.new(9.99, 'USD') end end context 'opted out' do before do Money.assume_from_symbol = false end it "parses formatted inputs with the currency passed as a symbol but ignores the symbol" do Money.parse("$5.95").should == Money.new(5.95, 'USD') Money.parse("€5.95").should == Money.new(5.95, 'USD') Money.parse(" €5.95 ").should == Money.new(5.95, 'USD') Money.parse("£9.99").should == Money.new(9.99, 'USD') end end it 'should opt out by default' do Money.assume_from_symbol.should be_false end end it "parses USD-formatted inputs under $10" do five_ninety_five = Money.new(5.95, 'USD') Money.parse(5.95).should == five_ninety_five Money.parse('5.95').should == five_ninety_five Money.parse('$5.95').should == five_ninety_five Money.parse("\n $5.95 \n").should == five_ninety_five Money.parse('$ 5.95').should == five_ninety_five Money.parse('$5.95 ea.').should == five_ninety_five Money.parse('$5.95, each').should == five_ninety_five end it "parses USD-formatted inputs with multiple thousands-seperators" do Money.parse('1,234,567.89').should == Money.new(1234567.89, 'USD') Money.parse('1,111,234,567.89').should == Money.new(1111234567.89, 'USD') end it "does not return a price if there is a price range" do lambda { Money.parse('$5.95-10.95') }.should raise_error ArgumentError lambda { Money.parse('$5.95 - 10.95') }.should raise_error ArgumentError lambda { Money.parse('$5.95 - $10.95') }.should raise_error ArgumentError end it "does not return a price for completely invalid input" do # TODO: shouldn't these throw an error instead of being considered # equal to $0.0? empty_price = Money.new(0, 'USD') Money.parse(nil).should == empty_price Money.parse('hellothere').should == empty_price Money.parse('').should == empty_price end it "handles negative inputs" do five_ninety_five = Money.new(5.95, 'USD') Money.parse("$-5.95").should == -five_ninety_five Money.parse("-$5.95").should == -five_ninety_five Money.parse("$5.95-").should == -five_ninety_five end it "raises ArgumentError when unable to detect polarity" do lambda { Money.parse('-$5.95-') }.should raise_error ArgumentError end it "parses correctly strings with exactly 3 decimal digits" do Money.parse("6,534", "EUR").should == Money.new(6.534, "EUR") end context "custom currencies with 4 decimal places" do before :each do Money::Currency.register(JSON.parse(bar, :symbolize_names => true)) Money::Currency.register(JSON.parse(eu4, :symbolize_names => true)) end it "parses strings respecting subunit to unit, decimal and thousands separator" do Money.parse("$0.4", "BAR").should == Money.new(0.4, "BAR") Money.parse("€0,4", "EU4").should == Money.new(0.4, "EU4") Money.parse("$0.04", "BAR").should == Money.new(0.04, "BAR") Money.parse("€0,04", "EU4").should == Money.new(0.04, "EU4") Money.parse("$0.004", "BAR").should == Money.new(0.004, "BAR") Money.parse("€0,004", "EU4").should == Money.new(0.004, "EU4") Money.parse("$0.0004", "BAR").should == Money.new(0.0004, "BAR") Money.parse("€0,0004", "EU4").should == Money.new(0.0004, "EU4") Money.parse("$0.0024", "BAR").should == Money.new(0.0024, "BAR") Money.parse("€0,0024", "EU4").should == Money.new(0.0024, "EU4") Money.parse("$0.0324", "BAR").should == Money.new(0.0324, "BAR") Money.parse("€0,0324", "EU4").should == Money.new(0.0324, "EU4") Money.parse("$0.5324", "BAR").should == Money.new(0.5324, "BAR") Money.parse("€0,5324", "EU4").should == Money.new(0.5324, "EU4") Money.parse("$6.5324", "BAR").should == Money.new(6.5324, "BAR") Money.parse("€6,5324", "EU4").should == Money.new(6.5324, "EU4") Money.parse("$86.5324", "BAR").should == Money.new(86.5324, "BAR") Money.parse("€86,5324", "EU4").should == Money.new(86.5324, "EU4") Money.parse("$186.5324", "BAR").should == Money.new(186.5324, "BAR") Money.parse("€186,5324", "EU4").should == Money.new(186.5324, "EU4") Money.parse("$3,331.0034", "BAR").should == Money.new(3331.0034, "BAR") Money.parse("€3.331,0034", "EU4").should == Money.new(3331.0034, "EU4") Money.parse("$8,883,331.0034", "BAR").should == Money.new(8883331.0034, "BAR") Money.parse("€8.883.331,0034", "EU4").should == Money.new(8883331.0034, "EU4") end end end describe ".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 "respects :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 ".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 ".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 "respects :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.2, "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 ".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 "respects :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 ".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 "raises ArgumentError with unsupported argument" do lambda { Money.from_numeric("100") }.should raise_error(ArgumentError) end it "respects :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 ".extract_cents" do it "correctly treats pipe marks '|' in input (regression test)" do Money.extract_subunits('100|0').should == Money.extract_subunits('100!0') end end context "given the same inputs to .parse and .from_*" do it "gives the same results" do 4.635.to_money.should == Money.parse("4.635") end end end